diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 389bcda90d..eb77167ce8 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 e826628a07..4ea110cb13 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 @@ -126,7 +126,8 @@ runs: if [ -n "$covered" ] && [ -n "$missed" ]; then local total=$((covered + missed)) if [ "$total" -gt 0 ]; then - echo "scale=1; $covered * 100 / $total" | bc + awk -v covered="$covered" -v total="$total" \ + 'BEGIN { printf "%.1f\n", covered * 100 / total }' else echo "0" fi diff --git a/.github/actions/setup-copilot/action.yml b/.github/actions/setup-copilot/action.yml index 3769bc3751..506f472ca2 100644 --- a/.github/actions/setup-copilot/action.yml +++ b/.github/actions/setup-copilot/action.yml @@ -23,16 +23,15 @@ runs: - name: Set CLI path id: cli-path run: | - # 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. - cli_path=$(ls "$(pwd)"/nodejs/node_modules/@github/copilot-*/index.js 2>/dev/null | head -n1) + cli_path=$(npm --prefix "$(pwd)/nodejs" run --silent prepare:runtime -- --print-path) if [ -z "$cli_path" ]; then - echo "Could not find @github/copilot platform package (index.js) under nodejs/node_modules" >&2 + echo "Could not prepare the Copilot CLI runtime" >&2 exit 1 fi echo "path=$cli_path" >> $GITHUB_OUTPUT shell: bash - name: Verify CLI works - run: node ${{ steps.cli-path.outputs.path }} --version + run: | + legacy_cli=$(npm --prefix "$(pwd)/nodejs" run --silent prepare:runtime -- --print-legacy-path) + node "$legacy_cli" --version shell: bash diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 79fa9522c9..476f4e5689 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 ✅ @@ -47,7 +47,7 @@ - Tools: each SDK has helper APIs to expose functions as tools; prefer the language's `DefineTool`/`@define_tool`/`CopilotTool.DefineTool` patterns (see language READMEs). - Infinite sessions are enabled by default and persist workspace state to `~/.copilot/session-state/{sessionId}`; compaction events are emitted (`session.compaction_start`, `session.compaction_complete`). See language READMEs for usage. - Streaming: when `streaming`/`Streaming=true` you receive delta events (`assistant.message_delta`, `assistant.reasoning_delta`) and final events (`assistant.message`, `assistant.reasoning`) — tests expect this behavior. -- Type generation is centralized in `nodejs/scripts/generate-session-types.ts` and requires the `@github/copilot` schema to be present (often via `npm link` or installed package). +- Type generation is centralized in `scripts/codegen/` and downloads schemas from the pinned `github/copilot-cli` release. - Java code style: 4-space indent (Spotless + Eclipse formatter), fluent setter pattern for config classes, Javadoc required on public APIs (enforced by Checkstyle, except `json`/`events` packages). - Java handlers return `CompletableFuture` (the Java equivalent of C# `async/await`). When porting from .NET: convert properties → getters/fluent setters, use Jackson (`ObjectMapper`, `@JsonProperty`) for serialization. @@ -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 the pinned Copilot CLI version, run `cd nodejs && npm run generate`, and commit generated files in each language's 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/skills/new-java-e2e-test-yaml-and-test/SKILL.md b/.github/skills/new-java-e2e-test-yaml-and-test/SKILL.md index 060228d553..a0fd94b943 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 @@ -33,7 +33,7 @@ The format is: ```yaml models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system @@ -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/skills/new-java-e2e-test-yaml-and-test/examples.md b/.github/skills/new-java-e2e-test-yaml-and-test/examples.md index af82ef4dba..12971244a2 100644 --- a/.github/skills/new-java-e2e-test-yaml-and-test/examples.md +++ b/.github/skills/new-java-e2e-test-yaml-and-test/examples.md @@ -8,7 +8,7 @@ File: `test/snapshots/system_message_sections/should_use_replaced_identity_secti ```yaml models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system @@ -73,7 +73,7 @@ File: `test/snapshots/system_message_transform/should_invoke_transform_callbacks ```yaml models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: # First exchange: model decides to call tools - messages: diff --git a/.github/workflows/codegen-check.yml b/.github/workflows/codegen-check.yml index 78927f1600..f37a71e45e 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/docs-validation.yml b/.github/workflows/docs-validation.yml index 4f53b71e4c..dff02f0d26 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 fa2e2dc757..9b7216ad20 100644 --- a/.github/workflows/dotnet-sdk-tests.yml +++ b/.github/workflows/dotnet-sdk-tests.yml @@ -1,9 +1,6 @@ name: ".NET SDK Tests" on: - push: - branches: - - main workflow_dispatch: workflow_call: @@ -11,6 +8,40 @@ permissions: contents: read jobs: + validate: + name: ".NET SDK Build and Format" + if: github.event.repository.fork == false + runs-on: ubuntu-latest + timeout-minutes: 20 + defaults: + run: + shell: bash + working-directory: ./dotnet + steps: + - uses: actions/checkout@v6.0.2 + - uses: actions/setup-dotnet@v5 + with: + dotnet-version: "10.0.x" + - uses: actions/setup-node@v6 + with: + node-version: "22" + + - name: Restore .NET dependencies + run: dotnet restore + + - name: Run dotnet format check + run: | + if ! dotnet format --no-restore --verify-no-changes; then + echo "❌ dotnet format produced changes. Please run 'dotnet format' in dotnet" + exit 1 + fi + echo "✅ dotnet format produced no changes" + + # Build the whole solution once to validate every SDK target framework. + # Matrix cells below only need to build frameworks consumed by the tests. + - name: Build SDK + run: dotnet build --no-restore + test: name: ".NET SDK Tests (${{ matrix.os }}, ${{ matrix.transport }}, ${{ matrix.backend }}, ${{ matrix.shard }})" if: github.event.repository.fork == false @@ -32,6 +63,23 @@ jobs: - os: windows-latest transport: default shard: full + # TODO(cli-1.0.81-2): CLI 1.0.81-5 still stops completing in-process + # CAPI model turns, causing repeated per-test timeouts until the + # 30-minute job limit. Stdio CAPI and in-process BYOK remain enabled. + - os: ubuntu-latest + transport: inprocess + - os: macos-latest + transport: inprocess + # The macOS default/capi host runs the whole suite on the smallest + # runner in the matrix (3 vCPU / 7 GB vs ubuntu's 4 / 16). Since the + # 1.0.81-2 bump it stopped finishing: the job ran 50+ minutes until + # the runner lost communication with the server, and a runner that + # dies that way uploads no logs at all, so the failure could not be + # diagnosed from CI output. Split it across isolated hosts so one + # poisoned runtime cannot consume the entire job without diagnostics. + - os: macos-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 @@ -45,6 +93,80 @@ jobs: transport: default backend: capi shard: "2" + - os: macos-latest + transport: default + backend: capi + shard: "1" + - os: macos-latest + transport: default + backend: capi + shard: "2a" + - os: macos-latest + transport: default + backend: capi + shard: "2b-pending" + - os: macos-latest + transport: default + backend: capi + shard: "2b-permission" + - os: macos-latest + transport: default + backend: capi + shard: "2b-auth" + - os: macos-latest + transport: default + backend: capi + shard: "2b-hooks" + - os: macos-latest + transport: default + backend: capi + shard: "2b-unit-p" + - os: macos-latest + transport: default + backend: capi + shard: "2b-provider" + - os: macos-latest + transport: default + backend: capi + shard: "2b-rpc-additional" + - os: macos-latest + transport: default + backend: capi + shard: "2b-rpc-agent" + - os: macos-latest + transport: default + backend: capi + shard: "2b-rpc-event-log" + - os: macos-latest + transport: default + backend: capi + shard: "2b-rpc-event-effects" + - os: macos-latest + transport: default + backend: capi + shard: "2b-rpc-mcp-skills" + - os: macos-latest + transport: default + backend: capi + shard: "2b-rpc-mcp-config" + - os: macos-latest + transport: default + backend: capi + shard: "2b-rpc-mcp-lifecycle" + - os: macos-latest + transport: default + backend: capi + shard: "2b-rpc-q-z" + # Standalone extension tests require the legacy JavaScript entrypoint. + # Keep its subprocess tree isolated from the rest of the O/P/R shard. + - os: macos-latest + transport: default + backend: capi + shard: extensions + - os: macos-latest + transport: default + backend: capi + shard: "2c" - os: ubuntu-latest transport: inprocess backend: anthropic-messages @@ -61,6 +183,10 @@ jobs: shard: full test-filter: "FullyQualifiedName~GitHub.Copilot.Test.E2E&E2EBackend!=SelfConfiguredBackend&E2EBackend!=CapiOnly" runs-on: ${{ matrix.os }} + # A hung test used to run until the runner died (~50 min) and the dying + # runner never uploaded its logs, so the failures were undiagnosable. + # Every healthy cell finishes well under 15 min. + timeout-minutes: 20 defaults: run: shell: bash @@ -74,27 +200,16 @@ jobs: with: node-version: "22" cache: "npm" - cache-dependency-path: "./nodejs/package-lock.json" + cache-dependency-path: | + ./nodejs/package-lock.json + ./test/harness/package-lock.json - - name: Install Node.js dependencies (for CLI version extraction) + - name: Install Node.js dependencies working-directory: ./nodejs run: npm ci --ignore-scripts - name: Restore .NET dependencies - run: dotnet restore - - - name: Run dotnet format check - if: runner.os == 'Linux' - run: | - dotnet format --verify-no-changes - if [ $? -ne 0 ]; then - echo "❌ dotnet format produced changes. Please run 'dotnet format' in dotnet" - exit 1 - fi - echo "✅ dotnet format produced no changes" - - - name: Build SDK - run: dotnet build --no-restore + run: dotnet restore test/GitHub.Copilot.SDK.Test.csproj - name: Install test harness dependencies working-directory: ./test/harness @@ -113,17 +228,104 @@ jobs: COPILOT_HMAC_KEY: ${{ secrets.COPILOT_DEVELOPER_CLI_INTEGRATION_HMAC_KEY }} DOTNET_TEST_SHARD: ${{ matrix.shard }} run: | - args=(--no-build -v n) + # --blame-hang names the offending test instead of letting it wedge + # the runner. No single test legitimately runs for 10 minutes; the + # whole suite normally finishes in about five. + # The validation job performs the full analyzer-enabled SDK build. + # Build only test-consumed frameworks here and do not repeat analyzers. + args=( + --no-restore + -v n + --blame-hang + --blame-hang-timeout 10m + --blame-hang-dump-type none + --logger "trx;LogFilePrefix=test-results" + --results-directory "$GITHUB_WORKSPACE/dotnet/TestResults" + -p:RunAnalyzers=false + ) 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 + case "$DOTNET_TEST_SHARD" in + 1) + # M moved here after recent runs showed shard 2 was about two + # minutes slower; this keeps Windows and macOS balanced. + initials=(A C D H I J K L M N Q S U W Y) + shard_filter="FullyQualifiedName~GitHub.Copilot.Test.ConnectionToken" + ;; + 2) + initials=(B E F G O P R T V X Z) + shard_filter="" + ;; + 2a) + initials=(B E F G) + shard_filter="" + ;; + 2b-pending) + initials=() + shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.PendingWorkResumeE2ETests" + ;; + 2b-permission) + initials=() + shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.PermissionE2ETests" + ;; + 2b-auth) + initials=() + shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.PerSessionAuthE2ETests" + ;; + 2b-hooks) + initials=() + shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.PreMcpToolCallHookE2ETests" + ;; + 2b-unit-p) + initials=() + shard_filter="FullyQualifiedName~GitHub.Copilot.Test.Unit.P" + ;; + 2b-provider) + initials=(O) + shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.ProviderEndpointE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RewindE2ETests|FullyQualifiedName~GitHub.Copilot.Test.Unit.R" + ;; + 2b-rpc-additional) + initials=() + shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcAdditionalEdgeCasesE2ETests" + ;; + 2b-rpc-agent) + initials=() + shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcAgentE2ETests" + ;; + 2b-rpc-event-log) + initials=() + shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcEventLogE2ETests" + ;; + 2b-rpc-event-effects) + initials=() + shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcEventSideEffectsE2ETests" + ;; + 2b-rpc-mcp-skills) + initials=() + shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcMcpAndSkillsE2ETests" + ;; + 2b-rpc-mcp-config) + initials=() + shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcMcpConfigE2ETests" + ;; + 2b-rpc-mcp-lifecycle) + initials=() + shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcMcpLifecycleE2ETests" + ;; + 2b-rpc-q-z) + initials=() + shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcQueueE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcRemoteE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcScheduleE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcServerE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcServerMiscE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcServerPluginsE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcServerRemoteControlE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcSessionStateE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcSessionStateExtrasE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcShellAndFleetE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcShellEdgeCaseE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcShellUserRequestedE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcTasksAndHandlersE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcUiEphemeralQueryE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcWorkspaceCheckpointsE2ETests" + ;; + extensions) + initials=() + shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcExtensionsLoadedE2ETests" + ;; + 2c) + initials=(T V X Z) + shard_filter="" + ;; + esac for namespace in E2E Unit; do for initial in "${initials[@]}"; do @@ -137,4 +339,13 @@ jobs: if [[ -n "$filter" ]]; then args+=(--filter "$filter") fi - dotnet test "${args[@]}" + dotnet test test/GitHub.Copilot.SDK.Test.csproj "${args[@]}" + + - name: Upload .NET test diagnostics + if: failure() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: dotnet-test-diagnostics-${{ matrix.os }}-${{ matrix.transport }}-${{ matrix.backend }}-${{ matrix.shard }}-${{ github.run_attempt }} + path: dotnet/TestResults/ + if-no-files-found: warn + retention-days: 7 diff --git a/.github/workflows/go-sdk-tests.yml b/.github/workflows/go-sdk-tests.yml index 61d74d257e..4fb6fd0184 100644 --- a/.github/workflows/go-sdk-tests.yml +++ b/.github/workflows/go-sdk-tests.yml @@ -1,9 +1,6 @@ name: "Go SDK Tests" on: - push: - branches: - - main workflow_dispatch: workflow_call: @@ -22,6 +19,7 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] transport: ["default", "inprocess"] runs-on: ${{ matrix.os }} + timeout-minutes: 20 defaults: run: shell: bash 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 de924a01ae..e94e0775c0 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,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"a5f19a89f89b0693f86ca89ea90e3a633fe19c17bf4d27214fa9124429cdc156","body_hash":"41bc10df4ac9179064417476fbb39fef7423d0998dc0beb7bad42e9eb7ab0494","compiler_version":"v0.83.1","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}} +# 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 # 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 b1623ff78a..dd1bfe2bbc 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 e1c11cd6da..f2f4527966 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 08005d7f7c..1d8d28c439 100644 --- a/.github/workflows/java-codegen-fix.lock.yml +++ b/.github/workflows/java-codegen-fix.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"b0390c9ab9beb0d7e106314299e89e486269ab7f64d8489d5021132d79aa6b9b","body_hash":"fe41f8fe1c12cb585cfaefdd755f58d2a84410d9d819cd706a3192ce052e2545","compiler_version":"v0.83.1","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}} +# 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 # diff --git a/.github/workflows/java-codegen-fix.md b/.github/workflows/java-codegen-fix.md index 1fe465f4c5..b1dcb1f636 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 e293d91271..fd07aae155 100644 --- a/.github/workflows/java-publish-maven.yml +++ b/.github/workflows/java-publish-maven.yml @@ -1,9 +1,6 @@ name: "Java Publish to Maven Central" env: - # Disable Husky Git hooks in CI to prevent local development hooks - # (e.g., pre-commit formatting checks) from running during automated - # workflows that perform git commits and pushes. HUSKY: 0 on: @@ -37,6 +34,10 @@ on: type: boolean required: false default: false + outputs: + mavenPublished: + description: "Whether the Java package was published to Maven Central" + value: ${{ jobs.deploy-maven.outputs.published }} secrets: JAVA_RELEASE_TOKEN: required: true @@ -52,8 +53,7 @@ on: required: true permissions: - contents: write - id-token: write + contents: read concurrency: group: publish-maven @@ -66,11 +66,6 @@ jobs: steps: - name: Verify JAVA_RELEASE_TOKEN can push to repository run: | - # JAVA_RELEASE_TOKEN is used by actions/checkout and for: - # - git push origin main (doc updates) - # - mvn release:prepare -DpushChanges=true (release commits + tags) - # - git revert + push (rollback on failure) - # It must have push (contents:write) permission on this repo. PUSH=$(gh api repos/${{ github.repository }} --jq '.permissions.push // false') if [ "$PUSH" != "true" ]; then echo "::error::JAVA_RELEASE_TOKEN lacks push permission on ${{ github.repository }}. It is required for pushing release commits and tags to main." @@ -80,49 +75,24 @@ 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 + prepare-release: + name: Prepare Java release needs: preflight runs-on: ubuntu-latest + permissions: + contents: write defaults: run: shell: bash working-directory: ./java outputs: - version: ${{ steps.versions.outputs.release_version }} + release_version: ${{ steps.versions.outputs.release_version }} + development_version: ${{ steps.versions.outputs.development_version }} + release_tag: ${{ steps.release-identity.outputs.release_tag }} + tag_commit: ${{ steps.release-identity.outputs.tag_commit }} + pre_prepare_commit: ${{ steps.pre-prepare.outputs.pre_prepare_commit }} + post_prepare_commit: ${{ steps.release-identity.outputs.post_prepare_commit }} + docs_commit: ${{ steps.update-docs.outputs.docs_commit_sha }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -136,88 +106,66 @@ jobs: - uses: ./.github/actions/setup-copilot - - name: Set up JDK 25 - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: "25" distribution: "microsoft" cache: "maven" - server-id: central - server-username: MAVEN_USERNAME - server-password: MAVEN_PASSWORD - gpg-private-key: ${{ secrets.JAVA_GPG_SECRET_KEY }} - gpg-passphrase: JAVA_GPG_PASSPHRASE - name: Determine versions id: versions - working-directory: ./java run: | CURRENT_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) echo "Current pom.xml version: $CURRENT_VERSION" - # Determine release version if [ -n "${{ inputs.releaseVersion }}" ]; then RELEASE_VERSION="${{ inputs.releaseVersion }}" else - # Remove -SNAPSHOT suffix if present RELEASE_VERSION="${CURRENT_VERSION%-SNAPSHOT}" fi echo "Release version: $RELEASE_VERSION" - # Determine next development version if [ -n "${{ inputs.developmentVersion }}" ]; then DEV_VERSION="${{ inputs.developmentVersion }}" if [[ "$DEV_VERSION" != *-SNAPSHOT ]]; then - echo "::error::developmentVersion '${DEV_VERSION}' must end with '-SNAPSHOT' (e.g., '${DEV_VERSION}-SNAPSHOT'). The maven-release-plugin requires the next development version to be a snapshot." + echo "::error::developmentVersion '${DEV_VERSION}' must end with '-SNAPSHOT'." exit 1 fi else - # Split version: supports "0.1.32", "0.1.32-preview.0", "0.1.32-java.0", and "0.1.32-java-preview.0" formats - # Validate RELEASE_VERSION format explicitly to provide clear errors if ! echo "$RELEASE_VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+(-(preview|(beta-)?java(-preview)?)\.[0-9]+)?$'; then - echo "Error: RELEASE_VERSION '$RELEASE_VERSION' is invalid. Expected format: M.M.P, M.M.P-preview.N, M.M.P-java.N, M.M.P-java-preview.N, M.M.P-beta-java.N, or M.M.P-beta-java-preview.N (e.g., 1.2.3, 1.2.3-preview.0, 1.2.3-java.0, 1.2.3-java-preview.0, 1.2.3-beta-java.0, or 1.2.3-beta-java-preview.0)." >&2 + echo "Error: RELEASE_VERSION '$RELEASE_VERSION' is invalid." >&2 exit 1 fi - # Extract the base M.M.P portion (before any qualifier) BASE_VERSION=$(echo "$RELEASE_VERSION" | grep -oE '^[0-9]+\.[0-9]+\.[0-9]+') QUALIFIER=$(echo "$RELEASE_VERSION" | sed "s|^${BASE_VERSION}||") IFS='.' read -r MAJOR MINOR PATCH <<< "$BASE_VERSION" - NEXT_PATCH=$((PATCH + 1)) - DEV_VERSION="${MAJOR}.${MINOR}.${NEXT_PATCH}${QUALIFIER}-SNAPSHOT" + DEV_VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))${QUALIFIER}-SNAPSHOT" fi - echo "Next development version: $DEV_VERSION" - - echo "release_version=$RELEASE_VERSION" >> $GITHUB_OUTPUT - echo "dev_version=$DEV_VERSION" >> $GITHUB_OUTPUT - echo "### Version Summary" >> $GITHUB_STEP_SUMMARY - echo "- **Release version:** $RELEASE_VERSION" >> $GITHUB_STEP_SUMMARY - echo "- **Next development version:** $DEV_VERSION" >> $GITHUB_STEP_SUMMARY + echo "release_version=$RELEASE_VERSION" >> "$GITHUB_OUTPUT" + echo "development_version=$DEV_VERSION" >> "$GITHUB_OUTPUT" - name: Update documentation with release version id: update-docs - working-directory: ./java run: | VERSION="${{ steps.versions.outputs.release_version }}" - DEV_VERSION="${{ steps.versions.outputs.dev_version }}" + DEV_VERSION="${{ steps.versions.outputs.development_version }}" ./scripts/test-update-documentation-versions.sh - ./scripts/update-documentation-versions.sh "$VERSION" "$DEV_VERSION" README.md jbang-example.java - - # Commit the documentation changes before release:prepare (requires clean working directory) - git add README.md jbang-example.java + ./scripts/update-documentation-versions.sh "$VERSION" "$DEV_VERSION" README.md sdk/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 - echo "docs_commit_sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - + echo "docs_commit_sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" git push origin main + - name: Record rollback base + id: pre-prepare + run: echo "pre_prepare_commit=$(git rev-parse HEAD^)" >> "$GITHUB_OUTPUT" + - name: Prepare Release - working-directory: ./java run: | mvn -B release:prepare \ -DreleaseVersion=${{ steps.versions.outputs.release_version }} \ - -DdevelopmentVersion=${{ steps.versions.outputs.dev_version }} \ + -DdevelopmentVersion=${{ steps.versions.outputs.development_version }} \ -DtagNameFormat=java/v@{project.version} \ -DpushChanges=true \ -Darguments="-DskipTests" @@ -226,33 +174,465 @@ jobs: MAVEN_PASSWORD: ${{ secrets.JAVA_MAVEN_CENTRAL_PASSWORD }} JAVA_GPG_PASSPHRASE: ${{ secrets.JAVA_GPG_PASSPHRASE }} - - name: Perform Release and Deploy to Maven Central + - name: Record immutable release identity + id: release-identity + run: | + TAG="java/v${{ steps.versions.outputs.release_version }}" + TAG_COMMIT=$(git rev-parse "${TAG}^{commit}") + POST_PREPARE_COMMIT=$(git rev-parse HEAD) + echo "release_tag=$TAG" >> "$GITHUB_OUTPUT" + echo "tag_commit=$TAG_COMMIT" >> "$GITHUB_OUTPUT" + echo "post_prepare_commit=$POST_PREPARE_COMMIT" >> "$GITHUB_OUTPUT" + + build-linux-arm64-classifier: + name: Build Linux ARM64 native classifier + needs: prepare-release + runs-on: ubuntu-24.04-arm + permissions: + contents: read + defaults: + run: + shell: bash working-directory: ./java + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ needs.prepare-release.outputs.release_tag }} + fetch-depth: 1 + 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: Build and validate linux-arm64 classifier run: | - mvn -B release:perform \ - -Dgoals="deploy" \ - -Darguments="-DskipTests -Prelease" - env: - MAVEN_USERNAME: ${{ secrets.JAVA_MAVEN_CENTRAL_USERNAME }} - MAVEN_PASSWORD: ${{ secrets.JAVA_MAVEN_CENTRAL_PASSWORD }} - JAVA_GPG_PASSPHRASE: ${{ secrets.JAVA_GPG_PASSPHRASE }} + set -euo pipefail + SOURCE_COMMIT=$(git rev-parse HEAD) + if [ "$SOURCE_COMMIT" != "${{ needs.prepare-release.outputs.tag_commit }}" ]; then + echo "::error::Checked out $SOURCE_COMMIT instead of the prepared tag commit." + exit 1 + fi + node copilot-native/scripts/validate-native-host.mjs linux-arm64 + mvn -B -pl copilot-native package -DskipTests -Dcopilot.native.libc=glibc + VERSION="${{ needs.prepare-release.outputs.release_version }}" + JAR="copilot-native/target/copilot-sdk-java-runtime-$VERSION-linux-arm64.jar" + PRIMARY_JAR="copilot-native/target/copilot-sdk-java-runtime-$VERSION.jar" + test -f "$JAR" + node copilot-native/scripts/validate-native-artifact.mjs \ + classifier linux-arm64 "$JAR" "$(basename "$JAR")" .. + node copilot-native/scripts/validate-native-artifact.mjs placeholder "$PRIMARY_JAR" + MANIFEST="copilot-native/target/linux-arm64-$VERSION.sha256" + HASH=$(sha256sum "$JAR" | cut -d ' ' -f 1) + printf '%s %s' "$HASH" "$(basename "$JAR")" > "$MANIFEST" + node copilot-native/scripts/validate-native-artifact.mjs \ + checksum "$JAR" "$MANIFEST" "$(basename "$JAR")" + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: java-native-linux-arm64-release-${{ github.run_id }}-${{ github.run_attempt }} + path: | + java/copilot-native/target/copilot-sdk-java-runtime-${{ needs.prepare-release.outputs.release_version }}-linux-arm64.jar + java/copilot-native/target/linux-arm64-${{ needs.prepare-release.outputs.release_version }}.sha256 + if-no-files-found: error + retention-days: 1 + + build-windows-classifier: + name: Build Windows native classifier + needs: prepare-release + runs-on: windows-latest + permissions: + contents: read + defaults: + run: + shell: pwsh + working-directory: ./java + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ needs.prepare-release.outputs.release_tag }} + fetch-depth: 1 + 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: Rollback documentation commit on failure - if: failure() && steps.update-docs.outputs.docs_commit_sha != '' + - name: Build and validate win32-x64 classifier + run: | + $sourceCommit = git rev-parse HEAD + if ($sourceCommit -ne '${{ needs.prepare-release.outputs.tag_commit }}') { + throw "Checked out $sourceCommit instead of the prepared tag commit." + } + node copilot-native/scripts/validate-native-host.mjs win32-x64 + mvn -B -pl copilot-native package -DskipTests + $version = '${{ needs.prepare-release.outputs.release_version }}' + $jar = "copilot-native/target/copilot-sdk-java-runtime-$version-win32-x64.jar" + $primaryJar = "copilot-native/target/copilot-sdk-java-runtime-$version.jar" + if (-not (Test-Path -LiteralPath $jar -PathType Leaf)) { + throw "Expected Windows classifier was not produced: $jar" + } + node copilot-native/scripts/validate-native-artifact.mjs classifier win32-x64 $jar ([IO.Path]::GetFileName($jar)) .. + node copilot-native/scripts/validate-native-artifact.mjs placeholder $primaryJar + $manifest = "copilot-native/target/win32-x64-$version.sha256" + $hash = (Get-FileHash -Algorithm SHA256 -LiteralPath $jar).Hash.ToLowerInvariant() + "$hash $([IO.Path]::GetFileName($jar))" | Set-Content -NoNewline -Encoding ascii $manifest + node copilot-native/scripts/validate-native-artifact.mjs checksum $jar $manifest ([IO.Path]::GetFileName($jar)) + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: java-native-win32-x64-release-${{ github.run_id }}-${{ github.run_attempt }} + path: | + java/copilot-native/target/copilot-sdk-java-runtime-${{ needs.prepare-release.outputs.release_version }}-win32-x64.jar + java/copilot-native/target/win32-x64-${{ needs.prepare-release.outputs.release_version }}.sha256 + if-no-files-found: error + retention-days: 1 + + build-windows-arm64-classifier: + name: Build Windows ARM64 native classifier + needs: prepare-release + runs-on: windows-11-arm + permissions: + contents: read + defaults: + run: + shell: pwsh working-directory: ./java + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ needs.prepare-release.outputs.release_tag }} + fetch-depth: 1 + 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: Build and validate win32-arm64 classifier run: | - echo "Release failed, rolling back documentation commit..." - git revert --no-edit ${{ steps.update-docs.outputs.docs_commit_sha }} - git push origin main + $sourceCommit = git rev-parse HEAD + if ($sourceCommit -ne '${{ needs.prepare-release.outputs.tag_commit }}') { + throw "Checked out $sourceCommit instead of the prepared tag commit." + } + node copilot-native/scripts/validate-native-host.mjs win32-arm64 + mvn -B -pl copilot-native package -DskipTests + $version = '${{ needs.prepare-release.outputs.release_version }}' + $jar = "copilot-native/target/copilot-sdk-java-runtime-$version-win32-arm64.jar" + $primaryJar = "copilot-native/target/copilot-sdk-java-runtime-$version.jar" + if (-not (Test-Path -LiteralPath $jar -PathType Leaf)) { + throw "Expected Windows ARM64 classifier was not produced: $jar" + } + node copilot-native/scripts/validate-native-artifact.mjs classifier win32-arm64 $jar ([IO.Path]::GetFileName($jar)) .. + node copilot-native/scripts/validate-native-artifact.mjs placeholder $primaryJar + $manifest = "copilot-native/target/win32-arm64-$version.sha256" + $hash = (Get-FileHash -Algorithm SHA256 -LiteralPath $jar).Hash.ToLowerInvariant() + "$hash $([IO.Path]::GetFileName($jar))" | Set-Content -NoNewline -Encoding ascii $manifest + node copilot-native/scripts/validate-native-artifact.mjs checksum $jar $manifest ([IO.Path]::GetFileName($jar)) + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: java-native-win32-arm64-release-${{ github.run_id }}-${{ github.run_attempt }} + path: | + java/copilot-native/target/copilot-sdk-java-runtime-${{ needs.prepare-release.outputs.release_version }}-win32-arm64.jar + java/copilot-native/target/win32-arm64-${{ needs.prepare-release.outputs.release_version }}.sha256 + if-no-files-found: error + retention-days: 1 + + build-darwin-classifier: + name: Build Darwin native classifier + needs: prepare-release + runs-on: macos-26 + permissions: + contents: read + defaults: + run: + shell: bash + working-directory: ./java + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ needs.prepare-release.outputs.release_tag }} + fetch-depth: 1 + persist-credentials: false + + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + java-version: "25" + distribution: "microsoft" + cache: "maven" - # Also run Maven release:rollback to clean up any partial release state - mvn -B release:rollback || true + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + with: + node-version: 22 - github-release: - name: Create GitHub Release - needs: [preflight, publish-maven] - if: github.ref == 'refs/heads/main' + - name: Build and validate darwin-arm64 classifier + run: | + set -euo pipefail + SOURCE_COMMIT=$(git rev-parse HEAD) + if [ "$SOURCE_COMMIT" != "${{ needs.prepare-release.outputs.tag_commit }}" ]; then + echo "::error::Checked out $SOURCE_COMMIT instead of the prepared tag commit." + exit 1 + fi + node copilot-native/scripts/validate-native-host.mjs darwin-arm64 + mvn -B -pl copilot-native package -DskipTests + VERSION="${{ needs.prepare-release.outputs.release_version }}" + JAR="copilot-native/target/copilot-sdk-java-runtime-$VERSION-darwin-arm64.jar" + PRIMARY_JAR="copilot-native/target/copilot-sdk-java-runtime-$VERSION.jar" + test -f "$JAR" + node copilot-native/scripts/validate-native-artifact.mjs \ + classifier darwin-arm64 "$JAR" "$(basename "$JAR")" .. + node copilot-native/scripts/validate-native-artifact.mjs placeholder "$PRIMARY_JAR" + MANIFEST="copilot-native/target/darwin-arm64-$VERSION.sha256" + HASH=$(shasum -a 256 "$JAR" | cut -d ' ' -f 1) + printf '%s %s' "$HASH" "$(basename "$JAR")" > "$MANIFEST" + node copilot-native/scripts/validate-native-artifact.mjs \ + checksum "$JAR" "$MANIFEST" "$(basename "$JAR")" + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: java-native-darwin-arm64-release-${{ github.run_id }}-${{ github.run_attempt }} + path: | + java/copilot-native/target/copilot-sdk-java-runtime-${{ needs.prepare-release.outputs.release_version }}-darwin-arm64.jar + java/copilot-native/target/darwin-arm64-${{ needs.prepare-release.outputs.release_version }}.sha256 + if-no-files-found: error + retention-days: 1 + + deploy-maven: + name: Deploy Java release to Maven Central + needs: + [ + prepare-release, + build-linux-arm64-classifier, + build-windows-classifier, + build-windows-arm64-classifier, + build-darwin-classifier, + ] runs-on: ubuntu-latest + permissions: + contents: read + defaults: + run: + shell: bash + working-directory: ./java + outputs: + version: ${{ needs.prepare-release.outputs.release_version }} + published: ${{ steps.publish-maven.outcome == 'success' }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ needs.prepare-release.outputs.release_tag }} + fetch-depth: 1 + persist-credentials: false + + - uses: ./.github/actions/setup-copilot + + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + java-version: "25" + distribution: "microsoft" + cache: "maven" + server-id: central + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + gpg-private-key: ${{ secrets.JAVA_GPG_SECRET_KEY }} + gpg-passphrase: JAVA_GPG_PASSPHRASE + + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + with: + node-version: 22 + + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: java-native-linux-arm64-release-${{ github.run_id }}-${{ github.run_attempt }} + path: ${{ runner.temp }}/java-native-linux-arm64 + + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: java-native-win32-x64-release-${{ github.run_id }}-${{ github.run_attempt }} + path: ${{ runner.temp }}/java-native-win32-x64 + + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: java-native-win32-arm64-release-${{ github.run_id }}-${{ github.run_attempt }} + path: ${{ runner.temp }}/java-native-win32-arm64 + + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: java-native-darwin-arm64-release-${{ github.run_id }}-${{ github.run_attempt }} + path: ${{ runner.temp }}/java-native-darwin-arm64 + + - name: Verify immutable source and Linux ARM64 classifier + id: linux-arm64-artifact + run: | + SOURCE_COMMIT=$(git rev-parse HEAD) + if [ "$SOURCE_COMMIT" != "${{ needs.prepare-release.outputs.tag_commit }}" ]; then + echo "::error::Checked out $SOURCE_COMMIT instead of the prepared tag commit." + exit 1 + fi + VERSION="${{ needs.prepare-release.outputs.release_version }}" + ARTIFACT_DIRECTORY="${{ runner.temp }}/java-native-linux-arm64" + JAR="$ARTIFACT_DIRECTORY/copilot-sdk-java-runtime-$VERSION-linux-arm64.jar" + MANIFEST="$ARTIFACT_DIRECTORY/linux-arm64-$VERSION.sha256" + test -f "$JAR" + test -f "$MANIFEST" + node "$GITHUB_WORKSPACE/java/copilot-native/scripts/validate-native-artifact.mjs" \ + checksum "$JAR" "$MANIFEST" "$(basename "$JAR")" + node "$GITHUB_WORKSPACE/java/copilot-native/scripts/validate-native-artifact.mjs" \ + classifier linux-arm64 "$JAR" "$(basename "$JAR")" "$GITHUB_WORKSPACE" + echo "linux_arm64_jar=$JAR" >> "$GITHUB_OUTPUT" + echo "linux_arm64_sha=$(cut -d ' ' -f 1 "$MANIFEST")" >> "$GITHUB_OUTPUT" + + - name: Verify immutable source and Windows classifier + id: windows-artifact + run: | + SOURCE_COMMIT=$(git rev-parse HEAD) + if [ "$SOURCE_COMMIT" != "${{ needs.prepare-release.outputs.tag_commit }}" ]; then + echo "::error::Checked out $SOURCE_COMMIT instead of the prepared tag commit." + exit 1 + fi + VERSION="${{ needs.prepare-release.outputs.release_version }}" + ARTIFACT_DIRECTORY="${{ runner.temp }}/java-native-win32-x64" + JAR="$ARTIFACT_DIRECTORY/copilot-sdk-java-runtime-$VERSION-win32-x64.jar" + MANIFEST="$ARTIFACT_DIRECTORY/win32-x64-$VERSION.sha256" + test -f "$JAR" + test -f "$MANIFEST" + node "$GITHUB_WORKSPACE/java/copilot-native/scripts/validate-native-artifact.mjs" \ + checksum "$JAR" "$MANIFEST" "$(basename "$JAR")" + node "$GITHUB_WORKSPACE/java/copilot-native/scripts/validate-native-artifact.mjs" \ + classifier win32-x64 "$JAR" "$(basename "$JAR")" "$GITHUB_WORKSPACE" + echo "windows_jar=$JAR" >> "$GITHUB_OUTPUT" + echo "windows_sha=$(cut -d ' ' -f 1 "$MANIFEST")" >> "$GITHUB_OUTPUT" + + - name: Verify immutable source and Darwin classifier + id: darwin-artifact + run: | + SOURCE_COMMIT=$(git rev-parse HEAD) + if [ "$SOURCE_COMMIT" != "${{ needs.prepare-release.outputs.tag_commit }}" ]; then + echo "::error::Checked out $SOURCE_COMMIT instead of the prepared tag commit." + exit 1 + fi + VERSION="${{ needs.prepare-release.outputs.release_version }}" + ARTIFACT_DIRECTORY="${{ runner.temp }}/java-native-darwin-arm64" + JAR="$ARTIFACT_DIRECTORY/copilot-sdk-java-runtime-$VERSION-darwin-arm64.jar" + MANIFEST="$ARTIFACT_DIRECTORY/darwin-arm64-$VERSION.sha256" + test -f "$JAR" + test -f "$MANIFEST" + node "$GITHUB_WORKSPACE/java/copilot-native/scripts/validate-native-artifact.mjs" \ + checksum "$JAR" "$MANIFEST" "$(basename "$JAR")" + node "$GITHUB_WORKSPACE/java/copilot-native/scripts/validate-native-artifact.mjs" \ + classifier darwin-arm64 "$JAR" "$(basename "$JAR")" "$GITHUB_WORKSPACE" + echo "darwin_jar=$JAR" >> "$GITHUB_OUTPUT" + echo "darwin_sha=$(cut -d ' ' -f 1 "$MANIFEST")" >> "$GITHUB_OUTPUT" + + - name: Verify immutable source and Windows ARM64 classifier + id: windows-arm64-artifact + run: | + SOURCE_COMMIT=$(git rev-parse HEAD) + if [ "$SOURCE_COMMIT" != "${{ needs.prepare-release.outputs.tag_commit }}" ]; then + echo "::error::Checked out $SOURCE_COMMIT instead of the prepared tag commit." + exit 1 + fi + VERSION="${{ needs.prepare-release.outputs.release_version }}" + ARTIFACT_DIRECTORY="${{ runner.temp }}/java-native-win32-arm64" + JAR="$ARTIFACT_DIRECTORY/copilot-sdk-java-runtime-$VERSION-win32-arm64.jar" + MANIFEST="$ARTIFACT_DIRECTORY/win32-arm64-$VERSION.sha256" + test -f "$JAR" + test -f "$MANIFEST" + node "$GITHUB_WORKSPACE/java/copilot-native/scripts/validate-native-artifact.mjs" \ + checksum "$JAR" "$MANIFEST" "$(basename "$JAR")" + node "$GITHUB_WORKSPACE/java/copilot-native/scripts/validate-native-artifact.mjs" \ + classifier win32-arm64 "$JAR" "$(basename "$JAR")" "$GITHUB_WORKSPACE" + echo "windows_arm64_jar=$JAR" >> "$GITHUB_OUTPUT" + echo "windows_arm64_sha=$(cut -d ' ' -f 1 "$MANIFEST")" >> "$GITHUB_OUTPUT" + + - name: Build Linux classifier and deploy complete release + id: publish-maven + run: | + VERSION="${{ needs.prepare-release.outputs.release_version }}" + mvn -B deploy -DskipTests -Prelease -Dcopilot.native.libc=glibc \ + "-Dcopilot.native.external.linux.arm64.classifier.path=${{ steps.linux-arm64-artifact.outputs.linux_arm64_jar }}" \ + "-Dcopilot.native.external.win32.classifier.path=${{ steps.windows-artifact.outputs.windows_jar }}" \ + "-Dcopilot.native.external.win32.arm64.classifier.path=${{ steps.windows-arm64-artifact.outputs.windows_arm64_jar }}" \ + "-Dcopilot.native.external.darwin.classifier.path=${{ steps.darwin-artifact.outputs.darwin_jar }}" + LINUX_JAR="copilot-native/target/copilot-sdk-java-runtime-$VERSION-linux-x64.jar" + test -f "$LINUX_JAR" + node copilot-native/scripts/validate-native-artifact.mjs \ + classifier linux-x64 "$LINUX_JAR" "$(basename "$LINUX_JAR")" .. + LINUX_SHA=$(sha256sum "$LINUX_JAR" | cut -d ' ' -f 1) + GROUP_ID=$(mvn -q -pl copilot-native help:evaluate -Dexpression=project.groupId -DforceStdout) + ARTIFACT_ID=$(mvn -q -pl copilot-native help:evaluate -Dexpression=project.artifactId -DforceStdout) + POM_VERSION=$(mvn -q -pl copilot-native help:evaluate -Dexpression=project.version -DforceStdout) + if [ -z "$GROUP_ID" ] || [ -z "$ARTIFACT_ID" ] || [ "$POM_VERSION" != "$VERSION" ]; then + echo "::error::Unexpected copilot-native Maven coordinates: $GROUP_ID:$ARTIFACT_ID:$POM_VERSION (expected version $VERSION)" + exit 1 + fi + { + echo "### Maven Central Release" + echo "- **Version:** $VERSION" + echo "- **Source tag:** \`${{ needs.prepare-release.outputs.release_tag }}\`" + echo "- **Source commit:** \`${{ needs.prepare-release.outputs.tag_commit }}\`" + echo "- **Next development version:** ${{ needs.prepare-release.outputs.development_version }}" + echo "- **Repository:** Maven Central" + echo "" + echo "#### Maven Coordinates" + echo "" + echo '```xml' + echo "" + echo " $GROUP_ID" + echo " $ARTIFACT_ID" + echo " $POM_VERSION" + echo "" + echo '```' + echo "" + echo "#### Published Native Classifiers" + echo "" + echo "| Classifier | Build runner | Artifact | SHA-256 | Status |" + echo "| --- | --- | --- | --- | --- |" + echo "| \`linux-x64\` | \`ubuntu-latest\` | \`$(basename "$LINUX_JAR")\` | \`$LINUX_SHA\` | Published |" + echo "| \`linux-arm64\` | \`ubuntu-24.04-arm\` | \`$(basename "${{ steps.linux-arm64-artifact.outputs.linux_arm64_jar }}")\` | \`${{ steps.linux-arm64-artifact.outputs.linux_arm64_sha }}\` | Published |" + echo "| \`win32-x64\` | \`windows-latest\` | \`$(basename "${{ steps.windows-artifact.outputs.windows_jar }}")\` | \`${{ steps.windows-artifact.outputs.windows_sha }}\` | Published |" + echo "| \`win32-arm64\` | \`windows-11-arm\` | \`$(basename "${{ steps.windows-arm64-artifact.outputs.windows_arm64_jar }}")\` | \`${{ steps.windows-arm64-artifact.outputs.windows_arm64_sha }}\` | Published |" + echo "| \`darwin-arm64\` | \`macos-26\` | \`$(basename "${{ steps.darwin-artifact.outputs.darwin_jar }}")\` | \`${{ steps.darwin-artifact.outputs.darwin_sha }}\` | Published |" + } >> "$GITHUB_STEP_SUMMARY" + env: + MAVEN_USERNAME: ${{ secrets.JAVA_MAVEN_CENTRAL_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.JAVA_MAVEN_CENTRAL_PASSWORD }} + JAVA_GPG_PASSPHRASE: ${{ secrets.JAVA_GPG_PASSPHRASE }} + + rollback-release: + name: Roll back failed Java release preparation + needs: + [ + prepare-release, + build-linux-arm64-classifier, + build-windows-classifier, + build-windows-arm64-classifier, + build-darwin-classifier, + deploy-maven, + ] + if: ${{ failure() && needs.prepare-release.outputs.docs_commit != '' }} + runs-on: ubuntu-latest + permissions: + contents: write defaults: run: shell: bash @@ -261,61 +641,127 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - name: Create GitHub Release + token: ${{ secrets.JAVA_RELEASE_TOKEN }} + + - name: Safely revert release commits and tag + env: + DOCS_COMMIT: ${{ needs.prepare-release.outputs.docs_commit }} + PREPARE_RESULT: ${{ needs.prepare-release.result }} + PRE_PREPARE_COMMIT: ${{ needs.prepare-release.outputs.pre_prepare_commit }} + POST_PREPARE_COMMIT: ${{ needs.prepare-release.outputs.post_prepare_commit }} + RELEASE_TAG: java/v${{ needs.prepare-release.outputs.release_version }} + TAG_COMMIT: ${{ needs.prepare-release.outputs.tag_commit }} 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 + set -euo pipefail + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git fetch origin main --tags + MAIN_COMMIT=$(git rev-parse origin/main) + echo "Rollback inspection: main=$MAIN_COMMIT docs=$DOCS_COMMIT pre=$PRE_PREPARE_COMMIT post=$POST_PREPARE_COMMIT tag=$RELEASE_TAG" + + unsafe_rollback() { + echo "::error::Unsafe rollback state: $*" >&2 + exit 1 + } + + if ! git cat-file -e "${PRE_PREPARE_COMMIT}^{commit}"; then + unsafe_rollback "Recorded pre-prepare commit does not exist: $PRE_PREPARE_COMMIT" + fi + if ! git cat-file -e "${DOCS_COMMIT}^{commit}"; then + unsafe_rollback "Recorded documentation commit does not exist: $DOCS_COMMIT" + fi + if ! git merge-base --is-ancestor "$PRE_PREPARE_COMMIT" "$MAIN_COMMIT"; then + unsafe_rollback "Recorded pre-prepare commit is not an ancestor of main." + fi + if [ "$(git rev-parse "${DOCS_COMMIT}^")" != "$PRE_PREPARE_COMMIT" ]; then + unsafe_rollback "Documentation commit is not immediately based on the recorded pre-prepare commit." fi - # Generate release notes from template - export VERSION GROUP_ID ARTIFACT_ID - RELEASE_NOTES=$(envsubst < $GITHUB_WORKSPACE/.github/workflows/java.notes.template) + mapfile -t ROLLBACK_COMMITS < <(git rev-list --reverse "$PRE_PREPARE_COMMIT..$MAIN_COMMIT") + FIRST_PARENT_COUNT=$(git rev-list --count --first-parent "$PRE_PREPARE_COMMIT..$MAIN_COMMIT") + if [ "${#ROLLBACK_COMMITS[@]}" -ne "$FIRST_PARENT_COUNT" ]; then + unsafe_rollback "Release range contains merged history." + fi + if [ "${#ROLLBACK_COMMITS[@]}" -lt 1 ] || [ "${#ROLLBACK_COMMITS[@]}" -gt 3 ]; then + unsafe_rollback "Expected one to three release-preparation commits after the recorded base; found ${#ROLLBACK_COMMITS[@]}." + fi + if [ "${ROLLBACK_COMMITS[0]}" != "$DOCS_COMMIT" ]; then + unsafe_rollback "The first commit after the recorded base is not the recorded documentation commit." + fi - # 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) + EXPECTED_PARENT="$PRE_PREPARE_COMMIT" + for COMMIT in "${ROLLBACK_COMMITS[@]}"; do + if git rev-parse -q --verify "${COMMIT}^2" >/dev/null; then + unsafe_rollback "Release range contains merge commit $COMMIT." + fi + if [ "$(git rev-parse "${COMMIT}^")" != "$EXPECTED_PARENT" ]; then + unsafe_rollback "Release range is not a linear continuation of the recorded base." + fi + EXPECTED_PARENT="$COMMIT" + done - echo "Current tag: ${CURRENT_TAG}" - echo "Previous tag: ${PREV_TAG}" + RELEASE_VERSION="${RELEASE_TAG#java/v}" + if [ "$(git log -1 --format=%s "$DOCS_COMMIT")" != "docs: update version references to $RELEASE_VERSION" ]; then + unsafe_rollback "Recorded documentation commit has an unexpected subject." + fi - # 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") + RELEASE_PREPARE_COMMIT="" + if [ "${#ROLLBACK_COMMITS[@]}" -ge 2 ]; then + RELEASE_PREPARE_COMMIT="${ROLLBACK_COMMITS[1]}" + if [ "$(git log -1 --format=%s "$RELEASE_PREPARE_COMMIT")" != "[maven-release-plugin] prepare release $RELEASE_TAG" ]; then + unsafe_rollback "Release-version commit has an unexpected subject." + fi + fi + if [ "${#ROLLBACK_COMMITS[@]}" -eq 3 ] && [ "$(git log -1 --format=%s "${ROLLBACK_COMMITS[2]}")" != "[maven-release-plugin] prepare for next development iteration" ]; then + unsafe_rollback "Development-version commit has an unexpected subject." + fi - if [ -n "$PREV_TAG" ]; then - GH_ARGS+=("--notes-start-tag" "$PREV_TAG") + TAG_OBJECT=$(git ls-remote --refs origin "refs/tags/$RELEASE_TAG" | awk '{print $1}') + if [ -n "$TAG_OBJECT" ]; then + git fetch --no-tags origin "+refs/tags/$RELEASE_TAG:refs/tags/$RELEASE_TAG" + REMOTE_TAG_COMMIT=$(git rev-parse "${RELEASE_TAG}^{commit}") + if [ -z "$RELEASE_PREPARE_COMMIT" ] || [ "$REMOTE_TAG_COMMIT" != "$RELEASE_PREPARE_COMMIT" ]; then + unsafe_rollback "Release tag does not point to the guarded release-version commit." + fi + elif [ -n "$TAG_COMMIT" ]; then + unsafe_rollback "Recorded release tag is absent from the remote." fi - ${{ inputs.prerelease == true && 'GH_ARGS+=("--prerelease")' || '' }} + if [ "$PREPARE_RESULT" = "success" ]; then + if [ -z "$POST_PREPARE_COMMIT" ] || [ -z "$TAG_COMMIT" ]; then + unsafe_rollback "Successful preparation did not record its immutable release identity." + fi + if [ "$MAIN_COMMIT" != "$POST_PREPARE_COMMIT" ]; then + unsafe_rollback "main has advanced beyond the recorded post-prepare commit." + fi + if [ -z "$TAG_OBJECT" ] || [ "$REMOTE_TAG_COMMIT" != "$TAG_COMMIT" ]; then + unsafe_rollback "Remote release tag does not match the recorded tag commit." + fi + fi - 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 }} + git checkout -B release-rollback "$MAIN_COMMIT" + git revert --no-edit "$PRE_PREPARE_COMMIT..$MAIN_COMMIT" + if [ -n "$TAG_OBJECT" ]; then + git push --atomic \ + "--force-with-lease=refs/heads/main:$MAIN_COMMIT" \ + "--force-with-lease=refs/tags/$RELEASE_TAG:$TAG_OBJECT" \ + origin HEAD:refs/heads/main ":refs/tags/$RELEASE_TAG" + else + git push \ + "--force-with-lease=refs/heads/main:$MAIN_COMMIT" \ + origin HEAD:refs/heads/main + fi + echo "Release preparation rollback completed after guarded history inspection." deploy-site: name: Deploy Documentation Site - needs: [preflight, publish-maven, github-release] - if: github.ref == 'refs/heads/main' + needs: [preflight, deploy-maven] + if: github.ref == 'refs/heads/main' && needs.deploy-maven.outputs.published == 'true' runs-on: ubuntu-latest steps: - name: Trigger site deployment on standalone repo run: | - VERSION="${{ needs.publish-maven.outputs.version }}" + VERSION="${{ needs.deploy-maven.outputs.version }}" TAG="java/v${VERSION}" PUBLISH_AS_LATEST=true if [ "${{ inputs.prerelease }}" = "true" ]; then @@ -327,7 +773,9 @@ jobs: -f version="${VERSION}" \ -f publish_as_latest="${PUBLISH_AS_LATEST}" \ -f monorepo_tag="${TAG}" - echo "### Site Deployment" >> $GITHUB_STEP_SUMMARY - echo "Triggered deploy-site.yml on github/copilot-sdk-java for version ${VERSION}" >> $GITHUB_STEP_SUMMARY + { + echo "### Site Deployment" + echo "Triggered deploy-site.yml on github/copilot-sdk-java for version ${VERSION}" + } >> "$GITHUB_STEP_SUMMARY" env: GITHUB_TOKEN: ${{ secrets.JAVA_RELEASE_GITHUB_TOKEN }} diff --git a/.github/workflows/java-publish-snapshot.yml b/.github/workflows/java-publish-snapshot.yml index 8c957627ff..c0a894dca0 100644 --- a/.github/workflows/java-publish-snapshot.yml +++ b/.github/workflows/java-publish-snapshot.yml @@ -16,8 +16,282 @@ concurrency: cancel-in-progress: false jobs: - publish-snapshot: + resolve-source: + name: Resolve immutable snapshot source + runs-on: ubuntu-latest + outputs: + source_sha: ${{ steps.source.outputs.sha }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.sha }} + fetch-depth: 1 + persist-credentials: false + + - id: source + shell: bash + run: | + SHA=$(git rev-parse HEAD) + echo "sha=$SHA" >> "$GITHUB_OUTPUT" + + build-linux-arm64-classifier: + name: Build Linux ARM64 snapshot classifier + needs: resolve-source + runs-on: ubuntu-24.04-arm + permissions: + contents: read + outputs: + version: ${{ steps.build.outputs.version }} + defaults: + run: + shell: bash + working-directory: ./java + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.sha }} + fetch-depth: 1 + 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: Build and validate linux-arm64 classifier + id: build + run: | + set -euo pipefail + SOURCE_COMMIT=$(git rev-parse HEAD) + if [ "$SOURCE_COMMIT" != "${{ needs.resolve-source.outputs.source_sha }}" ]; then + echo "::error::Checked out $SOURCE_COMMIT instead of the resolved snapshot source." + exit 1 + fi + node copilot-native/scripts/validate-native-host.mjs linux-arm64 + mvn -B -pl copilot-native package -DskipTests -Dcopilot.native.libc=glibc + VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) + JAR="copilot-native/target/copilot-sdk-java-runtime-$VERSION-linux-arm64.jar" + PRIMARY_JAR="copilot-native/target/copilot-sdk-java-runtime-$VERSION.jar" + test -f "$JAR" + node copilot-native/scripts/validate-native-artifact.mjs \ + classifier linux-arm64 "$JAR" "$(basename "$JAR")" .. + node copilot-native/scripts/validate-native-artifact.mjs placeholder "$PRIMARY_JAR" + MANIFEST="copilot-native/target/linux-arm64-$VERSION.sha256" + HASH=$(sha256sum "$JAR" | cut -d ' ' -f 1) + printf '%s %s' "$HASH" "$(basename "$JAR")" > "$MANIFEST" + node copilot-native/scripts/validate-native-artifact.mjs \ + checksum "$JAR" "$MANIFEST" "$(basename "$JAR")" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: java-native-linux-arm64-snapshot-${{ github.run_id }}-${{ github.run_attempt }} + path: | + java/copilot-native/target/copilot-sdk-java-runtime-${{ steps.build.outputs.version }}-linux-arm64.jar + java/copilot-native/target/linux-arm64-${{ steps.build.outputs.version }}.sha256 + if-no-files-found: error + retention-days: 1 + + build-windows-classifier: + name: Build Windows snapshot classifier + needs: resolve-source + runs-on: windows-latest + permissions: + contents: read + outputs: + version: ${{ steps.build.outputs.version }} + defaults: + run: + shell: pwsh + working-directory: ./java + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.sha }} + fetch-depth: 1 + 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: Build and validate win32-x64 classifier + id: build + run: | + $sourceCommit = git rev-parse HEAD + if ($sourceCommit -ne '${{ needs.resolve-source.outputs.source_sha }}') { + throw "Checked out $sourceCommit instead of the resolved snapshot source." + } + node copilot-native/scripts/validate-native-host.mjs win32-x64 + mvn -B -pl copilot-native package -DskipTests + $version = mvn help:evaluate "-Dexpression=project.version" -q "-DforceStdout" + $jar = "copilot-native/target/copilot-sdk-java-runtime-$version-win32-x64.jar" + $primaryJar = "copilot-native/target/copilot-sdk-java-runtime-$version.jar" + if (-not (Test-Path -LiteralPath $jar -PathType Leaf)) { + throw "Expected Windows classifier was not produced: $jar" + } + node copilot-native/scripts/validate-native-artifact.mjs classifier win32-x64 $jar ([IO.Path]::GetFileName($jar)) .. + node copilot-native/scripts/validate-native-artifact.mjs placeholder $primaryJar + $manifest = "copilot-native/target/win32-x64-$version.sha256" + $hash = (Get-FileHash -Algorithm SHA256 -LiteralPath $jar).Hash.ToLowerInvariant() + "$hash $([IO.Path]::GetFileName($jar))" | Set-Content -NoNewline -Encoding ascii $manifest + node copilot-native/scripts/validate-native-artifact.mjs checksum $jar $manifest ([IO.Path]::GetFileName($jar)) + "version=$version" | Add-Content $env:GITHUB_OUTPUT + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: java-native-win32-x64-snapshot-${{ github.run_id }}-${{ github.run_attempt }} + path: | + java/copilot-native/target/copilot-sdk-java-runtime-${{ steps.build.outputs.version }}-win32-x64.jar + java/copilot-native/target/win32-x64-${{ steps.build.outputs.version }}.sha256 + if-no-files-found: error + retention-days: 1 + + build-windows-arm64-classifier: + name: Build Windows ARM64 snapshot classifier + needs: resolve-source + runs-on: windows-11-arm + permissions: + contents: read + outputs: + version: ${{ steps.build.outputs.version }} + defaults: + run: + shell: pwsh + working-directory: ./java + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.sha }} + fetch-depth: 1 + 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: Build and validate win32-arm64 classifier + id: build + run: | + $sourceCommit = git rev-parse HEAD + if ($sourceCommit -ne '${{ needs.resolve-source.outputs.source_sha }}') { + throw "Checked out $sourceCommit instead of the resolved snapshot source." + } + node copilot-native/scripts/validate-native-host.mjs win32-arm64 + mvn -B -pl copilot-native package -DskipTests + $version = mvn help:evaluate "-Dexpression=project.version" -q "-DforceStdout" + $jar = "copilot-native/target/copilot-sdk-java-runtime-$version-win32-arm64.jar" + $primaryJar = "copilot-native/target/copilot-sdk-java-runtime-$version.jar" + if (-not (Test-Path -LiteralPath $jar -PathType Leaf)) { + throw "Expected Windows ARM64 classifier was not produced: $jar" + } + node copilot-native/scripts/validate-native-artifact.mjs classifier win32-arm64 $jar ([IO.Path]::GetFileName($jar)) .. + node copilot-native/scripts/validate-native-artifact.mjs placeholder $primaryJar + $manifest = "copilot-native/target/win32-arm64-$version.sha256" + $hash = (Get-FileHash -Algorithm SHA256 -LiteralPath $jar).Hash.ToLowerInvariant() + "$hash $([IO.Path]::GetFileName($jar))" | Set-Content -NoNewline -Encoding ascii $manifest + node copilot-native/scripts/validate-native-artifact.mjs checksum $jar $manifest ([IO.Path]::GetFileName($jar)) + "version=$version" | Add-Content $env:GITHUB_OUTPUT + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: java-native-win32-arm64-snapshot-${{ github.run_id }}-${{ github.run_attempt }} + path: | + java/copilot-native/target/copilot-sdk-java-runtime-${{ steps.build.outputs.version }}-win32-arm64.jar + java/copilot-native/target/win32-arm64-${{ steps.build.outputs.version }}.sha256 + if-no-files-found: error + retention-days: 1 + + build-darwin-classifier: + name: Build Darwin snapshot classifier + needs: resolve-source + runs-on: macos-26 + permissions: + contents: read + outputs: + version: ${{ steps.build.outputs.version }} + defaults: + run: + shell: bash + working-directory: ./java + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.sha }} + fetch-depth: 1 + 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: Build and validate darwin-arm64 classifier + id: build + run: | + set -euo pipefail + SOURCE_COMMIT=$(git rev-parse HEAD) + if [ "$SOURCE_COMMIT" != "${{ needs.resolve-source.outputs.source_sha }}" ]; then + echo "::error::Checked out $SOURCE_COMMIT instead of the resolved snapshot source." + exit 1 + fi + node copilot-native/scripts/validate-native-host.mjs darwin-arm64 + mvn -B -pl copilot-native package -DskipTests + VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) + JAR="copilot-native/target/copilot-sdk-java-runtime-$VERSION-darwin-arm64.jar" + PRIMARY_JAR="copilot-native/target/copilot-sdk-java-runtime-$VERSION.jar" + test -f "$JAR" + node copilot-native/scripts/validate-native-artifact.mjs \ + classifier darwin-arm64 "$JAR" "$(basename "$JAR")" .. + node copilot-native/scripts/validate-native-artifact.mjs placeholder "$PRIMARY_JAR" + MANIFEST="copilot-native/target/darwin-arm64-$VERSION.sha256" + HASH=$(shasum -a 256 "$JAR" | cut -d ' ' -f 1) + printf '%s %s' "$HASH" "$(basename "$JAR")" > "$MANIFEST" + node copilot-native/scripts/validate-native-artifact.mjs \ + checksum "$JAR" "$MANIFEST" "$(basename "$JAR")" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: java-native-darwin-arm64-snapshot-${{ github.run_id }}-${{ github.run_attempt }} + path: | + java/copilot-native/target/copilot-sdk-java-runtime-${{ steps.build.outputs.version }}-darwin-arm64.jar + java/copilot-native/target/darwin-arm64-${{ steps.build.outputs.version }}.sha256 + if-no-files-found: error + retention-days: 1 + + deploy-snapshot: name: Publish SNAPSHOT to Maven Central + needs: + [ + resolve-source, + build-linux-arm64-classifier, + build-windows-classifier, + build-windows-arm64-classifier, + build-darwin-classifier, + ] runs-on: ubuntu-latest defaults: run: @@ -26,12 +300,13 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - fetch-depth: 0 + ref: ${{ github.sha }} + fetch-depth: 1 + persist-credentials: false - uses: ./.github/actions/setup-copilot - - name: Set up JDK 25 - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: "25" distribution: "microsoft" @@ -40,22 +315,182 @@ jobs: server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD - - name: Verify version is a SNAPSHOT - working-directory: ./java + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + with: + node-version: 22 + + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: java-native-linux-arm64-snapshot-${{ github.run_id }}-${{ github.run_attempt }} + path: ${{ runner.temp }}/java-native-linux-arm64 + + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: java-native-win32-x64-snapshot-${{ github.run_id }}-${{ github.run_attempt }} + path: ${{ runner.temp }}/java-native-win32-x64 + + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: java-native-win32-arm64-snapshot-${{ github.run_id }}-${{ github.run_attempt }} + path: ${{ runner.temp }}/java-native-win32-arm64 + + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: java-native-darwin-arm64-snapshot-${{ github.run_id }}-${{ github.run_attempt }} + path: ${{ runner.temp }}/java-native-darwin-arm64 + + - name: Verify version, source, and Linux ARM64 classifier + id: linux-arm64-artifact run: | + SOURCE_COMMIT=$(git rev-parse HEAD) + if [ "$SOURCE_COMMIT" != "${{ needs.resolve-source.outputs.source_sha }}" ]; then + echo "::error::Checked out $SOURCE_COMMIT instead of the resolved snapshot source." + exit 1 + fi VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) - echo "Publishing version: $VERSION" if [[ "$VERSION" != *"-SNAPSHOT" ]]; then - echo "ERROR: This workflow only publishes SNAPSHOT versions. Current version: $VERSION" + echo "::error::This workflow only publishes SNAPSHOT versions. Current version: $VERSION" + exit 1 + fi + if [ "$VERSION" != "${{ needs.build-linux-arm64-classifier.outputs.version }}" ]; then + echo "::error::Linux ARM64 classifier version does not match deploy version." exit 1 fi - echo "### Snapshot Publish" >> $GITHUB_STEP_SUMMARY - echo "- **Version:** $VERSION" >> $GITHUB_STEP_SUMMARY - echo "- **Repository:** Maven Central Snapshots" >> $GITHUB_STEP_SUMMARY + ARTIFACT_DIRECTORY="${{ runner.temp }}/java-native-linux-arm64" + JAR="$ARTIFACT_DIRECTORY/copilot-sdk-java-runtime-$VERSION-linux-arm64.jar" + MANIFEST="$ARTIFACT_DIRECTORY/linux-arm64-$VERSION.sha256" + test -f "$JAR" + test -f "$MANIFEST" + node "$GITHUB_WORKSPACE/java/copilot-native/scripts/validate-native-artifact.mjs" \ + checksum "$JAR" "$MANIFEST" "$(basename "$JAR")" + node "$GITHUB_WORKSPACE/java/copilot-native/scripts/validate-native-artifact.mjs" \ + classifier linux-arm64 "$JAR" "$(basename "$JAR")" "$GITHUB_WORKSPACE" + echo "linux_arm64_jar=$JAR" >> "$GITHUB_OUTPUT" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "linux_arm64_sha=$(cut -d ' ' -f 1 "$MANIFEST")" >> "$GITHUB_OUTPUT" - - name: Deploy Snapshot - working-directory: ./java - run: mvn -B deploy -DskipTests + - name: Verify version, source, and Windows classifier + id: windows-artifact + run: | + SOURCE_COMMIT=$(git rev-parse HEAD) + if [ "$SOURCE_COMMIT" != "${{ needs.resolve-source.outputs.source_sha }}" ]; then + echo "::error::Checked out $SOURCE_COMMIT instead of the resolved snapshot source." + exit 1 + fi + VERSION="${{ steps.linux-arm64-artifact.outputs.version }}" + if [ "$VERSION" != "${{ needs.build-windows-classifier.outputs.version }}" ]; then + echo "::error::Windows classifier version does not match deploy version." + exit 1 + fi + ARTIFACT_DIRECTORY="${{ runner.temp }}/java-native-win32-x64" + JAR="$ARTIFACT_DIRECTORY/copilot-sdk-java-runtime-$VERSION-win32-x64.jar" + MANIFEST="$ARTIFACT_DIRECTORY/win32-x64-$VERSION.sha256" + test -f "$JAR" + test -f "$MANIFEST" + node "$GITHUB_WORKSPACE/java/copilot-native/scripts/validate-native-artifact.mjs" \ + checksum "$JAR" "$MANIFEST" "$(basename "$JAR")" + node "$GITHUB_WORKSPACE/java/copilot-native/scripts/validate-native-artifact.mjs" \ + classifier win32-x64 "$JAR" "$(basename "$JAR")" "$GITHUB_WORKSPACE" + echo "windows_jar=$JAR" >> "$GITHUB_OUTPUT" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "windows_sha=$(cut -d ' ' -f 1 "$MANIFEST")" >> "$GITHUB_OUTPUT" + + - name: Verify version, source, and Darwin classifier + id: darwin-artifact + run: | + SOURCE_COMMIT=$(git rev-parse HEAD) + if [ "$SOURCE_COMMIT" != "${{ needs.resolve-source.outputs.source_sha }}" ]; then + echo "::error::Checked out $SOURCE_COMMIT instead of the resolved snapshot source." + exit 1 + fi + VERSION="${{ steps.windows-artifact.outputs.version }}" + if [ "$VERSION" != "${{ needs.build-darwin-classifier.outputs.version }}" ]; then + echo "::error::Darwin classifier version does not match deploy version." + exit 1 + fi + ARTIFACT_DIRECTORY="${{ runner.temp }}/java-native-darwin-arm64" + JAR="$ARTIFACT_DIRECTORY/copilot-sdk-java-runtime-$VERSION-darwin-arm64.jar" + MANIFEST="$ARTIFACT_DIRECTORY/darwin-arm64-$VERSION.sha256" + test -f "$JAR" + test -f "$MANIFEST" + node "$GITHUB_WORKSPACE/java/copilot-native/scripts/validate-native-artifact.mjs" \ + checksum "$JAR" "$MANIFEST" "$(basename "$JAR")" + node "$GITHUB_WORKSPACE/java/copilot-native/scripts/validate-native-artifact.mjs" \ + classifier darwin-arm64 "$JAR" "$(basename "$JAR")" "$GITHUB_WORKSPACE" + echo "darwin_jar=$JAR" >> "$GITHUB_OUTPUT" + echo "darwin_sha=$(cut -d ' ' -f 1 "$MANIFEST")" >> "$GITHUB_OUTPUT" + + - name: Verify version, source, and Windows ARM64 classifier + id: windows-arm64-artifact + run: | + SOURCE_COMMIT=$(git rev-parse HEAD) + if [ "$SOURCE_COMMIT" != "${{ needs.resolve-source.outputs.source_sha }}" ]; then + echo "::error::Checked out $SOURCE_COMMIT instead of the resolved snapshot source." + exit 1 + fi + VERSION="${{ steps.windows-artifact.outputs.version }}" + if [ "$VERSION" != "${{ needs.build-windows-arm64-classifier.outputs.version }}" ]; then + echo "::error::Windows ARM64 classifier version does not match deploy version." + exit 1 + fi + ARTIFACT_DIRECTORY="${{ runner.temp }}/java-native-win32-arm64" + JAR="$ARTIFACT_DIRECTORY/copilot-sdk-java-runtime-$VERSION-win32-arm64.jar" + MANIFEST="$ARTIFACT_DIRECTORY/win32-arm64-$VERSION.sha256" + test -f "$JAR" + test -f "$MANIFEST" + node "$GITHUB_WORKSPACE/java/copilot-native/scripts/validate-native-artifact.mjs" \ + checksum "$JAR" "$MANIFEST" "$(basename "$JAR")" + node "$GITHUB_WORKSPACE/java/copilot-native/scripts/validate-native-artifact.mjs" \ + classifier win32-arm64 "$JAR" "$(basename "$JAR")" "$GITHUB_WORKSPACE" + echo "windows_arm64_jar=$JAR" >> "$GITHUB_OUTPUT" + echo "windows_arm64_sha=$(cut -d ' ' -f 1 "$MANIFEST")" >> "$GITHUB_OUTPUT" + + - name: Build Linux classifier and deploy complete snapshot + run: | + VERSION="${{ steps.windows-artifact.outputs.version }}" + mvn -B deploy -DskipTests -Dcopilot.native.libc=glibc \ + "-Dcopilot.native.external.linux.arm64.classifier.path=${{ steps.linux-arm64-artifact.outputs.linux_arm64_jar }}" \ + "-Dcopilot.native.external.win32.classifier.path=${{ steps.windows-artifact.outputs.windows_jar }}" \ + "-Dcopilot.native.external.win32.arm64.classifier.path=${{ steps.windows-arm64-artifact.outputs.windows_arm64_jar }}" \ + "-Dcopilot.native.external.darwin.classifier.path=${{ steps.darwin-artifact.outputs.darwin_jar }}" + LINUX_JAR="copilot-native/target/copilot-sdk-java-runtime-$VERSION-linux-x64.jar" + test -f "$LINUX_JAR" + node copilot-native/scripts/validate-native-artifact.mjs \ + classifier linux-x64 "$LINUX_JAR" "$(basename "$LINUX_JAR")" .. + LINUX_SHA=$(sha256sum "$LINUX_JAR" | cut -d ' ' -f 1) + GROUP_ID=$(mvn -q -pl copilot-native help:evaluate -Dexpression=project.groupId -DforceStdout) + ARTIFACT_ID=$(mvn -q -pl copilot-native help:evaluate -Dexpression=project.artifactId -DforceStdout) + POM_VERSION=$(mvn -q -pl copilot-native help:evaluate -Dexpression=project.version -DforceStdout) + if [ -z "$GROUP_ID" ] || [ -z "$ARTIFACT_ID" ] || [ "$POM_VERSION" != "$VERSION" ]; then + echo "::error::Unexpected copilot-native Maven coordinates: $GROUP_ID:$ARTIFACT_ID:$POM_VERSION (expected version $VERSION)" + exit 1 + fi + { + echo "### Snapshot Publish" + echo "- **Version:** $VERSION" + echo "- **Source commit:** \`${{ needs.resolve-source.outputs.source_sha }}\`" + echo "- **Repository:** Maven Central Snapshots" + echo "" + echo "#### Maven Coordinates" + echo "" + echo '```xml' + echo "" + echo " $GROUP_ID" + echo " $ARTIFACT_ID" + echo " $POM_VERSION" + echo "" + echo '```' + echo "" + echo "#### Published Native Classifiers" + echo "" + echo "| Classifier | Build runner | Artifact | SHA-256 | Status |" + echo "| --- | --- | --- | --- | --- |" + echo "| \`linux-x64\` | \`ubuntu-latest\` | \`$(basename "$LINUX_JAR")\` | \`$LINUX_SHA\` | Published |" + echo "| \`linux-arm64\` | \`ubuntu-24.04-arm\` | \`$(basename "${{ steps.linux-arm64-artifact.outputs.linux_arm64_jar }}")\` | \`${{ steps.linux-arm64-artifact.outputs.linux_arm64_sha }}\` | Published |" + echo "| \`win32-x64\` | \`windows-latest\` | \`$(basename "${{ steps.windows-artifact.outputs.windows_jar }}")\` | \`${{ steps.windows-artifact.outputs.windows_sha }}\` | Published |" + echo "| \`win32-arm64\` | \`windows-11-arm\` | \`$(basename "${{ steps.windows-arm64-artifact.outputs.windows_arm64_jar }}")\` | \`${{ steps.windows-arm64-artifact.outputs.windows_arm64_sha }}\` | Published |" + echo "| \`darwin-arm64\` | \`macos-26\` | \`$(basename "${{ steps.darwin-artifact.outputs.darwin_jar }}")\` | \`${{ steps.darwin-artifact.outputs.darwin_sha }}\` | Published |" + } >> "$GITHUB_STEP_SUMMARY" env: MAVEN_USERNAME: ${{ secrets.JAVA_MAVEN_CENTRAL_USERNAME }} MAVEN_PASSWORD: ${{ secrets.JAVA_MAVEN_CENTRAL_PASSWORD }} diff --git a/.github/workflows/java-sdk-tests.yml b/.github/workflows/java-sdk-tests.yml index 20083749ec..0b6b62a0d9 100644 --- a/.github/workflows/java-sdk-tests.yml +++ b/.github/workflows/java-sdk-tests.yml @@ -1,22 +1,423 @@ name: "Java SDK Tests" on: - push: - branches: - - main - paths: - - "java/**" - - "test/**" - - ".github/workflows/java-sdk-tests.yml" - - ".github/actions/setup-copilot/**" - - ".github/actions/java-test-report/**" workflow_dispatch: workflow_call: permissions: contents: read +env: + MAVEN_OPTS: >- + -Daether.connector.http.retryHandler.count=3 + -Daether.connector.http.retryHandler.serviceUnavailable=429,502,503 + jobs: + java-sdk-inprocess: + name: "Java SDK InProcess Tests (${{ matrix.classifier }})" + if: github.event.repository.fork == false + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + classifier: linux-x64 + - os: ubuntu-24.04-arm + classifier: linux-arm64 + maven-args: -Dcopilot.native.libc=glibc + - os: windows-latest + classifier: win32-x64 + - os: windows-11-arm + classifier: win32-arm64 + - os: macos-26 + classifier: darwin-arm64 + runs-on: ${{ matrix.os }} + defaults: + run: + 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: Validate native host + run: node copilot-native/scripts/validate-native-host.mjs ${{ matrix.classifier }} + + - name: Run Java SDK tests (InProcess) + env: + CI: "true" + run: mvn clean verify -Pinprocess ${{ matrix.maven-args }} + + - name: Generate Test Report Summary + if: failure() + 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-${{ matrix.classifier }} + path: | + java/sdk/target/surefire-reports/ + java/sdk/target/surefire-reports-isolated/ + java/sdk/target/failsafe-reports/ + retention-days: 7 + + java-native-publication-linux-arm64: + name: "Java Native Publication Input (linux-arm64)" + if: github.event.repository.fork == false + runs-on: ubuntu-24.04-arm + permissions: + contents: read + outputs: + source_sha: ${{ steps.build.outputs.source_sha }} + version: ${{ steps.build.outputs.version }} + 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: Build and validate linux-arm64 classifier + id: build + run: | + set -euo pipefail + node copilot-native/scripts/validate-native-host.mjs linux-arm64 + mvn -B -pl copilot-native package -DskipTests -Dcopilot.native.libc=glibc + VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) + JAR="copilot-native/target/copilot-sdk-java-runtime-$VERSION-linux-arm64.jar" + PRIMARY_JAR="copilot-native/target/copilot-sdk-java-runtime-$VERSION.jar" + test -f "$JAR" + node copilot-native/scripts/validate-native-artifact.mjs \ + classifier linux-arm64 "$JAR" "$(basename "$JAR")" .. + node copilot-native/scripts/validate-native-artifact.mjs placeholder "$PRIMARY_JAR" + MANIFEST="copilot-native/target/linux-arm64-$VERSION.sha256" + HASH=$(sha256sum "$JAR" | cut -d ' ' -f 1) + printf '%s %s' "$HASH" "$(basename "$JAR")" > "$MANIFEST" + node copilot-native/scripts/validate-native-artifact.mjs \ + checksum "$JAR" "$MANIFEST" "$(basename "$JAR")" + echo "source_sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: java-native-publication-linux-arm64-${{ github.run_id }} + path: | + java/copilot-native/target/copilot-sdk-java-runtime-${{ steps.build.outputs.version }}-linux-arm64.jar + java/copilot-native/target/linux-arm64-${{ steps.build.outputs.version }}.sha256 + if-no-files-found: error + overwrite: true + retention-days: 1 + + java-native-publication-windows: + name: "Java Native Publication Input (win32-x64)" + if: github.event.repository.fork == false + runs-on: windows-latest + permissions: + contents: read + outputs: + source_sha: ${{ steps.build.outputs.source_sha }} + version: ${{ steps.build.outputs.version }} + defaults: + run: + shell: pwsh + 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: Build and validate win32-x64 classifier + id: build + run: | + node copilot-native/scripts/validate-native-host.mjs win32-x64 + mvn -B -pl copilot-native package -DskipTests + $version = mvn help:evaluate "-Dexpression=project.version" -q "-DforceStdout" + $jar = "copilot-native/target/copilot-sdk-java-runtime-$version-win32-x64.jar" + $primaryJar = "copilot-native/target/copilot-sdk-java-runtime-$version.jar" + if (-not (Test-Path -LiteralPath $jar -PathType Leaf)) { + throw "Expected Windows classifier was not produced: $jar" + } + node copilot-native/scripts/validate-native-artifact.mjs classifier win32-x64 $jar ([IO.Path]::GetFileName($jar)) .. + node copilot-native/scripts/validate-native-artifact.mjs placeholder $primaryJar + $manifest = "copilot-native/target/win32-x64-$version.sha256" + $hash = (Get-FileHash -Algorithm SHA256 -LiteralPath $jar).Hash.ToLowerInvariant() + "$hash $([IO.Path]::GetFileName($jar))" | Set-Content -NoNewline -Encoding ascii $manifest + node copilot-native/scripts/validate-native-artifact.mjs checksum $jar $manifest ([IO.Path]::GetFileName($jar)) + echo "source_sha=$(git rev-parse HEAD)" >> $env:GITHUB_OUTPUT + echo "version=$version" >> $env:GITHUB_OUTPUT + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: java-native-publication-win32-x64-${{ github.run_id }} + path: | + java/copilot-native/target/copilot-sdk-java-runtime-${{ steps.build.outputs.version }}-win32-x64.jar + java/copilot-native/target/win32-x64-${{ steps.build.outputs.version }}.sha256 + if-no-files-found: error + overwrite: true + retention-days: 1 + + java-native-publication-windows-arm64: + name: "Java Native Publication Input (win32-arm64)" + if: github.event.repository.fork == false + runs-on: windows-11-arm + permissions: + contents: read + outputs: + source_sha: ${{ steps.build.outputs.source_sha }} + version: ${{ steps.build.outputs.version }} + defaults: + run: + shell: pwsh + 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: Build and validate win32-arm64 classifier + id: build + run: | + node copilot-native/scripts/validate-native-host.mjs win32-arm64 + mvn -B -pl copilot-native package -DskipTests + $version = mvn help:evaluate "-Dexpression=project.version" -q "-DforceStdout" + $jar = "copilot-native/target/copilot-sdk-java-runtime-$version-win32-arm64.jar" + $primaryJar = "copilot-native/target/copilot-sdk-java-runtime-$version.jar" + if (-not (Test-Path -LiteralPath $jar -PathType Leaf)) { + throw "Expected Windows ARM64 classifier was not produced: $jar" + } + node copilot-native/scripts/validate-native-artifact.mjs classifier win32-arm64 $jar ([IO.Path]::GetFileName($jar)) .. + node copilot-native/scripts/validate-native-artifact.mjs placeholder $primaryJar + $manifest = "copilot-native/target/win32-arm64-$version.sha256" + $hash = (Get-FileHash -Algorithm SHA256 -LiteralPath $jar).Hash.ToLowerInvariant() + "$hash $([IO.Path]::GetFileName($jar))" | Set-Content -NoNewline -Encoding ascii $manifest + node copilot-native/scripts/validate-native-artifact.mjs checksum $jar $manifest ([IO.Path]::GetFileName($jar)) + echo "source_sha=$(git rev-parse HEAD)" >> $env:GITHUB_OUTPUT + echo "version=$version" >> $env:GITHUB_OUTPUT + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: java-native-publication-win32-arm64-${{ github.run_id }} + path: | + java/copilot-native/target/copilot-sdk-java-runtime-${{ steps.build.outputs.version }}-win32-arm64.jar + java/copilot-native/target/win32-arm64-${{ steps.build.outputs.version }}.sha256 + if-no-files-found: error + overwrite: true + retention-days: 1 + + java-native-publication-darwin: + name: "Java Native Publication Input (darwin-arm64)" + if: github.event.repository.fork == false + runs-on: macos-26 + permissions: + contents: read + outputs: + source_sha: ${{ steps.build.outputs.source_sha }} + version: ${{ steps.build.outputs.version }} + 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: Build and validate darwin-arm64 classifier + id: build + run: | + set -euo pipefail + node copilot-native/scripts/validate-native-host.mjs darwin-arm64 + mvn -B -pl copilot-native package -DskipTests + VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) + JAR="copilot-native/target/copilot-sdk-java-runtime-$VERSION-darwin-arm64.jar" + PRIMARY_JAR="copilot-native/target/copilot-sdk-java-runtime-$VERSION.jar" + test -f "$JAR" + node copilot-native/scripts/validate-native-artifact.mjs \ + classifier darwin-arm64 "$JAR" "$(basename "$JAR")" .. + node copilot-native/scripts/validate-native-artifact.mjs placeholder "$PRIMARY_JAR" + MANIFEST="copilot-native/target/darwin-arm64-$VERSION.sha256" + HASH=$(shasum -a 256 "$JAR" | cut -d ' ' -f 1) + printf '%s %s' "$HASH" "$(basename "$JAR")" > "$MANIFEST" + node copilot-native/scripts/validate-native-artifact.mjs \ + checksum "$JAR" "$MANIFEST" "$(basename "$JAR")" + echo "source_sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: java-native-publication-darwin-arm64-${{ github.run_id }} + path: | + java/copilot-native/target/copilot-sdk-java-runtime-${{ steps.build.outputs.version }}-darwin-arm64.jar + java/copilot-native/target/darwin-arm64-${{ steps.build.outputs.version }}.sha256 + if-no-files-found: error + overwrite: true + retention-days: 1 + + java-native-publication-assembly: + name: "Java Native Publication Assembly" + if: github.event.repository.fork == false + needs: + [ + java-native-publication-linux-arm64, + java-native-publication-windows, + java-native-publication-windows-arm64, + java-native-publication-darwin, + ] + runs-on: ubuntu-latest + defaults: + run: + shell: bash + working-directory: ./java + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.sha }} + 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 + + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: java-native-publication-linux-arm64-${{ github.run_id }} + path: ${{ github.workspace }}/java/native-publication-input/linux-arm64 + + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: java-native-publication-win32-x64-${{ github.run_id }} + path: ${{ github.workspace }}/java/native-publication-input/windows + + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: java-native-publication-win32-arm64-${{ github.run_id }} + path: ${{ github.workspace }}/java/native-publication-input/windows-arm64 + + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: java-native-publication-darwin-arm64-${{ github.run_id }} + path: ${{ github.workspace }}/java/native-publication-input/darwin + + - name: Verify native inputs and deploy the complete local release + run: | + set -euo pipefail + test "$(git rev-parse HEAD)" = "${{ needs.java-native-publication-linux-arm64.outputs.source_sha }}" + test "$(git rev-parse HEAD)" = "${{ needs.java-native-publication-windows.outputs.source_sha }}" + test "$(git rev-parse HEAD)" = "${{ needs.java-native-publication-windows-arm64.outputs.source_sha }}" + test "$(git rev-parse HEAD)" = "${{ needs.java-native-publication-darwin.outputs.source_sha }}" + VERSION="${{ needs.java-native-publication-windows.outputs.version }}" + test "$VERSION" = "${{ needs.java-native-publication-linux-arm64.outputs.version }}" + test "$VERSION" = "${{ needs.java-native-publication-windows-arm64.outputs.version }}" + test "$VERSION" = "${{ needs.java-native-publication-darwin.outputs.version }}" + LINUX_ARM64_DIRECTORY="$GITHUB_WORKSPACE/java/native-publication-input/linux-arm64" + WINDOWS_DIRECTORY="$GITHUB_WORKSPACE/java/native-publication-input/windows" + WINDOWS_ARM64_DIRECTORY="$GITHUB_WORKSPACE/java/native-publication-input/windows-arm64" + DARWIN_DIRECTORY="$GITHUB_WORKSPACE/java/native-publication-input/darwin" + LINUX_ARM64_JAR="$LINUX_ARM64_DIRECTORY/copilot-sdk-java-runtime-$VERSION-linux-arm64.jar" + LINUX_ARM64_MANIFEST="$LINUX_ARM64_DIRECTORY/linux-arm64-$VERSION.sha256" + WINDOWS_JAR="$WINDOWS_DIRECTORY/copilot-sdk-java-runtime-$VERSION-win32-x64.jar" + WINDOWS_MANIFEST="$WINDOWS_DIRECTORY/win32-x64-$VERSION.sha256" + WINDOWS_ARM64_JAR="$WINDOWS_ARM64_DIRECTORY/copilot-sdk-java-runtime-$VERSION-win32-arm64.jar" + WINDOWS_ARM64_MANIFEST="$WINDOWS_ARM64_DIRECTORY/win32-arm64-$VERSION.sha256" + DARWIN_JAR="$DARWIN_DIRECTORY/copilot-sdk-java-runtime-$VERSION-darwin-arm64.jar" + DARWIN_MANIFEST="$DARWIN_DIRECTORY/darwin-arm64-$VERSION.sha256" + node copilot-native/scripts/validate-native-artifact.mjs \ + checksum "$LINUX_ARM64_JAR" "$LINUX_ARM64_MANIFEST" "$(basename "$LINUX_ARM64_JAR")" + node copilot-native/scripts/validate-native-artifact.mjs \ + classifier linux-arm64 "$LINUX_ARM64_JAR" "$(basename "$LINUX_ARM64_JAR")" .. + node copilot-native/scripts/validate-native-artifact.mjs \ + checksum "$WINDOWS_JAR" "$WINDOWS_MANIFEST" "$(basename "$WINDOWS_JAR")" + node copilot-native/scripts/validate-native-artifact.mjs \ + classifier win32-x64 "$WINDOWS_JAR" "$(basename "$WINDOWS_JAR")" .. + node copilot-native/scripts/validate-native-artifact.mjs \ + checksum "$WINDOWS_ARM64_JAR" "$WINDOWS_ARM64_MANIFEST" "$(basename "$WINDOWS_ARM64_JAR")" + node copilot-native/scripts/validate-native-artifact.mjs \ + classifier win32-arm64 "$WINDOWS_ARM64_JAR" "$(basename "$WINDOWS_ARM64_JAR")" .. + node copilot-native/scripts/validate-native-artifact.mjs \ + checksum "$DARWIN_JAR" "$DARWIN_MANIFEST" "$(basename "$DARWIN_JAR")" + node copilot-native/scripts/validate-native-artifact.mjs \ + classifier darwin-arm64 "$DARWIN_JAR" "$(basename "$DARWIN_JAR")" .. + export GNUPGHOME="$GITHUB_WORKSPACE/java/copilot-native/target/local-publication-gpg" + rm -rf "$GNUPGHOME" + mkdir -p "$GNUPGHOME" + chmod 700 "$GNUPGHOME" + gpg --batch --pinentry-mode loopback --passphrase '' \ + --quick-generate-key 'Copilot SDK local validation ' rsa2048 sign 1d + LOCAL_REPOSITORY="$GITHUB_WORKSPACE/java/copilot-native/target/local-publication-repository" + rm -rf "$LOCAL_REPOSITORY" + mvn -B -pl copilot-native deploy -Prelease -DskipTests \ + -Dcopilot.native.libc=glibc \ + -Dcopilot.native.test.local.publication=true \ + "-Dcopilot.native.external.linux.arm64.classifier.path=$LINUX_ARM64_JAR" \ + "-Dcopilot.native.external.win32.classifier.path=$WINDOWS_JAR" \ + "-Dcopilot.native.external.win32.arm64.classifier.path=$WINDOWS_ARM64_JAR" \ + "-Dcopilot.native.external.darwin.classifier.path=$DARWIN_JAR" \ + "-Dmaven.repo.local=$LOCAL_REPOSITORY" + node copilot-native/scripts/validate-local-publication.mjs \ + "$LOCAL_REPOSITORY" copilot-sdk-java-runtime "$VERSION" .. --signatures + java-sdk: name: "Java SDK Tests (JDK ${{ matrix.test-jdk }})" if: github.event.repository.fork == false @@ -57,7 +458,12 @@ jobs: run: mvn javadoc:javadoc -q - name: Verify CLI works - run: node ../nodejs/node_modules/@github/copilot/npm-loader.js --version + run: | + npm --prefix ../nodejs ci --ignore-scripts + cli_path=$(npm --prefix ../nodejs run --silent prepare:runtime -- --print-path) + test -x "$cli_path" + legacy_cli=$(npm --prefix ../nodejs run --silent prepare:runtime -- --print-legacy-path) + node "$legacy_cli" --version - name: Run spotless check if: matrix.test-jdk == '25' @@ -80,7 +486,8 @@ jobs: if: matrix.test-jdk == '25' env: CI: "true" - run: mvn verify -Dskip.test.harness=true + # Native artifacts are built and exercised by every classifier job. + run: mvn -pl sdk verify -Dskip.test.harness=true - name: Switch to JDK 17 if: matrix.test-jdk == '17' @@ -96,17 +503,17 @@ 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' + if: success() && github.event_name == 'merge_group' && matrix.test-jdk == '25' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 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 @@ -121,7 +528,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 cffef0a353..e7e9a417d2 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 e209a110b6..0000000000 --- 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 4c31f79cc4..894f3b5d0d 100644 --- a/.github/workflows/nodejs-sdk-tests.yml +++ b/.github/workflows/nodejs-sdk-tests.yml @@ -4,9 +4,6 @@ env: HUSKY: 0 on: - push: - branches: - - main workflow_dispatch: workflow_call: @@ -52,10 +49,26 @@ jobs: - name: Build SDK run: npm run build + - name: Build and verify release packages + if: runner.os == 'Linux' && matrix.transport == 'default' + run: | + npm run pack:release + npm run verify:release-packages + - name: Install test harness dependencies working-directory: ./test/harness run: npm ci --ignore-scripts + - name: Run test harness tests + if: runner.os == 'Linux' && matrix.transport == 'default' + working-directory: ./test/harness + run: npm test + + - name: Prepare Copilot CLI runtime + run: | + runtime_path=$(npm run --silent prepare:runtime -- --print-path) + echo "COPILOT_CLI_PATH=$runtime_path" >> "$GITHUB_ENV" + - name: Warm up PowerShell if: runner.os == 'Windows' run: pwsh.exe -Command "Write-Host 'PowerShell ready'" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b44fd582a2..d1961d4a97 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -107,19 +107,18 @@ jobs: - name: Build run: npm run build - name: Pack - id: pack run: | - TARBALL="$(npm pack . --json | jq -r '.[0].filename')" - if [ -z "$TARBALL" ] || [ ! -f "$TARBALL" ]; then - echo "::error::npm pack did not produce a tarball." + npm run pack:release + TARBALL_COUNT="$(find . -maxdepth 1 -name 'github-copilot-sdk-*.tgz' | wc -l | tr -d ' ')" + if [ "$TARBALL_COUNT" -ne 9 ]; then + echo "::error::Expected nine Node.js package tarballs, found $TARBALL_COUNT." exit 1 fi - echo "tarball=$TARBALL" >> "$GITHUB_OUTPUT" - name: Upload artifact uses: actions/upload-artifact@v7.0.0 with: name: nodejs-package - path: nodejs/${{ steps.pack.outputs.tarball }} + path: nodejs/github-copilot-sdk-*.tgz if-no-files-found: error publish-nodejs: @@ -150,12 +149,29 @@ jobs: set -euo pipefail shopt -s nullglob TARBALLS=(./dist/*.tgz) - if [ "${#TARBALLS[@]}" -ne 1 ]; then - echo "::error::Expected exactly one Node.js package tarball, found ${#TARBALLS[@]}." + if [ "${#TARBALLS[@]}" -ne 9 ]; then + echo "::error::Expected nine Node.js package tarballs, found ${#TARBALLS[@]}." + exit 1 + fi + MAIN_TARBALL="" + for TARBALL in "${TARBALLS[@]}"; do + PACKAGE_NAME="$(tar -xOf "$TARBALL" package/package.json | jq -r .name)" + if [ "$PACKAGE_NAME" = "@github/copilot-sdk" ]; then + MAIN_TARBALL="$TARBALL" + continue + fi + node nodejs/scripts/npm-release.js publish \ + "$TARBALL" \ + "$DIST_TAG" \ + https://registry.npmjs.org \ + public + done + if [ -z "$MAIN_TARBALL" ]; then + echo "::error::Main @github/copilot-sdk tarball not found." exit 1 fi node nodejs/scripts/npm-release.js publish \ - "${TARBALLS[0]}" \ + "$MAIN_TARBALL" \ "$DIST_TAG" \ https://registry.npmjs.org \ public @@ -209,12 +225,29 @@ jobs: fi shopt -s nullglob TARBALLS=(./dist/*.tgz) - if [ "${#TARBALLS[@]}" -ne 1 ]; then - echo "::error::Expected exactly one Node.js package tarball, found ${#TARBALLS[@]}." + if [ "${#TARBALLS[@]}" -ne 9 ]; then + echo "::error::Expected nine Node.js package tarballs, found ${#TARBALLS[@]}." + exit 1 + fi + MAIN_TARBALL="" + for TARBALL in "${TARBALLS[@]}"; do + PACKAGE_NAME="$(tar -xOf "$TARBALL" package/package.json | jq -r .name)" + if [ "$PACKAGE_NAME" = "@github/copilot-sdk" ]; then + MAIN_TARBALL="$TARBALL" + continue + fi + node nodejs/scripts/npm-release.js publish \ + "$TARBALL" \ + "$DIST_TAG" \ + "$FEED_URL" \ + azure + done + if [ -z "$MAIN_TARBALL" ]; then + echo "::error::Main @github/copilot-sdk tarball not found." exit 1 fi node nodejs/scripts/npm-release.js publish \ - "${TARBALLS[0]}" \ + "$MAIN_TARBALL" \ "$DIST_TAG" \ "$FEED_URL" \ azure @@ -327,9 +360,6 @@ jobs: node-version: "22.x" - name: Set up uv uses: astral-sh/setup-uv@v7 - - name: Install Node.js dependencies (for CLI version) - working-directory: ./nodejs - run: npm ci --ignore-scripts - name: Set version run: sed -i "s/^version = .*/version = \"${{ needs.version.outputs.version }}\"/" pyproject.toml - name: Inject CLI version @@ -379,7 +409,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 +465,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 +482,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 1ea9739756..aa6e803372 100644 --- a/.github/workflows/python-sdk-tests.yml +++ b/.github/workflows/python-sdk-tests.yml @@ -4,9 +4,6 @@ env: PYTHONUTF8: 1 on: - push: - branches: - - main workflow_dispatch: workflow_call: @@ -27,6 +24,7 @@ jobs: python-version: ["3.11"] transport: ["default", "inprocess"] runs-on: ${{ matrix.os }} + timeout-minutes: 20 defaults: run: shell: bash @@ -52,7 +50,7 @@ jobs: - name: Install Node.js dependencies (for CLI in tests) working-directory: ./nodejs - run: npm ci --ignore-scripts + run: npm ci --ignore-scripts --fetch-retries=4 --fetch-retry-mintimeout=10000 --fetch-retry-maxtimeout=60000 - name: Run ruff format check run: uv run ruff format --check . @@ -65,7 +63,7 @@ jobs: - name: Install test harness dependencies working-directory: ./test/harness - run: npm ci --ignore-scripts + run: npm ci --ignore-scripts --fetch-retries=4 --fetch-retry-mintimeout=10000 --fetch-retry-maxtimeout=60000 - name: Warm up PowerShell if: runner.os == 'Windows' diff --git a/.github/workflows/release-changelog.lock.yml b/.github/workflows/release-changelog.lock.yml index 781f41b22b..23b19d9b82 100644 --- a/.github/workflows/release-changelog.lock.yml +++ b/.github/workflows/release-changelog.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"9342b428009e6a3b47258c08b78735a89fc72714b73a44b72c4714e310d60006","body_hash":"89e26ed929f440bd6af57d1da92b06dbf1739b4a1d34b9923286919d00f272d1","compiler_version":"v0.83.1","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}} +# 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 # @@ -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 diff --git a/.github/workflows/release-changelog.md b/.github/workflows/release-changelog.md index 7a682c56dd..c846b0dd32 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 index dbd79fd4cf..0fb8acf70b 100644 --- a/.github/workflows/required-checks.yml +++ b/.github/workflows/required-checks.yml @@ -10,6 +10,10 @@ permissions: contents: read pull-requests: read +concurrency: + group: sdk-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: changes: name: Select SDK workflows @@ -33,42 +37,41 @@ jobs: - '.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}' + - '{python/**,test/**,nodejs/package.json,nodejs/scripts/**,.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/**}' + - '{go/**,test/**,nodejs/package.json,nodejs/scripts/**,.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}' + - '{dotnet/**,test/**,nodejs/package.json,nodejs/scripts/**,.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/**}' + - '{java/**,test/**,nodejs/package.json,nodejs/scripts/**,.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/**}' + - '{rust/**,test/**,nodejs/package.json,nodejs/scripts/**,.github/workflows/rust-sdk-tests.yml,.github/actions/setup-copilot/**}' - '!**/*.md' - '!**/LICENSE*' - '!**/.gitignore' diff --git a/.github/workflows/rust-sdk-tests.yml b/.github/workflows/rust-sdk-tests.yml index 7fdac3b818..828459126b 100644 --- a/.github/workflows/rust-sdk-tests.yml +++ b/.github/workflows/rust-sdk-tests.yml @@ -1,9 +1,6 @@ name: "Rust SDK Tests" on: - push: - branches: - - main workflow_dispatch: workflow_call: @@ -23,6 +20,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} + timeout-minutes: 20 defaults: run: shell: bash @@ -38,17 +36,6 @@ jobs: uses: dtolnay/rust-toolchain@stable with: toolchain: "1.94.0" - components: rustfmt, clippy - - # Nightly rustfmt for unstable format options (group_imports, - # imports_granularity, reorder_impl_items) — pinned in - # `.rustfmt.nightly.toml`. - - name: Install nightly rustfmt - if: runner.os == 'Linux' - uses: dtolnay/rust-toolchain@master - with: - toolchain: nightly-2026-04-14 - components: rustfmt - uses: Swatinem/rust-cache@v2 with: @@ -56,11 +43,11 @@ jobs: prefix-key: v1-rust-no-bin cache-bin: false - - name: Read pinned @github/copilot CLI version + - name: Read pinned Copilot CLI version id: cli-version working-directory: ./nodejs run: | - version=$(node -p "require('./package-lock.json').packages['node_modules/@github/copilot'].version") + version=$(node -p "require('./package.json').copilotCliVersion") echo "version=$version" >> "$GITHUB_OUTPUT" echo "Pinned CLI version: $version" @@ -73,23 +60,6 @@ jobs: path: ./rust/.bundled-cli-cache key: bundled-cli-${{ matrix.os }}-${{ steps.cli-version.outputs.version }} - - name: cargo fmt --check (nightly) - if: runner.os == 'Linux' - run: cargo +nightly-2026-04-14 fmt --all -- --config-path .rustfmt.nightly.toml --check - - - name: cargo clippy - if: runner.os == 'Linux' - env: - BUNDLED_CLI_CACHE_DIR: ${{ github.workspace }}/rust/.bundled-cli-cache - run: cargo clippy --all-targets --features test-support,bundled-in-process -- --no-deps -D warnings -D clippy::unwrap_used -D clippy::disallowed_macros -D clippy::await_holding_invalid_type - - - name: cargo doc - if: runner.os == 'Linux' - env: - RUSTDOCFLAGS: "-D warnings" - BUNDLED_CLI_CACHE_DIR: ${{ github.workspace }}/rust/.bundled-cli-cache - run: cargo doc --no-deps --all-features - - name: Install test harness dependencies working-directory: ./test/harness run: npm ci --ignore-scripts @@ -112,6 +82,112 @@ jobs: # The dedicated `bundle` job below exercises the embed pipeline. run: cargo test --no-default-features --features test-support -- --test-threads=4 --nocapture + clippy: + name: "Rust SDK Format and Clippy" + if: github.event.repository.fork == false + env: + POWERSHELL_UPDATECHECK: Off + CARGO_TERM_COLOR: always + RUST_BACKTRACE: 1 + runs-on: ubuntu-latest + timeout-minutes: 20 + defaults: + run: + shell: bash + working-directory: ./rust + steps: + - uses: actions/checkout@v6.0.2 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 # stable + with: + toolchain: "1.94.0" + components: clippy + + # Nightly rustfmt for unstable format options (group_imports, + # imports_granularity, reorder_impl_items) — pinned in + # `.rustfmt.nightly.toml`. + - name: Install nightly rustfmt + uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 + with: + toolchain: nightly-2026-04-14 + components: rustfmt + + - uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2 + with: + workspaces: "rust" + prefix-key: v1-rust-no-bin + cache-bin: false + + - name: Read pinned Copilot CLI version + id: cli-version + working-directory: ./nodejs + run: | + version=$(node -p "require('./package.json').copilotCliVersion") + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "Pinned CLI version: $version" + + - name: Cache bundled CLI archives + uses: actions/cache@v4 + with: + path: ./rust/.bundled-cli-cache + key: bundled-cli-ubuntu-latest-${{ steps.cli-version.outputs.version }} + + - name: cargo fmt --check (nightly) + run: cargo +nightly-2026-04-14 fmt --all -- --config-path .rustfmt.nightly.toml --check + + - name: cargo clippy + env: + BUNDLED_CLI_CACHE_DIR: ${{ github.workspace }}/rust/.bundled-cli-cache + run: cargo clippy --all-targets --features test-support,bundled-in-process -- --no-deps -D warnings -D clippy::unwrap_used -D clippy::disallowed_macros -D clippy::await_holding_invalid_type + + doc: + name: "Rust SDK Docs" + if: github.event.repository.fork == false + env: + POWERSHELL_UPDATECHECK: Off + CARGO_TERM_COLOR: always + RUST_BACKTRACE: 1 + runs-on: ubuntu-latest + timeout-minutes: 20 + defaults: + run: + shell: bash + working-directory: ./rust + steps: + - uses: actions/checkout@v6.0.2 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 # stable + with: + toolchain: "1.94.0" + + - uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2 + with: + workspaces: "rust" + prefix-key: v1-rust-no-bin + cache-bin: false + + - name: Read pinned Copilot CLI version + id: cli-version + working-directory: ./nodejs + run: | + version=$(node -p "require('./package.json').copilotCliVersion") + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "Pinned CLI version: $version" + + - name: Cache bundled CLI archives + uses: actions/cache@v4 + with: + path: ./rust/.bundled-cli-cache + key: bundled-cli-ubuntu-latest-${{ steps.cli-version.outputs.version }} + + - name: cargo doc + env: + RUSTDOCFLAGS: "-D warnings" + BUNDLED_CLI_CACHE_DIR: ${{ github.workspace }}/rust/.bundled-cli-cache + run: cargo doc --no-deps --all-features + # Exercises the in-process FFI transport (`Transport::InProcess`, the Rust # analogue of the .NET `RuntimeConnection.ForInProcess()`), mirroring the # `inprocess` transport cell in dotnet-sdk-tests.yml. Sets @@ -133,6 +209,7 @@ jobs: # TODO: Re-enable Windows after fixing the napi-oop peer shutdown crash. os: [ubuntu-latest, macos-latest] runs-on: ${{ matrix.os }} + timeout-minutes: 20 defaults: run: shell: bash @@ -154,11 +231,11 @@ jobs: prefix-key: v1-rust-no-bin cache-bin: false - - name: Read pinned @github/copilot CLI version + - name: Read pinned Copilot CLI version id: cli-version working-directory: ./nodejs run: | - version=$(node -p "require('./package-lock.json').packages['node_modules/@github/copilot'].version") + version=$(node -p "require('./package.json').copilotCliVersion") echo "version=$version" >> "$GITHUB_OUTPUT" echo "Pinned CLI version: $version" @@ -207,6 +284,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} + timeout-minutes: 20 defaults: run: shell: bash @@ -229,11 +307,11 @@ jobs: prefix-key: v1-rust-no-bin cache-bin: false - - name: Read pinned @github/copilot CLI version + - name: Read pinned Copilot CLI version id: cli-version working-directory: ./nodejs run: | - version=$(node -p "require('./package-lock.json').packages['node_modules/@github/copilot'].version") + version=$(node -p "require('./package.json').copilotCliVersion") echo "version=$version" >> "$GITHUB_OUTPUT" echo "Pinned CLI version: $version" diff --git a/.github/workflows/sdk-canary.yml b/.github/workflows/sdk-canary.yml index 95f8b1c926..7425f8e720 100644 --- a/.github/workflows/sdk-canary.yml +++ b/.github/workflows/sdk-canary.yml @@ -1,391 +1,428 @@ -name: "SDK Canary Test/Publish" - -# Nightly-style canary pipeline. First installs an explicit version of the -# @github/copilot runtime, builds the Node SDK, and runs the Node e2e suite -# against it to prove runtime <-> SDK compatibility. When that gate passes (and -# mode allows), publishes an SDK canary pinned to the tested runtime to the -# internal Azure Artifacts feed only (never public npm). - -env: - HUSKY: 0 - # Internal org-scoped Azure Artifacts feed — single source of truth so the - # feed name isn't repeated across steps. The SDK canary publishes here and - # (when runtime_source=internal) installs the runtime from here; it must NEVER - # reach public npm (@github/copilot-sdk is a live public package). - FEED_URL: https://pkgs.dev.azure.com/devdiv/_packaging/copilot-canary/npm/registry/ - # Azure DevOps resource ID used to mint an ADO access token for the feed. - ADO_RESOURCE: 499b84ac-1321-427f-aa17-267ca6975798 - -on: - workflow_dispatch: - inputs: - runtime_version: - description: "Exact @github/copilot version to test (e.g. 1.0.69 or 1.0.70-canary.)" - required: true - type: string - runtime_source: - description: "Where to install the runtime from" - required: true - type: choice - options: - - public - - internal - default: public - mode: - description: "publish (tests must pass), publish-force (publish even if tests fail), or tests-only (run gate, never publish)" - required: false - type: choice - default: publish - options: - - publish - - publish-force - - tests-only - repository_dispatch: - types: [runtime-canary] - -permissions: - contents: read - id-token: write - -# Serialize runs per ref so two overlapping canary runs can't race the feed -# publish. cancel-in-progress: false — never kill an in-flight publish. -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - -jobs: - resolve: - name: "Resolve runtime inputs" - if: github.event.repository.fork == false - runs-on: ubuntu-latest - permissions: {} - outputs: - RUNTIME_VERSION: ${{ steps.normalize.outputs.RUNTIME_VERSION }} - RUNTIME_SOURCE: ${{ steps.normalize.outputs.RUNTIME_SOURCE }} - PUBLISH_MODE: ${{ steps.normalize.outputs.PUBLISH_MODE }} - steps: - # Normalize whichever trigger fired into a single (RUNTIME_VERSION, - # RUNTIME_SOURCE, PUBLISH_MODE) triple that every downstream step - # references. workflow_dispatch reads the human-supplied inputs; - # repository_dispatch reads client_payload and forces source=internal - # (a runtime canary only exists on the feed), defaulting mode to publish. - - name: Normalize inputs - id: normalize - env: - EVENT_NAME: ${{ github.event_name }} - INPUT_VERSION: ${{ inputs.runtime_version }} - INPUT_SOURCE: ${{ inputs.runtime_source }} - INPUT_MODE: ${{ inputs.mode }} - PAYLOAD_VERSION: ${{ github.event.client_payload.runtime_version }} - PAYLOAD_SOURCE: ${{ github.event.client_payload.runtime_source }} - PAYLOAD_MODE: ${{ github.event.client_payload.mode }} - run: | - set -euo pipefail - case "$EVENT_NAME" in - workflow_dispatch) - VERSION="$INPUT_VERSION" - SOURCE="$INPUT_SOURCE" - MODE="$INPUT_MODE" - ;; - repository_dispatch) - VERSION="$PAYLOAD_VERSION" - # A runtime canary only ever exists on the internal feed. - SOURCE="${PAYLOAD_SOURCE:-internal}" - MODE="${PAYLOAD_MODE:-publish}" - ;; - *) - echo "::error::Unsupported event '$EVENT_NAME'." - exit 1 - ;; - esac - if [ -z "$VERSION" ]; then echo "::error::Could not determine runtime version."; exit 1; fi - if [ -z "$SOURCE" ]; then SOURCE="public"; fi - case "$SOURCE" in - public|internal) ;; - *) echo "::error::Invalid runtime source '$SOURCE'. Expected one of: public, internal."; exit 1 ;; - esac - if [ -z "$MODE" ]; then MODE="publish"; fi - case "$MODE" in - publish|publish-force|tests-only) ;; - *) echo "::error::Invalid publish mode '$MODE'. Expected one of: publish, publish-force, tests-only."; exit 1 ;; - esac - echo "Resolved RUNTIME_VERSION=$VERSION RUNTIME_SOURCE=$SOURCE PUBLISH_MODE=$MODE" - echo "RUNTIME_VERSION=$VERSION" >> "$GITHUB_OUTPUT" - echo "RUNTIME_SOURCE=$SOURCE" >> "$GITHUB_OUTPUT" - echo "PUBLISH_MODE=$MODE" >> "$GITHUB_OUTPUT" - - - name: Validate runtime version (semver) - env: - RUNTIME_VERSION: ${{ steps.normalize.outputs.RUNTIME_VERSION }} - run: | - if [[ ! "$RUNTIME_VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$ ]]; then - echo "::error::Invalid runtime version '$RUNTIME_VERSION'. Expected semver (e.g. 1.0.69 or 1.0.70-canary.abc123)." - exit 1 - fi - - test: - name: "E2E tests (${{ matrix.os }})" - needs: resolve - if: github.event.repository.fork == false - environment: cicd - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - runs-on: ${{ matrix.os }} - env: - POWERSHELL_UPDATECHECK: Off - RUNTIME_VERSION: ${{ needs.resolve.outputs.RUNTIME_VERSION }} - RUNTIME_SOURCE: ${{ needs.resolve.outputs.RUNTIME_SOURCE }} - defaults: - run: - shell: bash - working-directory: ./nodejs - steps: - - uses: actions/checkout@v6.0.2 - - - uses: actions/setup-node@v6 - with: - cache: "npm" - cache-dependency-path: "./nodejs/package-lock.json" - node-version: 22 - - - name: Install SDK dependencies - run: npm ci --ignore-scripts - - - name: Install test harness dependencies - working-directory: ./test/harness - run: npm ci --ignore-scripts - - - name: Azure Login (OIDC -> id-cpd-ci) - if: env.RUNTIME_SOURCE == 'internal' - uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0 - with: - client-id: "${{ vars.CPD_ID_CLIENT_ID }}" # id-cpd-ci - tenant-id: "${{ vars.CPD_ID_TENANT_ID }}" - allow-no-subscriptions: true - - # Route ONLY @github/* (the runtime + its 8 platform packages) to the - # internal feed via a scoped registry. All other deps (e.g. detect-libc) - # still resolve from public npm. A global --registry would break because - # detect-libc is not on the feed. - - name: Configure canary feed (.npmrc) - if: env.RUNTIME_SOURCE == 'internal' - run: | - set -euo pipefail - TOKEN="$(az account get-access-token --resource "$ADO_RESOURCE" --query accessToken -o tsv)" - echo "::add-mask::$TOKEN" - # Derive the protocol-relative auth scopes from FEED_URL so the feed - # name lives in exactly one place (the workflow-level env). - FEED_AUTH_REGISTRY="${FEED_URL#https:}" - FEED_AUTH_BASE="${FEED_AUTH_REGISTRY%registry/}" - NPMRC="$(printf '%s\n' \ - "@github:registry=${FEED_URL}" \ - "${FEED_AUTH_REGISTRY}:_authToken=${TOKEN}" \ - "${FEED_AUTH_BASE}:_authToken=${TOKEN}")" - printf '%s\n' "$NPMRC" > .npmrc - echo "Wrote scoped @github registry .npmrc to ./nodejs" - - - name: Override runtime version - run: | - set -euo pipefail - echo "Installing @github/copilot@${RUNTIME_VERSION} (source: ${RUNTIME_SOURCE})" - npm install "@github/copilot@${RUNTIME_VERSION}" --save-exact --ignore-scripts - - - name: Verify installed runtime - run: | - set -euo pipefail - node -e ' - const fs = require("fs"); - const expected = process.env.RUNTIME_VERSION; - const pkg = require("./node_modules/@github/copilot/package.json"); - if (pkg.version !== expected) { - console.error(`::error::Installed @github/copilot version ${pkg.version} does not match requested ${expected}`); - process.exit(1); - } - const dir = "./node_modules/@github"; - const entries = fs.readdirSync(dir).filter((d) => d.startsWith("copilot-")); - const plat = process.platform === "win32" ? "win32" : process.platform === "darwin" ? "darwin" : "linux"; - const arch = process.arch; - const match = entries.find((d) => d.includes(plat) && d.includes(arch)); - if (!match) { - console.error(`::error::No @github/copilot platform optional dep for ${plat}-${arch}. Present: ${entries.join(", ") || "(none)"}`); - process.exit(1); - } - const platPkg = require(`${dir}/${match}/package.json`); - if (platPkg.version !== expected) { - console.error(`::error::Platform package @github/${match} version ${platPkg.version} does not match requested ${expected}`); - process.exit(1); - } - console.log(`Verified @github/copilot@${pkg.version} with platform package @github/${match}@${platPkg.version}`); - ' - - - name: Build SDK - run: npm run build - - - name: Warm up PowerShell - if: runner.os == 'Windows' - run: pwsh.exe -Command "Write-Host 'PowerShell ready'" - - - name: Run Node.js SDK e2e tests - env: - COPILOT_HMAC_KEY: ${{ secrets.COPILOT_DEVELOPER_CLI_INTEGRATION_HMAC_KEY }} - run: npm test - - publish: - name: "Publish SDK canary (internal feed)" - needs: [resolve, test] - # Publish runs only when the gate permits it. Mode governs behavior: - # - tests-only: never publish (skips this job entirely). - # - publish: publish only when the e2e gate is green (the default for both - # the human and automated triggers). - # - publish-force: publish even on a non-green gate — a human-acknowledged - # flake override, audited via the ::warning:: step below and the run actor. - # publish-force only skips the e2e *signal* — the publish job still runs the - # build (so a broken build can't publish) and enforces the feed-only guards. - if: > - !cancelled() && - github.event.repository.fork == false && - needs.resolve.result == 'success' && - needs.resolve.outputs.PUBLISH_MODE != 'tests-only' && - (needs.test.result == 'success' || - needs.resolve.outputs.PUBLISH_MODE == 'publish-force') - environment: cicd - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - env: - RUNTIME_VERSION: ${{ needs.resolve.outputs.RUNTIME_VERSION }} - defaults: - run: - shell: bash - working-directory: ./nodejs - steps: - - name: Warn — publishing despite failed e2e gate (publish-force) - # always() so this audit is never skipped by prior-step status; it fires - # specifically when publish proceeded on a non-green gate via publish-force. - # Runs at the workspace root because it executes before checkout, so the - # job's default working-directory (./nodejs) does not exist yet. - if: always() && needs.test.result != 'success' && needs.resolve.outputs.PUBLISH_MODE == 'publish-force' - working-directory: ${{ github.workspace }} - run: | - echo "::warning title=e2e gate bypassed::Publishing SDK canary despite a non-passing e2e gate (test job result: ${{ needs.test.result }}) via publish-force. Triggered by '${{ github.actor }}' through '${{ github.event_name }}'. The e2e signal was bypassed; build + feed-only guards still apply." - - - uses: actions/checkout@v6.0.2 - - - uses: actions/setup-node@v6 - with: - node-version: 22 - - # Default public registry: installs build deps and the currently pinned - # runtime. Do NOT write any feed .npmrc or scoped @github:registry line - # here, or npm ci would try to fetch the runtime from the upstream-less - # feed and 404. - - name: Install SDK dependencies - run: npm ci --ignore-scripts - - - name: Compute SDK canary version - id: sdkver - env: - RUN_NUMBER: ${{ github.run_number }} - SHA: ${{ github.sha }} - run: | - set -euo pipefail - SHORT_SHA="${SHA:0:7}" - # Base the canary on the NEXT patch of the public SDK latest so canaries - # correlate with public releases: they sort ABOVE the current public - # latest and BELOW the eventual real release of that next patch (a - # prerelease of X.Y.Z always sorts below X.Y.Z), so a canary can never - # shadow the real release when it ships. - # Reuse the repo's own version helper (scripts/get-version.js) so this - # stays consistent with publish.yml: `current` returns the latest public - # dist-tag version, read-only from public npm (never the feed), then - # we bump the patch ourselves to keep strict patch+1 semantics. - PUBLIC_LATEST="$(node scripts/get-version.js current || true)" - BASE="${PUBLIC_LATEST%%-*}"; BASE="${BASE%%+*}" - if [[ "$BASE" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then - NEXT="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.$(( BASH_REMATCH[3] + 1 ))" - else - echo "::error::Could not resolve public SDK latest version (got '$PUBLIC_LATEST'); refusing to publish a canary with an unknown base." - exit 1 - fi - SDK_VERSION="${NEXT}-canary.${RUN_NUMBER}.g${SHORT_SHA}" - if [[ ! "$SDK_VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$ ]]; then - echo "::error::Computed SDK canary version '$SDK_VERSION' is not valid semver." - exit 1 - fi - echo "SDK canary version: $SDK_VERSION" - echo "SDK_VERSION=$SDK_VERSION" >> "$GITHUB_OUTPUT" - - - name: Set package version and pin runtime dependency - env: - SDK_VERSION: ${{ steps.sdkver.outputs.SDK_VERSION }} - run: | - set -euo pipefail - npm version "$SDK_VERSION" --no-git-tag-version --allow-same-version - # Exact pin (no caret) so the published SDK canary depends on precisely - # the runtime version that was just tested by the e2e gate. - npm pkg set "dependencies.@github/copilot=$RUNTIME_VERSION" - echo "Pinned @github/copilot to $(npm pkg get dependencies.@github/copilot)" - - - name: Build SDK - run: npm run build - - - name: Azure Login (OIDC -> id-cpd-ci) - uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0 - with: - client-id: "${{ vars.CPD_ID_CLIENT_ID }}" # id-cpd-ci - tenant-id: "${{ vars.CPD_ID_TENANT_ID }}" - allow-no-subscriptions: true - - # Auth-only .npmrc: just the two token lines, NO scoped registry line. - # The publish target is supplied explicitly via publishConfig + --registry. - - name: Configure feed auth (.npmrc) - run: | - set -euo pipefail - TOKEN="$(az account get-access-token --resource "$ADO_RESOURCE" --query accessToken -o tsv)" - echo "::add-mask::$TOKEN" - # Derive the protocol-relative auth scopes from FEED_URL (single source - # of truth). NO scoped @github:registry line here — publish target is - # supplied explicitly via publishConfig + --registry. - FEED_AUTH_REGISTRY="${FEED_URL#https:}" - FEED_AUTH_BASE="${FEED_AUTH_REGISTRY%registry/}" - printf '%s\n' \ - "${FEED_AUTH_REGISTRY}:_authToken=${TOKEN}" \ - "${FEED_AUTH_BASE}:_authToken=${TOKEN}" > .npmrc - echo "Wrote auth-only .npmrc to ./nodejs" - - # Belt and suspenders (2 of 3): pin the publish target in the package too. - - name: Set publishConfig registry - run: npm pkg set "publishConfig.registry=$FEED_URL" - - # Belt and suspenders (3 of 3): fail loudly unless the effective publish - # target is the internal feed. Guards against ever reaching public npm. - - name: Assert publish target is the internal feed - run: | - set -euo pipefail - EFFECTIVE="$(npm pkg get publishConfig.registry | tr -d '"')" - echo "Effective publishConfig.registry: $EFFECTIVE" - if [ "$EFFECTIVE" != "$FEED_URL" ]; then - echo "::error::publishConfig.registry ('$EFFECTIVE') is not the internal feed ('$FEED_URL'). Refusing to publish." - exit 1 - fi - - - name: Publish SDK canary to internal feed - run: npm publish --registry "$FEED_URL" - - - name: Summarize published canary - env: - SDK_VERSION: ${{ steps.sdkver.outputs.SDK_VERSION }} - run: | - set -euo pipefail - { - echo "## SDK canary published" - echo "" - echo "| | |" - echo "| --- | --- |" - echo "| Runtime consumed | \`@github/copilot@${RUNTIME_VERSION}\` |" - echo "| Canary SDK produced | \`@github/copilot-sdk@${SDK_VERSION}\` |" - echo "| Feed | ${FEED_URL} |" - } >> "$GITHUB_STEP_SUMMARY" +name: "SDK Canary Test/Publish" + +# Nightly-style canary pipeline. First installs an explicit version of the +# @github/copilot runtime, builds the Node SDK, and runs the Node e2e suite +# against it to prove runtime <-> SDK compatibility. When that gate passes (and +# mode allows), publishes an SDK canary pinned to the tested runtime to the +# internal Azure Artifacts feed only (never public npm). + +env: + HUSKY: 0 + # Internal org-scoped Azure Artifacts feed — single source of truth so the + # feed name isn't repeated across steps. The SDK canary publishes here and + # (when runtime_source=internal) installs the runtime from here; it must NEVER + # reach public npm (@github/copilot-sdk is a live public package). + FEED_URL: https://pkgs.dev.azure.com/devdiv/_packaging/copilot-canary/npm/registry/ + # Azure DevOps resource ID used to mint an ADO access token for the feed. + ADO_RESOURCE: 499b84ac-1321-427f-aa17-267ca6975798 + +on: + workflow_dispatch: + inputs: + runtime_version: + description: "Exact github/copilot-cli release (public) or @github/copilot package version (internal)" + required: true + type: string + runtime_source: + description: "Where to install the runtime from" + required: true + type: choice + options: + - public + - internal + default: public + mode: + description: "publish (tests must pass), publish-force (publish even if tests fail), or tests-only (run gate, never publish)" + required: false + type: choice + default: publish + options: + - publish + - publish-force + - tests-only + repository_dispatch: + types: [runtime-canary] + +permissions: + contents: read + id-token: write + +# Serialize runs per ref so two overlapping canary runs can't race the feed +# publish. cancel-in-progress: false — never kill an in-flight publish. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + resolve: + name: "Resolve runtime inputs" + if: github.event.repository.fork == false + runs-on: ubuntu-latest + permissions: {} + outputs: + RUNTIME_VERSION: ${{ steps.normalize.outputs.RUNTIME_VERSION }} + RUNTIME_SOURCE: ${{ steps.normalize.outputs.RUNTIME_SOURCE }} + PUBLISH_MODE: ${{ steps.normalize.outputs.PUBLISH_MODE }} + steps: + # Normalize whichever trigger fired into a single (RUNTIME_VERSION, + # RUNTIME_SOURCE, PUBLISH_MODE) triple that every downstream step + # references. workflow_dispatch reads the human-supplied inputs; + # repository_dispatch reads client_payload and forces source=internal + # (a runtime canary only exists on the feed), defaulting mode to publish. + - name: Normalize inputs + id: normalize + env: + EVENT_NAME: ${{ github.event_name }} + INPUT_VERSION: ${{ inputs.runtime_version }} + INPUT_SOURCE: ${{ inputs.runtime_source }} + INPUT_MODE: ${{ inputs.mode }} + PAYLOAD_VERSION: ${{ github.event.client_payload.runtime_version }} + PAYLOAD_SOURCE: ${{ github.event.client_payload.runtime_source }} + PAYLOAD_MODE: ${{ github.event.client_payload.mode }} + run: | + set -euo pipefail + case "$EVENT_NAME" in + workflow_dispatch) + VERSION="$INPUT_VERSION" + SOURCE="$INPUT_SOURCE" + MODE="$INPUT_MODE" + ;; + repository_dispatch) + VERSION="$PAYLOAD_VERSION" + # A runtime canary only ever exists on the internal feed. + SOURCE="${PAYLOAD_SOURCE:-internal}" + MODE="${PAYLOAD_MODE:-publish}" + ;; + *) + echo "::error::Unsupported event '$EVENT_NAME'." + exit 1 + ;; + esac + if [ -z "$VERSION" ]; then echo "::error::Could not determine runtime version."; exit 1; fi + if [ -z "$SOURCE" ]; then SOURCE="public"; fi + case "$SOURCE" in + public|internal) ;; + *) echo "::error::Invalid runtime source '$SOURCE'. Expected one of: public, internal."; exit 1 ;; + esac + if [ -z "$MODE" ]; then MODE="publish"; fi + case "$MODE" in + publish|publish-force|tests-only) ;; + *) echo "::error::Invalid publish mode '$MODE'. Expected one of: publish, publish-force, tests-only."; exit 1 ;; + esac + echo "Resolved RUNTIME_VERSION=$VERSION RUNTIME_SOURCE=$SOURCE PUBLISH_MODE=$MODE" + echo "RUNTIME_VERSION=$VERSION" >> "$GITHUB_OUTPUT" + echo "RUNTIME_SOURCE=$SOURCE" >> "$GITHUB_OUTPUT" + echo "PUBLISH_MODE=$MODE" >> "$GITHUB_OUTPUT" + + - name: Validate runtime version (semver) + env: + RUNTIME_VERSION: ${{ steps.normalize.outputs.RUNTIME_VERSION }} + run: | + if [[ ! "$RUNTIME_VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$ ]]; then + echo "::error::Invalid runtime version '$RUNTIME_VERSION'. Expected semver (e.g. 1.0.69 or 1.0.70-canary.abc123)." + exit 1 + fi + + test: + name: "E2E tests (${{ matrix.os }})" + needs: resolve + if: github.event.repository.fork == false + environment: cicd + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + env: + POWERSHELL_UPDATECHECK: Off + RUNTIME_VERSION: ${{ needs.resolve.outputs.RUNTIME_VERSION }} + RUNTIME_SOURCE: ${{ needs.resolve.outputs.RUNTIME_SOURCE }} + defaults: + run: + shell: bash + working-directory: ./nodejs + steps: + - uses: actions/checkout@v6.0.2 + + - uses: actions/setup-node@v6 + with: + cache: "npm" + cache-dependency-path: "./nodejs/package-lock.json" + node-version: 22 + + - name: Install SDK dependencies + run: npm ci --ignore-scripts + + - name: Install test harness dependencies + working-directory: ./test/harness + run: npm ci --ignore-scripts + + - name: Azure Login (OIDC -> id-cpd-ci) + if: env.RUNTIME_SOURCE == 'internal' + uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0 + with: + client-id: "${{ vars.CPD_ID_CLIENT_ID }}" # id-cpd-ci + tenant-id: "${{ vars.CPD_ID_TENANT_ID }}" + allow-no-subscriptions: true + + # Route ONLY @github/* (the runtime + its platform packages) to the + # internal feed via a scoped registry. All other deps (e.g. detect-libc) + # still resolve from public npm. A global --registry would break because + # detect-libc is not on the feed. + - name: Configure canary feed (.npmrc) + if: env.RUNTIME_SOURCE == 'internal' + run: | + set -euo pipefail + TOKEN="$(az account get-access-token --resource "$ADO_RESOURCE" --query accessToken -o tsv)" + echo "::add-mask::$TOKEN" + # Derive the protocol-relative auth scopes from FEED_URL so the feed + # name lives in exactly one place (the workflow-level env). + FEED_AUTH_REGISTRY="${FEED_URL#https:}" + FEED_AUTH_BASE="${FEED_AUTH_REGISTRY%registry/}" + NPMRC="$(printf '%s\n' \ + "@github:registry=${FEED_URL}" \ + "${FEED_AUTH_REGISTRY}:_authToken=${TOKEN}" \ + "${FEED_AUTH_BASE}:_authToken=${TOKEN}")" + printf '%s\n' "$NPMRC" > .npmrc + echo "Wrote scoped @github registry .npmrc to ./nodejs" + + - name: Override runtime version + run: | + set -euo pipefail + if [ "$RUNTIME_SOURCE" = "internal" ]; then + echo "Installing internal @github/copilot@${RUNTIME_VERSION}" + npm install "@github/copilot@${RUNTIME_VERSION}" --save-exact --ignore-scripts + node scripts/set-cli-version.js "$RUNTIME_VERSION" --npm-package + else + echo "Pinning github/copilot-cli release ${RUNTIME_VERSION}" + node scripts/set-cli-version.js "$RUNTIME_VERSION" + npm install --ignore-scripts + fi + + - name: Verify release runtime + run: | + set -euo pipefail + runtime_path=$(npm run --silent prepare:runtime -- --print-path) + node -e " + const fs = require('node:fs'); + const path = require('node:path'); + const runtime = process.argv[1]; + const runtimeStat = fs.statSync(runtime); + if (!runtimeStat.isFile()) throw new Error('Runtime wrapper is not a file'); + if (process.platform !== 'win32' && (runtimeStat.mode & 0o111) === 0) { + throw new Error('Runtime wrapper is not executable'); + } + if (!fs.statSync(path.join(path.dirname(runtime), 'runtime.node')).isFile()) { + throw new Error('runtime.node is not adjacent to the runtime wrapper'); + } + " "$runtime_path" + legacy_path=$(npm run --silent prepare:runtime -- --print-legacy-path) + node "$legacy_path" --version | grep -F "$RUNTIME_VERSION" + echo "COPILOT_CLI_PATH=$runtime_path" >> "$GITHUB_ENV" + + - name: Build SDK + run: npm run build + + - name: Warm up PowerShell + if: runner.os == 'Windows' + run: pwsh.exe -Command "Write-Host 'PowerShell ready'" + + - name: Run Node.js SDK e2e tests + env: + COPILOT_HMAC_KEY: ${{ secrets.COPILOT_DEVELOPER_CLI_INTEGRATION_HMAC_KEY }} + run: npm test + + publish: + name: "Publish SDK canary (internal feed)" + needs: [resolve, test] + # Publish runs only when the gate permits it. Mode governs behavior: + # - tests-only: never publish (skips this job entirely). + # - publish: publish only when the e2e gate is green (the default for both + # the human and automated triggers). + # - publish-force: publish even on a non-green gate — a human-acknowledged + # flake override, audited via the ::warning:: step below and the run actor. + # publish-force only skips the e2e *signal* — the publish job still runs the + # build (so a broken build can't publish) and enforces the feed-only guards. + if: > + !cancelled() && + github.event.repository.fork == false && + needs.resolve.result == 'success' && + needs.resolve.outputs.PUBLISH_MODE != 'tests-only' && + (needs.test.result == 'success' || + needs.resolve.outputs.PUBLISH_MODE == 'publish-force') + environment: cicd + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + env: + RUNTIME_VERSION: ${{ needs.resolve.outputs.RUNTIME_VERSION }} + RUNTIME_SOURCE: ${{ needs.resolve.outputs.RUNTIME_SOURCE }} + defaults: + run: + shell: bash + working-directory: ./nodejs + steps: + - name: Warn — publishing despite failed e2e gate (publish-force) + # always() so this audit is never skipped by prior-step status; it fires + # specifically when publish proceeded on a non-green gate via publish-force. + # Runs at the workspace root because it executes before checkout, so the + # job's default working-directory (./nodejs) does not exist yet. + if: always() && needs.test.result != 'success' && needs.resolve.outputs.PUBLISH_MODE == 'publish-force' + working-directory: ${{ github.workspace }} + run: | + echo "::warning title=e2e gate bypassed::Publishing SDK canary despite a non-passing e2e gate (test job result: ${{ needs.test.result }}) via publish-force. Triggered by '${{ github.actor }}' through '${{ github.event_name }}'. The e2e signal was bypassed; build + feed-only guards still apply." + + - uses: actions/checkout@v6.0.2 + + - uses: actions/setup-node@v6 + with: + node-version: 22 + + # Default public registry: installs build deps and the currently pinned + # runtime. Do NOT write any feed .npmrc or scoped @github:registry line + # here, or npm ci would try to fetch the runtime from the upstream-less + # feed and 404. + - name: Install SDK dependencies + run: npm ci --ignore-scripts + + - name: Compute SDK canary version + id: sdkver + env: + RUN_NUMBER: ${{ github.run_number }} + SHA: ${{ github.sha }} + run: | + set -euo pipefail + SHORT_SHA="${SHA:0:7}" + # Base the canary on the NEXT patch of the public SDK latest so canaries + # correlate with public releases: they sort ABOVE the current public + # latest and BELOW the eventual real release of that next patch (a + # prerelease of X.Y.Z always sorts below X.Y.Z), so a canary can never + # shadow the real release when it ships. + # Reuse the repo's own version helper (scripts/get-version.js) so this + # stays consistent with publish.yml: `current` returns the latest public + # dist-tag version, read-only from public npm (never the feed), then + # we bump the patch ourselves to keep strict patch+1 semantics. + PUBLIC_LATEST="$(node scripts/get-version.js current || true)" + BASE="${PUBLIC_LATEST%%-*}"; BASE="${BASE%%+*}" + if [[ "$BASE" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then + NEXT="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.$(( BASH_REMATCH[3] + 1 ))" + else + echo "::error::Could not resolve public SDK latest version (got '$PUBLIC_LATEST'); refusing to publish a canary with an unknown base." + exit 1 + fi + SDK_VERSION="${NEXT}-canary.${RUN_NUMBER}.g${SHORT_SHA}" + if [[ ! "$SDK_VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$ ]]; then + echo "::error::Computed SDK canary version '$SDK_VERSION' is not valid semver." + exit 1 + fi + echo "SDK canary version: $SDK_VERSION" + echo "SDK_VERSION=$SDK_VERSION" >> "$GITHUB_OUTPUT" + + - name: Set package and runtime versions + env: + SDK_VERSION: ${{ steps.sdkver.outputs.SDK_VERSION }} + run: | + set -euo pipefail + npm version "$SDK_VERSION" --no-git-tag-version --allow-same-version + if [ "$RUNTIME_SOURCE" = "internal" ]; then + npm pkg set "dependencies.@github/copilot=$RUNTIME_VERSION" + node scripts/set-cli-version.js "$RUNTIME_VERSION" --npm-package + else + node scripts/set-cli-version.js "$RUNTIME_VERSION" + fi + echo "Pinned github/copilot-cli release to $(npm pkg get copilotCliVersion)" + + - name: Build SDK + run: npm run build + + - name: Package public release runtimes + if: env.RUNTIME_SOURCE == 'public' + run: npm run pack:release + + - name: Azure Login (OIDC -> id-cpd-ci) + uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0 + with: + client-id: "${{ vars.CPD_ID_CLIENT_ID }}" # id-cpd-ci + tenant-id: "${{ vars.CPD_ID_TENANT_ID }}" + allow-no-subscriptions: true + + # Auth-only .npmrc: just the two token lines, NO scoped registry line. + # The publish target is supplied explicitly via publishConfig + --registry. + - name: Configure feed auth (.npmrc) + run: | + set -euo pipefail + TOKEN="$(az account get-access-token --resource "$ADO_RESOURCE" --query accessToken -o tsv)" + echo "::add-mask::$TOKEN" + # Derive the protocol-relative auth scopes from FEED_URL (single source + # of truth). NO scoped @github:registry line here — publish target is + # supplied explicitly via publishConfig + --registry. + FEED_AUTH_REGISTRY="${FEED_URL#https:}" + FEED_AUTH_BASE="${FEED_AUTH_REGISTRY%registry/}" + printf '%s\n' \ + "${FEED_AUTH_REGISTRY}:_authToken=${TOKEN}" \ + "${FEED_AUTH_BASE}:_authToken=${TOKEN}" > .npmrc + echo "Wrote auth-only .npmrc to ./nodejs" + + # Belt and suspenders (2 of 3): pin the publish target in the package too. + - name: Set publishConfig registry + run: npm pkg set "publishConfig.registry=$FEED_URL" + + # Belt and suspenders (3 of 3): fail loudly unless the effective publish + # target is the internal feed. Guards against ever reaching public npm. + - name: Assert publish target is the internal feed + run: | + set -euo pipefail + EFFECTIVE="$(npm pkg get publishConfig.registry | tr -d '"')" + echo "Effective publishConfig.registry: $EFFECTIVE" + if [ "$EFFECTIVE" != "$FEED_URL" ]; then + echo "::error::publishConfig.registry ('$EFFECTIVE') is not the internal feed ('$FEED_URL'). Refusing to publish." + exit 1 + fi + + - name: Publish SDK canary to internal feed + run: | + set -euo pipefail + if [ "$RUNTIME_SOURCE" = "internal" ]; then + node scripts/npm-release.js publish . canary "$FEED_URL" azure + exit + fi + shopt -s nullglob + TARBALLS=(./github-copilot-sdk-*.tgz) + if [ "${#TARBALLS[@]}" -ne 9 ]; then + echo "::error::Expected nine Node.js package tarballs, found ${#TARBALLS[@]}." + exit 1 + fi + MAIN_TARBALL="" + for TARBALL in "${TARBALLS[@]}"; do + PACKAGE_NAME="$(tar -xOf "$TARBALL" package/package.json | jq -r .name)" + if [ "$PACKAGE_NAME" = "@github/copilot-sdk" ]; then + MAIN_TARBALL="$TARBALL" + else + node scripts/npm-release.js publish "$TARBALL" canary "$FEED_URL" azure + fi + done + if [ -z "$MAIN_TARBALL" ]; then + echo "::error::Main @github/copilot-sdk tarball not found." + exit 1 + fi + node scripts/npm-release.js publish "$MAIN_TARBALL" canary "$FEED_URL" azure + + - name: Summarize published canary + env: + SDK_VERSION: ${{ steps.sdkver.outputs.SDK_VERSION }} + run: | + set -euo pipefail + { + echo "## SDK canary published" + echo "" + echo "| | |" + echo "| --- | --- |" + if [ "$RUNTIME_SOURCE" = "public" ]; then + echo "| Runtime consumed | \`github/copilot-cli@${RUNTIME_VERSION}\` release assets |" + else + echo "| Runtime consumed | \`@github/copilot@${RUNTIME_VERSION}\` |" + fi + echo "| Canary SDK produced | \`@github/copilot-sdk@${SDK_VERSION}\` |" + echo "| Feed | ${FEED_URL} |" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/sdk-consistency-review.lock.yml b/.github/workflows/sdk-consistency-review.lock.yml index 3c1756644d..4480d40559 100644 --- a/.github/workflows/sdk-consistency-review.lock.yml +++ b/.github/workflows/sdk-consistency-review.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"fb73d13f101fc375308576a64180f63934cc9e8306cb6ef6303f1b9788d9df28","body_hash":"478a74e14ae52b64cd38c57a44953fad4d8003a2414912a19de8f0f4a354a92f","compiler_version":"v0.83.1","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"79b4a25722897db4bbdb7fa862f579bd3c37e12d0ce147e46d618ab79bce67c4","body_hash":"cc60c817de34cdb662ae4c091203c67a5ef240ca0165b0cd26a842f03b22614f","compiler_version":"v0.83.1","strict":true,"agent_id":"copilot","agent_model":"${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-5' }}","detection_agent_id":"copilot","detection_agent_model":"${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-5' }}","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 # @@ -134,7 +134,7 @@ jobs: env: 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_MODEL: "${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-5' }}" GH_AW_INFO_VERSION: "1.0.73" GH_AW_INFO_AGENT_VERSION: "1.0.73" GH_AW_INFO_CLI_VERSION: "v0.83.1" @@ -863,10 +863,11 @@ jobs: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ github.token }} - COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-5' }} GH_AW_LLM_PROVIDER: github GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} + GH_AW_MODEL_FALLBACK: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} @@ -1467,10 +1468,11 @@ jobs: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ github.token }} - COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-5' }} GH_AW_LLM_PROVIDER: github GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }} GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} + GH_AW_MODEL_FALLBACK: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_TIMEOUT_MINUTES: 20 @@ -1564,7 +1566,7 @@ jobs: GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} 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_MODEL: "${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-5' }}" 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 }} diff --git a/.github/workflows/sdk-consistency-review.md b/.github/workflows/sdk-consistency-review.md index 8a4ff106a0..4d1c7e4511 100644 --- a/.github/workflows/sdk-consistency-review.md +++ b/.github/workflows/sdk-consistency-review.md @@ -1,6 +1,7 @@ --- description: Reviews PRs to ensure features are implemented consistently across all SDK language implementations tracker-id: sdk-consistency-review +model: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-5' }} on: roles: all pull_request: @@ -35,6 +36,10 @@ safe-outputs: max: 1 hide-older-comments: true allowed-reasons: [outdated] + threat-detection: + engine: + id: copilot + model: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-5' }} timeout-minutes: 15 --- @@ -74,7 +79,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 b1a668a7cb..2870ad27f7 100644 --- a/.github/workflows/update-copilot-dependency.yml +++ b/.github/workflows/update-copilot-dependency.yml @@ -1,10 +1,10 @@ -name: "Update @github/copilot Dependency" +name: "Update Copilot CLI Version" on: workflow_dispatch: inputs: version: - description: "Target version of @github/copilot (e.g. 0.0.420)" + description: "Target github/copilot-cli release version (e.g. 1.0.83-0)" required: true type: string @@ -14,7 +14,7 @@ permissions: jobs: update: - name: "Update @github/copilot to ${{ inputs.version }}" + name: "Update Copilot CLI to ${{ inputs.version }}" runs-on: ubuntu-latest steps: - name: Validate version input @@ -56,17 +56,13 @@ jobs: toolchain: nightly-2026-04-14 components: rustfmt - - name: Update @github/copilot in nodejs + - name: Update the Node.js CLI release pin env: VERSION: ${{ inputs.version }} working-directory: ./nodejs - run: npm install "@github/copilot@$VERSION" - - - name: Update @github/copilot in test harness - env: - VERSION: ${{ inputs.version }} - working-directory: ./test/harness - run: npm install "@github/copilot@$VERSION" + run: | + node scripts/set-cli-version.js "$VERSION" + npm install --ignore-scripts - name: Refresh nodejs/samples lockfile working-directory: ./nodejs/samples @@ -165,19 +161,20 @@ jobs: exit 0 fi - git commit -m "Update @github/copilot to $VERSION + git commit -m "Update Copilot CLI to $VERSION - - Updated nodejs and test harness dependencies + - Updated the Node.js CLI release pin - Re-ran code generators - Formatted generated code" git push origin "$BRANCH" --force-with-lease PR_BODY=$(cat <<'BODY_EOF' - Automated update of `@github/copilot` to version `PLACEHOLDER_VERSION`. + Automated update of the Copilot CLI release to version `PLACEHOLDER_VERSION`. ### Changes - - Updated `@github/copilot` in `nodejs/package.json` and `test/harness/package.json` + - Updated the release pin in `nodejs/package.json` + - Validated the release assets listed in `SHA256SUMS.txt` - Re-ran all code generators (`scripts/codegen`) - Formatted generated output - Updated Java codegen dependency, POM property, and regenerated Java types @@ -192,11 +189,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` @@ -208,7 +205,7 @@ jobs: ### Next steps When ready, click **Ready for review** to trigger CI checks. - > Created by the **Update @github/copilot Dependency** workflow. + > Created by the **Update Copilot CLI Version** workflow. BODY_EOF ) PR_BODY="${PR_BODY//PLACEHOLDER_VERSION/$VERSION}" diff --git a/.gitignore b/.gitignore index 4aff9be11d..c1e9833769 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/CHANGELOG.md b/CHANGELOG.md index 41bf05e171..f7e753beda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,80 @@ 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: declare application identity with client info + +Client options now accept optional client info (application name and version, integration name and version) across all six SDKs, exposed idiomatically per language (`clientInfo` in Node.js, `client_info` in Python and Rust, `ClientInfo` in Go and .NET, `setClientInfo` in Java). When set, the SDK forwards it on the `server.connect` handshake so the telemetry the runtime emits on the connection is attributed to the application and its Copilot integration instead of the runtime's own build. All fields are optional, and leaving client info unset keeps the runtime's default attribution. See [Client info](./docs/features/client-info.md). + +### Feature: Node Agent Factories pagination and run notifications + +The experimental Node.js Agent Factories convenience API now supports paginated run history. Existing `session.factory.listRuns()` calls still return the runs array, while calls with `afterSeq`, `beforeSeq`, or `limit` return the full page with cursor and truncation metadata. + +Factory `run` and `resume` options now accept `notifyOnComplete` and `logPhaseNames`. The SDK forwards these options to the Copilot CLI for new and resumed runs. + +### Feature: selectable `ask_user` session behavior + +Session create and cold resume now accept a language-specific `askUserVariant` option with `legacy` and `elicitation` values. SDK sessions retain the legacy question-and-answer tool by default. Select `elicitation` and provide an elicitation handler to expose the structured form-based `ask_user` tool. + +### Feature: rotating session-scoped GitHub credentials + +All six SDKs can now acquire short-lived GitHub credentials through a session-scoped callback. The SDK registers the callback before session create or resume, maps `initial` and `refresh` requests to the owning session, and removes registrations on rollback, replacement, session close, and client close. Static per-session `gitHubToken` credentials remain supported and are mutually exclusive with the callback. + +Token responses use the shared tagged token/cancelled shape and require `expiresIn`, expressed as the positive number of seconds remaining when the callback completes. See [github/copilot-agent-runtime#16381](https://github.com/github/copilot-agent-runtime/pull/16381) for the runtime credential-authority implementation. + +Initial acquisition occurs during create or resume; cancellation, callback errors, and invalid credentials reject that operation instead of falling back to ambient authentication. Idle sessions refresh only before their next credential-consuming operation. + +### Feature: extensions can request sensitive environment variables + +Copilot CLI extensions can now ask for named sensitive environment variables when they join a session. `joinSession()` accepts a `requestedEnvironmentVariables` option listing the variable names the extension needs. The CLI shows a permission prompt naming the extension and the exact variables requested. On approval, only those variables reach that extension and their values are written into the extension process's `process.env` before `joinSession()` resolves. On denial, `joinSession()` rejects, the extension does not load, and its tools never reach the model. + +An approval is remembered against the exact set of names the user saw, so an extension that later asks for one more variable prompts again. Names that are unset, or that the CLI does not filter from extensions, are not prompted for. This is the client half of the feature; it requires a Copilot CLI that supports extension environment access, and older CLIs ignore the request and grant nothing. + +```ts +import { joinSession } from "@github/copilot-sdk/extension"; + +const session = await joinSession({ + requestedEnvironmentVariables: ["GITHUB_TOKEN"], +}); +const token = process.env.GITHUB_TOKEN; +``` + +### 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/docs/auth/authenticate.md b/docs/auth/authenticate.md index d54c954517..a7582b5510 100644 --- a/docs/auth/authenticate.md +++ b/docs/auth/authenticate.md @@ -7,7 +7,7 @@ The GitHub Copilot SDK supports multiple authentication methods to fit different | Method | Use Case | Copilot Subscription Required | |--------|----------|-------------------------------| | [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 | +| [GitHub OAuth App](#github-oauth-app) | Apps acting on behalf of users via OAuth | Yes | | [Environment Variables](#environment-variables) | CI/CD, automation, server-to-server | Yes | | [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 | @@ -24,26 +24,13 @@ This is the default authentication method when running the Copilot CLI interacti **SDK Configuration:**
-Node.js / TypeScript +.NET -```typescript -import { CopilotClient } from "@github/copilot-sdk"; +```csharp +using GitHub.Copilot; // Default: uses logged-in user credentials -const client = new CopilotClient(); -``` - -
- -
-Python - -```python -from copilot import CopilotClient - -# Default: uses logged-in user credentials -client = CopilotClient() -await client.start() +await using CopilotClient client = new(); ```
@@ -75,26 +62,51 @@ client := copilot.NewClient(nil)
-.NET +Java -```csharp -using GitHub.Copilot; +```java +import com.github.copilot.CopilotClient; // Default: uses logged-in user credentials -await using var client = new CopilotClient(); +var client = new CopilotClient(); +client.start().get(); ```
-Java +Python -```java -import com.github.copilot.CopilotClient; +```python +from copilot import CopilotClient + +# Default: uses logged-in user credentials +client = CopilotClient() +await client.start() +``` + +
+ +
+Rust + +```rust +use github_copilot_sdk::{Client, ClientOptions}; // Default: uses logged-in user credentials -var client = new CopilotClient(); -client.start().get(); +let client = Client::start(ClientOptions::default()).await?; +``` + +
+ +
+TypeScript + +```typescript +import { CopilotClient } from "@github/copilot-sdk"; + +// Default: uses logged-in user credentials +const client = new CopilotClient(); ```
@@ -104,42 +116,41 @@ client.start().get(); * Development and testing environments * Any scenario where a user can sign in interactively -## OAuth GitHub App +## GitHub OAuth App Use an OAuth GitHub App to authenticate users through your application and pass their credentials to the SDK. This enables your application to make Copilot API requests on behalf of users who authorize your app. **How it works:** 1. User authorizes your OAuth GitHub App 1. Your app receives a user access token (`gho_` or `ghu_` prefix) -1. Pass the token to the SDK via `gitHubToken` option +1. Pass the token to the SDK through its client configuration **SDK Configuration:**
-Node.js / TypeScript +.NET -```typescript -import { CopilotClient } from "@github/copilot-sdk"; + +```csharp +using GitHub.Copilot; -const client = new CopilotClient({ - gitHubToken: userAccessToken, // Token from OAuth flow - useLoggedInUser: false, // Don't use stored CLI credentials +var userAccessToken = "token"; +await using CopilotClient client = new(new CopilotClientOptions +{ + GitHubToken = userAccessToken, + UseLoggedInUser = false, }); ``` + -
- -
-Python - -```python -from copilot import CopilotClient +```csharp +using GitHub.Copilot; -client = CopilotClient({ - "github_token": user_access_token, # Token from OAuth flow - "use_logged_in_user": False, # Don't use stored CLI credentials -}) -await client.start() +await using var client = new CopilotClient(new CopilotClientOptions +{ + GitHubToken = userAccessToken, // Token from OAuth flow + UseLoggedInUser = false, // Don't use stored CLI credentials +}); ```
@@ -168,41 +179,13 @@ func main() { import copilot "github.com/github/copilot-sdk/go" client := copilot.NewClient(&copilot.ClientOptions{ - GitHubToken: userAccessToken, // Token from OAuth flow - UseLoggedInUser: copilot.Bool(false), // Don't use stored CLI credentials + GitHubToken: userAccessToken, // Token from OAuth flow + UseLoggedInUser: copilot.Bool(false), // Don't use stored CLI credentials }) ``` -
-.NET - - -```csharp -using GitHub.Copilot; - -var userAccessToken = "token"; -await using var client = new CopilotClient(new CopilotClientOptions -{ - GitHubToken = userAccessToken, - UseLoggedInUser = false, -}); -``` - - -```csharp -using GitHub.Copilot; - -await using var client = new CopilotClient(new CopilotClientOptions -{ - GitHubToken = userAccessToken, // Token from OAuth flow - UseLoggedInUser = false, // Don't use stored CLI credentials -}); -``` - -
-
Java @@ -220,6 +203,50 @@ client.start().get();
+
+Python + +```python +from copilot import CopilotClient + +client = CopilotClient({ + "github_token": user_access_token, # Token from OAuth flow + "use_logged_in_user": False, # Don't use stored CLI credentials +}) +await client.start() +``` + +
+ +
+Rust + +```rust +use github_copilot_sdk::{Client, ClientOptions}; + +let client = Client::start( + ClientOptions::default() + .with_github_token(user_access_token) + .with_use_logged_in_user(false), +).await?; +``` + +
+ +
+TypeScript + +```typescript +import { CopilotClient } from "@github/copilot-sdk"; + +const client = new CopilotClient({ + gitHubToken: userAccessToken, // Token from OAuth flow + useLoggedInUser: false, // Don't use stored CLI credentials +}); +``` + +
+ **Supported token types:** * `gho_` - OAuth user access tokens * `ghu_` - GitHub App user access tokens @@ -233,6 +260,133 @@ client.start().get(); * SaaS applications building on top of Copilot * Any multi-user application where you need to make requests on behalf of different users +For more information, see [GitHub OAuth](../setup/github-oauth.md). + +## Rotating session-scoped GitHub tokens + +For multi-user services and integrations, set a token provider on each session instead of storing one long-lived token. The runtime calls the provider for the effective GitHub host and identifies the request as `initial` or `refresh`. The session ID is absent only when a cloud session has not received its ID yet. + +Return a tagged token result or an explicit cancellation. Every token result must include `expiresIn`: the positive number of seconds remaining when the callback completes. Production GitHub tokens typically last eight hours, so `8 * 60 * 60` is a common value. Do not set both the static per-session token and the provider. + +
+TypeScript + + +```typescript +const session = await client.createSession({ + gitHubTokenProvider: async ({ host, sessionId, reason }) => { + const token = await acquireGitHubToken({ host, sessionId, reason }); + return { + kind: "token", + accessToken: token.value, + expiresIn: token.secondsRemaining, + }; + }, +}); +``` + +
+
+Python + + +```python +async def provide_github_token(args): + token = await acquire_github_token( + host=args["host"], + session_id=args["session_id"], + reason=args["reason"], + ) + return { + "kind": "token", + "accessToken": token.value, + "expiresIn": token.seconds_remaining, + } + + +session = await client.create_session(github_token_provider=provide_github_token) +``` + +
+
+Go + + +```go +session, err := client.CreateSession(ctx, &copilot.SessionConfig{ + GitHubTokenProvider: func(args copilot.GitHubTokenProviderArgs) (*copilot.GitHubTokenProviderResult, error) { + token, secondsRemaining, err := acquireGitHubToken(args.Host, args.SessionID, args.Reason) + if err != nil { + return nil, err + } + return copilot.GitHubTokenResult(&copilot.GitHubToken{ + AccessToken: token, + ExpiresIn: secondsRemaining, + }), nil + }, +}) +``` + +
+
+.NET + + +```csharp +await using var session = await client.CreateSessionAsync(new SessionConfig +{ + GitHubTokenProvider = async args => + { + var token = await AcquireGitHubTokenAsync(args.Host, args.SessionId, args.Reason); + return GitHubTokenProviderResult.FromToken(new GitHubToken + { + AccessToken = token.Value, + ExpiresIn = token.SecondsRemaining, + }); + }, +}); +``` + +
+
+Java + + +```java +var session = client.createSession(new SessionConfig() + .setGitHubTokenProvider(args -> + acquireGitHubToken(args.host(), args.sessionId(), args.reason()) + .thenApply(token -> GitHubTokenProviderResult.token( + token.value(), token.secondsRemaining()))) + .setOnPermissionRequest(PermissionHandler.APPROVE_ALL) +).get(); +``` + +
+
+Rust + + +```rust +let provider = Arc::new(|args: GitHubTokenProviderArgs| async move { + let token = acquire_github_token(&args.host, args.session_id.as_ref(), args.reason).await?; + Ok(GitHubTokenProviderResult::Token(GitHubToken::new( + token.value, + token.seconds_remaining, + ))) +}); + +let session = client + .create_session(SessionConfig::default().with_github_token_provider(provider)) + .await?; +``` + +
+ +The runtime performs the `initial` acquisition as part of session creation or resume. A cancelled acquisition, provider error, invalid response, or token without a stable account identity rejects the create or resume operation. The runtime does not fall back to ambient authentication. + +After the session is established, the runtime performs async preflight before each credential-consuming operation. It requests a `refresh` when the current token has one hour or less remaining. Idle sessions are not refreshed until their next credential-consuming operation. The runtime does not use background timers, rejection-driven replay, 401/403 challenge propagation, or upscope for this callback. + ## Environment variables For automation, CI/CD pipelines, and server-to-server scenarios, you can authenticate using environment variables. @@ -253,13 +407,52 @@ For organization-attributed automation that should not use a user's personal acc No code changes needed—the SDK automatically detects environment variables:
-Node.js / TypeScript +.NET -```typescript -import { CopilotClient } from "@github/copilot-sdk"; +```csharp +using GitHub.Copilot; // Token is read from environment variable automatically -const client = new CopilotClient(); +await using CopilotClient client = new(); +``` + +
+ +
+Go + + +```go +package main + +import copilot "github.com/github/copilot-sdk/go" + +func main() { + // Token is read from environment variable automatically + client := copilot.NewClient(nil) + _ = client +} +``` + + +```go +import copilot "github.com/github/copilot-sdk/go" + +// Token is read from environment variable automatically +client := copilot.NewClient(nil) +``` + +
+ +
+Java + +```java +import com.github.copilot.CopilotClient; + +// Token is read from environment variable automatically +var client = new CopilotClient(); +client.start().get(); ```
@@ -277,6 +470,30 @@ await client.start() +
+Rust + +```rust +use github_copilot_sdk::{Client, ClientOptions}; + +// Token is read from environment variable automatically +let client = Client::start(ClientOptions::default()).await?; +``` + +
+ +
+TypeScript + +```typescript +import { CopilotClient } from "@github/copilot-sdk"; + +// Token is read from environment variable automatically +const client = new CopilotClient(); +``` + +
+ **When to use:** * CI/CD pipelines (GitHub Actions, Jenkins, and more) * Automated testing @@ -313,40 +530,20 @@ For multi-user server mode, pass a per-session `gitHubToken` so each session run ## Disabling auto-login -To prevent the SDK from automatically using stored credentials or `gh` CLI auth, use the `useLoggedInUser: false` option: +To prevent the SDK from automatically using stored credentials or `gh` CLI auth, configure it to disable logged-in-user fallback:
-Node.js / TypeScript +.NET -```typescript -const client = new CopilotClient({ - useLoggedInUser: false, // Only use explicit tokens +```csharp +await using var client = new CopilotClient(new CopilotClientOptions +{ + UseLoggedInUser = false, // Only use explicit tokens }); ```
-
-Python - - -```python -from copilot import CopilotClient - -client = CopilotClient({ - "use_logged_in_user": False, -}) -``` - - -```python -client = CopilotClient({ - "use_logged_in_user": False, # Only use explicit tokens -}) -``` - -
-
Go @@ -373,18 +570,6 @@ client := copilot.NewClient(&copilot.ClientOptions{
-
-.NET - -```csharp -await using var client = new CopilotClient(new CopilotClientOptions -{ - UseLoggedInUser = false, // Only use explicit tokens -}); -``` - -
-
Java @@ -400,6 +585,51 @@ client.start().get();
+
+Python + + +```python +from copilot import CopilotClient + +client = CopilotClient({ + "use_logged_in_user": False, +}) +``` + + +```python +client = CopilotClient({ + "use_logged_in_user": False, # Only use explicit tokens +}) +``` + +
+ +
+Rust + +```rust +use github_copilot_sdk::{Client, ClientOptions}; + +let client = Client::start( + ClientOptions::default().with_use_logged_in_user(false), +).await?; +``` + +
+ +
+TypeScript + +```typescript +const client = new CopilotClient({ + useLoggedInUser: false, // Only use explicit tokens +}); +``` + +
+ ## Next steps * [BYOK Documentation](./byok.md) - Learn how to use your own API keys diff --git a/docs/developer-docs/secrets.md b/docs/developer-docs/secrets.md index 15788bbe56..573f4f22e1 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 9f3b937c97..5ea070a5aa 100644 --- a/docs/features/README.md +++ b/docs/features/README.md @@ -20,7 +20,9 @@ These guides cover the capabilities you can add to your Copilot SDK application. | [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 | +| [Client info](./client-info.md) | Declare application and integration identity for runtime telemetry attribution | | [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/client-info.md b/docs/features/client-info.md new file mode 100644 index 0000000000..f72780e41b --- /dev/null +++ b/docs/features/client-info.md @@ -0,0 +1,241 @@ +# Client info + +Client info identifies the application using the Copilot SDK and, when applicable, a specific integration within it. An integration is an identifiable sub-part of the application through which the SDK is used, such as an extension or plugin. Set the optional `clientInfo` client option to attribute runtime telemetry for that connection to your application instead of the runtime's own build. + +## When to set client info + +Set client info when your SDK application represents a distinct product, service, or integration whose runtime activity should be attributed consistently. + +Leave client info unset for scripts, one-off tools, and jobs that do not represent a distinct application. The runtime then keeps its default attribution. + +Client info has four optional string fields. Set the fields you know and omit the rest. The SDK includes client info in the `server.connect` handshake only when at least one field has a non-empty value. + +| Field | Example | Meaning | +|---|---|---| +| `applicationName` | `"vscode"` | Name of the application using the SDK | +| `applicationVersion` | `"1.124.2"` | Version of the application using the SDK | +| `integrationName` | `"copilot-chat"` | Name of the extension, plugin, or other application sub-part using the SDK | +| `integrationVersion` | `"0.54.0"` | Version of that extension, plugin, or application sub-part | + +For a standalone application without a distinct integration, set only the application fields. For example, a developer portal could set `applicationName` to `"acme-developer-portal"` and `applicationVersion` to `"2.4.0"`, leaving both integration fields unset. + +The SDK sends client info once when it establishes the connection. The identity applies for the lifetime of that connection. + +## Configure client info + +Pass client info when you create the client: + +
+TypeScript + + +```typescript +import { CopilotClient } from "@github/copilot-sdk"; + +async function main() { + const client = new CopilotClient({ + clientInfo: { + applicationName: "vscode", + applicationVersion: "1.124.2", + integrationName: "copilot-chat", + integrationVersion: "0.54.0", + }, + }); + + await client.start(); +} + +main(); +``` + + +```typescript +import { CopilotClient } from "@github/copilot-sdk"; + +const client = new CopilotClient({ + clientInfo: { + applicationName: "vscode", + applicationVersion: "1.124.2", + integrationName: "copilot-chat", + integrationVersion: "0.54.0", + }, +}); + +await client.start(); +``` + +
+ +
+Python + + +```python +from copilot import CopilotClient + +client = CopilotClient( + client_info={ + "application_name": "vscode", + "application_version": "1.124.2", + "integration_name": "copilot-chat", + "integration_version": "0.54.0", + }, +) +await client.start() +``` + +
+ +
+Go + + +```go +package main + +import ( + "context" + + copilot "github.com/github/copilot-sdk/go" +) + +func main() { + ctx := context.Background() + client := copilot.NewClient(&copilot.ClientOptions{ + ClientInfo: &copilot.ClientInfo{ + ApplicationName: "vscode", + ApplicationVersion: "1.124.2", + IntegrationName: "copilot-chat", + IntegrationVersion: "0.54.0", + }, + }) + if err := client.Start(ctx); err != nil { + return + } +} +``` + + +```go +client := copilot.NewClient(&copilot.ClientOptions{ + ClientInfo: &copilot.ClientInfo{ + ApplicationName: "vscode", + ApplicationVersion: "1.124.2", + IntegrationName: "copilot-chat", + IntegrationVersion: "0.54.0", + }, +}) +if err := client.Start(ctx); err != nil { + return err +} +``` + +
+ +
+.NET + +```csharp +using GitHub.Copilot; + +await using var client = new CopilotClient(new CopilotClientOptions +{ + ClientInfo = new CopilotClientInfo + { + ApplicationName = "vscode", + ApplicationVersion = "1.124.2", + IntegrationName = "copilot-chat", + IntegrationVersion = "0.54.0", + }, +}); + +await client.StartAsync(); +``` + +
+ +
+Java + + +```java +import com.github.copilot.CopilotClient; +import com.github.copilot.rpc.ClientInfo; +import com.github.copilot.rpc.CopilotClientOptions; + +public class ClientInfoExample { + public static void main(String[] args) throws Exception { + var options = new CopilotClientOptions() + .setClientInfo(new ClientInfo() + .setApplicationName("vscode") + .setApplicationVersion("1.124.2") + .setIntegrationName("copilot-chat") + .setIntegrationVersion("0.54.0")); + + var client = new CopilotClient(options); + client.start().get(); + } +} +``` + + +```java +var options = new CopilotClientOptions() + .setClientInfo(new ClientInfo() + .setApplicationName("vscode") + .setApplicationVersion("1.124.2") + .setIntegrationName("copilot-chat") + .setIntegrationVersion("0.54.0")); + +var client = new CopilotClient(options); +client.start().get(); +``` + +
+ +
+Rust + + +```rust +use github_copilot_sdk::{Client, ClientInfo, ClientOptions}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let _client = Client::start( + ClientOptions::new().with_client_info( + ClientInfo::new() + .with_application_name("vscode") + .with_application_version("1.124.2") + .with_integration_name("copilot-chat") + .with_integration_version("0.54.0"), + ), + ) + .await?; + Ok(()) +} +``` + + +```rust +use github_copilot_sdk::{Client, ClientInfo, ClientOptions}; + +let client = Client::start( + ClientOptions::new().with_client_info( + ClientInfo::new() + .with_application_name("vscode") + .with_application_version("1.124.2") + .with_integration_name("copilot-chat") + .with_integration_version("0.54.0"), + ), +) +.await?; +``` + +
+ +## Notes + +* Client info is advisory. The runtime can ignore values that do not match the expected format, such as an invalid version string. +* Setting client info changes how the runtime attributes its telemetry. It does not change what the runtime records. +* If every field is unset or empty, the SDK omits client info from the handshake and the runtime keeps its default attribution. diff --git a/docs/features/context-management.md b/docs/features/context-management.md new file mode 100644 index 0000000000..6472b046aa --- /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/mcp.md b/docs/features/mcp.md index 6f715bd2ed..caac633275 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 ccd95df95a..4af11d9a7e 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/session-persistence.md b/docs/features/session-persistence.md index 3bfff10d0f..9b0aa9434c 100644 --- a/docs/features/session-persistence.md +++ b/docs/features/session-persistence.md @@ -242,6 +242,7 @@ When resuming a session, you can optionally reconfigure many settings. This is u | `availableTools` | Restrict which tools are available | | `excludedTools` | Disable specific tools | | `provider` | Re-provide BYOK credentials (required for BYOK sessions) | +| `capi.autoTier` | Override the persisted Auto routing preference on cold resume only | | `reasoningEffort` | Adjust reasoning effort level | | `streaming` | Enable/disable streaming responses | | `workingDirectory` | Change the working directory | @@ -253,6 +254,21 @@ When resuming a session, you can optionally reconfigure many settings. This is u | `disabledSkills` | Skills to disable | | `infiniteSessions` | Configure infinite session behavior | +### Auto tier persistence + +With `model: "auto"`, the optional `capi.autoTier` setting selects an Auto routing preference: `efficiency`, `balance`, or `intelligence`. In Python, use `capi={"auto_tier": "balance"}`. This requires Copilot CLI `1.0.82-1` or later with V2 Auto routing; V1 Auto requests are unchanged. + +The runtime persists the selected tier, so applications do not need to resend it on every resume: + +* Omitting the tier when creating a session uses the runtime's default routing behavior. +* A cold resume restores the persisted tier. Supplying an explicit tier overrides the restored value for the new activation. +* When resuming a session already resident in the runtime, omitting the tier preserves the current selection, supplying the same tier is a no-op, and supplying a different tier is rejected. +* Older sessions without a persisted tier retain default routing behavior. + +Tier selection is not a live model-switch operation. The SDK forwards the preference; the runtime owns persistence and validation. + +The `session.start` and `session.resume` events expose the selected tier in their optional `data.autoTier` field (`data.auto_tier` in Python). When no tier is selected, the field is omitted. + ### Example: changing model on resume ```typescript diff --git a/docs/features/skills.md b/docs/features/skills.md index 5b8388162a..d76196429e 100644 --- a/docs/features/skills.md +++ b/docs/features/skills.md @@ -351,6 +351,22 @@ The markdown body contains the instructions that are injected into the session c | .NET | `SkillDirectories` | `List` | Directories to load skills from | | .NET | `DisabledSkills` | `List` | Skills to disable | +### Built-in skills and `mode: "empty"` + +The runtime ships with a set of bundled **built-in** skills that are eligible by +default. When you run the client in `mode: "empty"` (the recommended baseline for +[multi-tenant servers](../setup/multi-tenancy.md)), the SDK excludes every +runtime-bundled built-in skill: it sends an empty `includedBuiltinSkills` list on +the post-create and post-resume options patch, alongside the empty +`installedPlugins` list. + +This exclusion is the default, not a permanent restriction. To allow selected +runtime-bundled skills, set `includedBuiltinSkills` (or the language-specific +casing) to their names. You can also opt into your **own** custom skills under +`mode: "empty"`—enable skills and pass your own `skillDirectories`—and those +remain fully usable, including a custom skill that shares a name with a built-in. +Under `mode: "copilot-cli"` the field is omitted unless you set the option. + ## Best practices 1. **Organize by domain** - Group related skills together (e.g., `skills/security/`, `skills/testing/`) diff --git a/docs/setup/backend-services.md b/docs/setup/backend-services.md index 7f1da36e82..b7b288bb8f 100644 --- a/docs/setup/backend-services.md +++ b/docs/setup/backend-services.md @@ -540,7 +540,7 @@ setInterval(() => cleanupSessions(24 * 60 * 60 * 1000), 60 * 60 * 1000); | **Single CLI server = single point of failure** | See [Scaling guide](./scaling.md) for HA patterns | | **No built-in auth between SDK and CLI** | Secure the network path (same host, VPC, etc.) | | **Session state on local disk** | Mount persistent storage for container restarts | -| **30-minute idle timeout** | Sessions without activity are auto-cleaned | +| **No idle timeout by default** | Pass `--session-idle-timeout ` to the CLI server to automatically clean up inactive sessions | ## When to move on diff --git a/docs/setup/bundled-cli.md b/docs/setup/bundled-cli.md index f067de8fde..9d9c80c35b 100644 --- a/docs/setup/bundled-cli.md +++ b/docs/setup/bundled-cli.md @@ -39,6 +39,9 @@ flowchart TB ## Quick start +> [!NOTE] +> Each SDK has a minimum language runtime requirement—see the Prerequisites section of the [Node.js](../../nodejs/README.md#prerequisites), [Python](../../python/README.md#prerequisites), [Go](../../go/README.md#prerequisites), [Rust](../../rust/README.md#prerequisites), [Java](../../java/README.md#prerequisites), or [.NET](../../dotnet/README.md#prerequisites) README—since an unsupported runtime (e.g. Python below the stated floor) can cause `pip`/package managers to silently resolve an outdated SDK release instead of reporting a version conflict. +
Node.js / TypeScript diff --git a/docs/setup/multi-tenancy.md b/docs/setup/multi-tenancy.md index 2f82dde0bf..ebbafe4fb0 100644 --- a/docs/setup/multi-tenancy.md +++ b/docs/setup/multi-tenancy.md @@ -24,7 +24,9 @@ This guide is a sister to [Scaling and multi-tenancy](./scaling.md). Use that gu | `baseDirectory` | Isolating `COPILOT_HOME` per runtime instance | Ignored when connecting to an existing runtime. | | `sessionFs` | Routing session filesystem storage off local disk | Pair with per-session filesystem providers. | | `RuntimeConnection.forUri(url)` | Sharing one already-running runtime | Language names vary; see samples below. | -| Per-session `gitHubToken` | Scoping auth to the requesting user | Prefer this over a single shared user token. | +| Per-session GitHub token or provider | Scoping auth to the requesting user | Prefer a rotating provider for short-lived credentials; use a static `gitHubToken` only when rotation is unnecessary. | + +For callback-backed credentials, see [Rotating session-scoped GitHub tokens](../auth/authenticate.md#rotating-session-scoped-github-tokens). Each session owns its provider registration, so concurrent sessions can use different GitHub hosts and accounts without sharing callback state. ### `mode: "empty"` @@ -396,10 +398,13 @@ Session-level isolation means the runtime keeps user-specific model and state in | Session state | Per session ID under `COPILOT_HOME/session-state/{sessionId}`. | | GitHub identity | Per-session when `gitHubToken` is set on the session. | | Tools | Explicit in `mode: "empty"`; ambient in `mode: "copilot-cli"`. | +| Skills | In `mode: "empty"` no runtime-bundled built-in skills are eligible by default; callers can allow selected built-ins or opt into their own custom skills. Ambient in `mode: "copilot-cli"`. | | Host filesystem | Shared by the runtime process if host tools are available. | `mode: "empty"` is what makes shared runtime patterns viable: no ambient OS tools are exposed unless your application registers or allows them. With `mode: "copilot-cli"`, OS filesystem access is shared through the host process, so do not use that mode for multi-user server mode. +Under `mode: "empty"` the SDK excludes every runtime-bundled built-in skill by default (it sends an empty `includedBuiltinSkills` list on the post-create/post-resume options patch, alongside the empty `installedPlugins` list). Set `includedBuiltinSkills` (or the language-specific casing) to explicitly allow selected built-ins, just as `availableTools` allows selected runtime-bundled tools. A caller can also opt into its **own** custom skills—for example by enabling skills and pointing at its own skill directories—and those remain usable. + Session state is stored under `COPILOT_HOME/session-state/{sessionId}` unless you route it through `sessionFs`. Use unique session IDs that include your own tenant or user boundary, and enforce access control before resuming or deleting sessions. ## Pattern comparison diff --git a/docs/setup/scaling.md b/docs/setup/scaling.md index c4a7a0953f..acf82bdb26 100644 --- a/docs/setup/scaling.md +++ b/docs/setup/scaling.md @@ -626,7 +626,7 @@ flowchart TB | **No built-in session locking** | Implement application-level locking for concurrent access | | **No built-in load balancing** | Use external LB or service mesh | | **Session state is file-based** | Requires shared filesystem for multi-server setups | -| **30-minute idle timeout** | Sessions without activity are auto-cleaned by the CLI | +| **No idle timeout by default** | Pass `--session-idle-timeout ` to the CLI server to automatically clean up inactive sessions | | **CLI is single-process** | Scale by adding more CLI server instances, not threads | ## Next steps diff --git a/docs/troubleshooting/compatibility.md b/docs/troubleshooting/compatibility.md index 795c0f5fd4..da8bf0daae 100644 --- a/docs/troubleshooting/compatibility.md +++ b/docs/troubleshooting/compatibility.md @@ -77,7 +77,7 @@ 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 | @@ -89,6 +89,7 @@ The Copilot SDK communicates with the CLI via JSON-RPC protocol. Features must b | 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 6efd6e094c..23a78030b4 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -101,6 +101,11 @@ new CopilotClient(CopilotClientOptions? options = null) - `RuntimeConnection.ForTcp(port = 0, connectionToken?, path?, args?)` — spawns the runtime as a child process listening on a TCP port. `port = 0` auto-allocates; if a non-zero port is already in use, startup fails (no fallback). Use `CopilotClient.RuntimePort` after `StartAsync` to read the assigned port. `connectionToken` is required if other clients will connect via `RuntimeConnection.ForUri(...)`. - `RuntimeConnection.ForUri(url, connectionToken?)` — connects to an already-running runtime at `url` (e.g., `"localhost:8080"`). Does not spawn a process. +Managed stdio and TCP connections use the bundled `copilot-runtime[.exe]` and +adjacent `runtime.node` by default. An explicit connection path or +`COPILOT_CLI_PATH` overrides the bundled runtime. +Managed launch fails if the bundled wrapper pair is unavailable. + #### Methods ##### `StartAsync(): Task` @@ -133,8 +138,10 @@ Create a new conversation session. - `InfiniteSessions` - Configure automatic context compaction (see below) - `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. +- `GitHubTokenProvider` - Acquires session-scoped GitHub tokens on demand. Return `GitHubTokenProviderResult.FromToken` with a positive `ExpiresIn` value (production GitHub tokens typically use `8 * 60 * 60` seconds), or `GitHubTokenProviderResult.Cancel()`. Cannot be combined with `GitHubToken`. - `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. +- `OnUserInputRequest` - Handler for legacy question-and-answer requests from the agent. Enables the legacy `ask_user` tool. See [User Input Requests](#user-input-requests) section. +- `AskUserVariant` - Selects the model-facing `ask_user` tool shape. Defaults to `AskUserVariant.Legacy`; use `AskUserVariant.Elicitation` with `OnElicitationRequest`. - `Hooks` - Hook handlers for session lifecycle events. See [Session Hooks](#session-hooks) section. ##### `ResumeSessionAsync(string sessionId, ResumeSessionConfig? config = null): Task` @@ -144,6 +151,25 @@ Resume an existing session. Returns the session with `WorkspacePath` populated i **ResumeSessionConfig:** - `OnPermissionRequest` - Optional handler called before each tool execution to approve or deny it. See [Permission Handling](#permission-handling) section. +- `GitHubTokenProvider` - Replaces the session-scoped token provider when resuming. Cannot be combined with `GitHubToken`. +- `AskUserVariant` - Re-supplies the model-facing `ask_user` tool shape on cold resume. + +```csharp +await using var session = await client.CreateSessionAsync(new SessionConfig +{ + GitHubTokenProvider = async args => + { + var token = await AcquireTokenAsync(args.Host); + return GitHubTokenProviderResult.FromToken(new GitHubToken + { + AccessToken = token, + ExpiresIn = 8 * 60 * 60 + }); + } +}); +``` + +Initial acquisition runs during session creation or resume. Cancellation, provider errors, and invalid token responses reject that operation instead of falling back to ambient authentication. Idle sessions refresh only before their next credential-consuming operation; there is no background refresh timer. ##### `PingAsync(string? message = null): Task` @@ -852,7 +878,7 @@ To let a specific custom tool bypass the permission prompt entirely, set `SkipPe ## User Input Requests -Enable the agent to ask questions to the user using the `ask_user` tool by providing an `OnUserInputRequest` handler: +Enable the legacy question-and-answer `ask_user` tool by providing an `OnUserInputRequest` handler: ```csharp var session = await client.CreateSessionAsync(new SessionConfig @@ -985,6 +1011,7 @@ var session = await client.CreateSessionAsync(new SessionConfig { Model = "gpt-5", OnPermissionRequest = PermissionHandler.ApproveAll, + AskUserVariant = AskUserVariant.Elicitation, OnElicitationRequest = async (context) => { // context.SessionId - Session that triggered the request diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index 4e271598eb..b02958ea97 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -55,6 +55,7 @@ namespace GitHub.Copilot; /// public sealed partial class CopilotClient : IDisposable, IAsyncDisposable { + private const string ExplicitBundledCliMarker = ".copilot-explicit-cli"; /// /// Minimum protocol version this SDK can communicate with. /// @@ -70,12 +71,14 @@ public sealed partial class CopilotClient : IDisposable, IAsyncDisposable /// that has not been explicitly disposed or removed. /// internal readonly ConcurrentDictionary _sessions = new(); + private readonly ConcurrentDictionary>> _gitHubTokenProviders = new(); private readonly CopilotClientOptions _options; private readonly RuntimeConnection _connection; private readonly ILogger _logger; private readonly int? _optionsPort; private readonly string? _optionsHost; + private readonly string[] _builtinPluginDirectories; private readonly Func>>? _onListModels; private readonly List _lifecycleHandlers = []; @@ -90,8 +93,7 @@ public sealed partial class CopilotClient : IDisposable, IAsyncDisposable /// /// Client-global RPC handlers (e.g. the LLM inference provider adapter), - /// built once at construction when the corresponding option is configured and - /// registered on every connection. Null when no client-global API is enabled. + /// built once at construction and registered on every connection. /// private readonly ClientGlobalApiHandlers? _clientGlobalApis; @@ -138,6 +140,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) { @@ -167,7 +177,7 @@ public CopilotClient(CopilotClientOptions? options = null) throw new ArgumentException("GitHubToken and UseLoggedInUser cannot be combined with RuntimeConnection.ForUri (the existing runtime manages its own auth).", nameof(options)); } var parsed = ParseRuntimeUrl(uri.Url); - _optionsHost = parsed.Host; + _optionsHost = parsed.Host.Trim('[', ']'); _optionsPort = parsed.Port; break; @@ -298,7 +308,7 @@ private static RuntimeConnection ResolveDefaultConnection(CopilotClientOptions o /// /// Parses a runtime URL into a URI with host and port. /// - /// The URL to parse. Supports formats: "port", "host:port", "http://host:port". + /// The URL to parse. Supports formats: "port", "host:port", "[ipv6]:port", "http://host:port". private static Uri ParseRuntimeUrl(string url) { // If it's just a port number, treat as localhost @@ -317,6 +327,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. /// @@ -387,9 +417,19 @@ async Task StartCoreAsync(CancellationToken ct) ffiArgs.Add("--remote"); } + var explicitCliPath = System.Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"); + if (string.IsNullOrEmpty(explicitCliPath)) + { + explicitCliPath = null; + } + var ffiRuntimePath = explicitCliPath is null + ? GetBundledNativePath(FfiRuntimeHost.GetRuntimeLibraryFileName(), out var searchedRuntime) + ?? throw new InvalidOperationException( + $"In-process FFI runtime library not found at '{searchedRuntime}'.") + : ResolveRuntimePathForExplicitCli(explicitCliPath); var ffiHost = FfiRuntimeHost.Create( - ResolveCliPathForFfi(), - GetNapiPrebuildsFolderOrThrow(), + ffiRuntimePath, + explicitCliPath, ffiEnvironment, ffiArgs, _logger); @@ -423,6 +463,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) @@ -457,6 +504,20 @@ async Task StartCoreAsync(CancellationToken ct) await CleanupCliProcessAsync(cliProcess, stderrPump, errors: null, _logger); } + if (ex is IOException + && cliProcess is not null + && stderrPump is not null + && !ex.Message.Contains("stderr:", StringComparison.OrdinalIgnoreCase)) + { + var stderrOutput = GetStderrOutput(stderrPump.Buffer); + if (!string.IsNullOrEmpty(stderrOutput)) + { + throw new IOException( + FormatCliExitedMessage("CLI process exited unexpectedly.", stderrOutput), + ex); + } + } + throw; } } @@ -505,6 +566,7 @@ public async Task StopAsync() } _sessions.Clear(); + ClearGitHubTokenProviders(); await CleanupConnectionAsync(errors, gracefulRuntimeShutdown: true); @@ -536,6 +598,7 @@ public async Task StopAsync() public async Task ForceStopAsync() { _sessions.Clear(); + ClearGitHubTokenProviders(); var errors = new List(); await CleanupConnectionAsync(errors, gracefulRuntimeShutdown: false); @@ -635,7 +698,7 @@ or IOException private static async Task CleanupCliProcessAsync(Process childProcess, ProcessStderrPump? stderrPump, List? errors, ILogger? logger) { - stderrPump?.Cancel(); + var processExited = false; try { @@ -668,12 +731,19 @@ private static async Task CleanupCliProcessAsync(Process childProcess, ProcessSt AddCleanupError(errors, ex, logger); } } + + processExited = childProcess.HasExited; } catch (Exception ex) { AddCleanupError(errors, ex, logger); } + if (!processExited) + { + stderrPump?.Cancel(); + } + if (stderrPump is not null) { var stderrPumpWaitTimestamp = Stopwatch.GetTimestamp(); @@ -683,6 +753,7 @@ private static async Task CleanupCliProcessAsync(Process childProcess, ProcessSt } catch (TimeoutException ex) { + stderrPump.Cancel(); if (logger is not null) { LoggingHelpers.LogTiming(logger, LogLevel.Debug, ex, @@ -785,7 +856,7 @@ private CopilotSession InitializeSession( session.RegisterTools(config.Tools ?? []); session.RegisterPermissionHandler( config.OnPermissionRequest, - config.EnableManagedSettings is true); + config.EnableManagedSettings is true || config.ManagedSettings is not null); session.RegisterMcpAuthHandler(config.OnMcpAuthRequest); session.RegisterCommands(config.Commands); session.RegisterElicitationHandler(config.OnElicitationRequest); @@ -994,8 +1065,9 @@ private void ApplyConfigDefaultsForMode(SessionConfigBase config) /// patch for the current mode. In empty mode this defaults the four /// overridable feature flags to safe values (caller values from /// win); installedPlugins=[] is - /// unconditional under empty mode so apps that need plugins must switch - /// modes. In copilot-cli mode only explicitly-set fields are forwarded. + /// unconditional under empty mode. includedBuiltinSkills defaults to + /// an empty list, but callers can explicitly allow selected runtime-bundled + /// skills. In copilot-cli mode only explicitly-set fields are forwarded. /// private async Task UpdateSessionOptionsForModeAsync(CopilotSession session, SessionConfigBase config, CancellationToken cancellationToken) { @@ -1005,6 +1077,7 @@ private async Task UpdateSessionOptionsForModeAsync(CopilotSession session, Sess bool? coauthorEnabled = null; bool? manageScheduleEnabled = null; IList? installedPlugins = null; + IList? includedBuiltinSkills = null; if (_options.Mode == CopilotClientMode.Empty) { @@ -1013,6 +1086,7 @@ private async Task UpdateSessionOptionsForModeAsync(CopilotSession session, Sess coauthorEnabled = config.CoauthorEnabled ?? false; manageScheduleEnabled = config.ManageScheduleEnabled ?? false; installedPlugins = []; + includedBuiltinSkills = config.IncludedBuiltinSkills ?? []; hasAnyPatch = true; } else @@ -1021,6 +1095,7 @@ private async Task UpdateSessionOptionsForModeAsync(CopilotSession session, Sess if (config.CustomAgentsLocalOnly is not null) { customAgentsLocalOnly = config.CustomAgentsLocalOnly; hasAnyPatch = true; } if (config.CoauthorEnabled is not null) { coauthorEnabled = config.CoauthorEnabled; hasAnyPatch = true; } if (config.ManageScheduleEnabled is not null) { manageScheduleEnabled = config.ManageScheduleEnabled; hasAnyPatch = true; } + if (config.IncludedBuiltinSkills is not null) { includedBuiltinSkills = config.IncludedBuiltinSkills; hasAnyPatch = true; } } if (!hasAnyPatch) return; @@ -1034,6 +1109,7 @@ await session.Rpc.Options.UpdateAsync( coauthorEnabled: coauthorEnabled, manageScheduleEnabled: manageScheduleEnabled, installedPlugins: installedPlugins, + includedBuiltinSkills: includedBuiltinSkills, cancellationToken: cancellationToken).ConfigureAwait(false); #pragma warning restore GHCP001 } @@ -1083,6 +1159,7 @@ await session.Rpc.Options.UpdateAsync( public async Task CreateSessionAsync(SessionConfig config, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(config); + ValidateGitHubTokenConfig(config); var connection = await EnsureConnectedAsync(cancellationToken); var totalTimestamp = Stopwatch.GetTimestamp(); @@ -1117,19 +1194,22 @@ public async Task CreateSessionAsync(SessionConfig config, Cance ? null : (string.IsNullOrEmpty(config.SessionId) ? Guid.NewGuid().ToString() : config.SessionId); + var registrationId = RegisterGitHubTokenProvider(config.GitHubTokenProvider); + var registrationTransferred = false; CopilotSession? session = null; - if (localSessionId != null) - { - session = InitializeSession( - localSessionId, - connection.Rpc, - config, - transformCallbacks, - hasHooks, - "CopilotClient.CreateSessionAsync"); - } try { + if (localSessionId != null) + { + session = InitializeSession( + localSessionId, + connection.Rpc, + config, + transformCallbacks, + hasHooks, + "CopilotClient.CreateSessionAsync"); + } + var (traceparent, tracestate) = TelemetryHelpers.GetTraceContext(); var request = new CreateSessionRequest( @@ -1141,7 +1221,9 @@ public async Task CreateSessionAsync(SessionConfig config, Cance config.ContextTier, config.Tools?.Select(ToolDefinition.FromAIFunction).ToList(), config.EnableCitations, + config.EnableFileChangeTracking, wireSystemMessage, + config.AskUserVariant, toolFilter.AvailableTools, toolFilter.ExcludedTools, config.ExcludedBuiltInAgents, @@ -1178,17 +1260,19 @@ public async Task CreateSessionAsync(SessionConfig config, Cance config.DisabledSkills, config.InfiniteSessions, config.SessionLimits, - Commands: config.Commands?.Select(c => new CommandWireDefinition(c.Name, c.Description)).ToList(), + Commands: config.Commands?.Select(c => new CommandWireDefinition(c.Name, c.Description ?? string.Empty)).ToList(), RequestElicitation: config.OnElicitationRequest != null, RequestMcpApps: config.EnableMcpApps ? true : null, Traceparent: traceparent, Tracestate: tracestate, ModelCapabilities: config.ModelCapabilities, GitHubToken: config.GitHubToken, + GitHubTokenProviderRegistrationId: registrationId, RemoteSession: config.RemoteSession, Cloud: config.Cloud, InstructionDirectories: config.InstructionDirectories, PluginDirectories: config.PluginDirectories, + DisabledMcpServers: config.DisabledMcpServers, LargeOutput: config.LargeOutput, ToolSearch: config.ToolSearch, Memory: config.Memory, @@ -1201,9 +1285,11 @@ public async Task CreateSessionAsync(SessionConfig config, Cance Providers: config.Providers, Models: config.Models, ToolFilterPrecedence: toolFilter.ToolFilterPrecedence, + FeatureFlags: config.FeatureFlags, ExpAssignments: config.ExpAssignments, EnableManagedSettings: config.EnableManagedSettings, GitHubMcpToolConfig: config.GitHubMcpToolConfig, + ManagedSettings: config.ManagedSettings, EnableGitHubTelemetryForwarding: _options.OnGitHubTelemetry != null ? true : null, AdditionalDirectories: config.AdditionalDirectories); @@ -1262,10 +1348,15 @@ public async Task CreateSessionAsync(SessionConfig config, Cance session.SetOpenCanvases(response.OpenCanvases); await UpdateSessionOptionsForModeAsync(session, config, cancellationToken).ConfigureAwait(false); + if (registrationId is not null) + { + session.SetGitHubTokenProviderRegistration(registrationId); + registrationTransferred = true; + } } catch (Exception ex) { - session?.RemoveFromClient(); + session?.Unregister(); if (ex is not OperationCanceledException) { @@ -1277,6 +1368,13 @@ public async Task CreateSessionAsync(SessionConfig config, Cance throw; } + finally + { + if (!registrationTransferred && registrationId is not null) + { + UnregisterGitHubTokenProvider(registrationId); + } + } LoggingHelpers.LogTiming(_logger, LogLevel.Debug, null, "CopilotClient.CreateSessionAsync complete. Elapsed={Elapsed}, SessionId={SessionId}", @@ -1314,6 +1412,7 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes { ArgumentNullException.ThrowIfNull(sessionId); ArgumentNullException.ThrowIfNull(config); + ValidateGitHubTokenConfig(config); var connection = await EnsureConnectedAsync(cancellationToken); var totalTimestamp = Stopwatch.GetTimestamp(); @@ -1336,17 +1435,21 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes var (wireSystemMessage, transformCallbacks) = ExtractTransformCallbacks(config.SystemMessage); - // Create and register the session before issuing the RPC so that - // events emitted by the CLI (e.g. session.start) are not dropped. - var session = InitializeSession( - sessionId, - connection.Rpc, - config, - transformCallbacks, - hasHooks, - "CopilotClient.ResumeSessionAsync"); + var registrationId = RegisterGitHubTokenProvider(config.GitHubTokenProvider); + var registrationTransferred = false; + CopilotSession? session = null; try { + // Create and register the session before issuing the RPC so that + // events emitted by the CLI (e.g. session.start) are not dropped. + session = InitializeSession( + sessionId, + connection.Rpc, + config, + transformCallbacks, + hasHooks, + "CopilotClient.ResumeSessionAsync"); + var (traceparent, tracestate) = TelemetryHelpers.GetTraceContext(); var request = new ResumeSessionRequest( @@ -1358,7 +1461,9 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes config.ContextTier, config.Tools?.Select(ToolDefinition.FromAIFunction).ToList(), config.EnableCitations, + config.EnableFileChangeTracking, wireSystemMessage, + config.AskUserVariant, toolFilter.AvailableTools, toolFilter.ExcludedTools, config.ExcludedBuiltInAgents, @@ -1396,17 +1501,19 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes config.DisabledSkills, config.InfiniteSessions, config.SessionLimits, - Commands: config.Commands?.Select(c => new CommandWireDefinition(c.Name, c.Description)).ToList(), + Commands: config.Commands?.Select(c => new CommandWireDefinition(c.Name, c.Description ?? string.Empty)).ToList(), RequestElicitation: config.OnElicitationRequest != null, RequestMcpApps: config.EnableMcpApps ? true : null, Traceparent: traceparent, Tracestate: tracestate, ModelCapabilities: config.ModelCapabilities, GitHubToken: config.GitHubToken, + GitHubTokenProviderRegistrationId: registrationId, RemoteSession: config.RemoteSession, ContinuePendingWork: config.ContinuePendingWork, InstructionDirectories: config.InstructionDirectories, PluginDirectories: config.PluginDirectories, + DisabledMcpServers: config.DisabledMcpServers, LargeOutput: config.LargeOutput, ToolSearch: config.ToolSearch, Memory: config.Memory, @@ -1420,9 +1527,11 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes Providers: config.Providers, Models: config.Models, ToolFilterPrecedence: toolFilter.ToolFilterPrecedence, + FeatureFlags: config.FeatureFlags, ExpAssignments: config.ExpAssignments, EnableManagedSettings: config.EnableManagedSettings, GitHubMcpToolConfig: config.GitHubMcpToolConfig, + ManagedSettings: config.ManagedSettings, EnableGitHubTelemetryForwarding: _options.OnGitHubTelemetry != null ? true : null, AdditionalDirectories: config.AdditionalDirectories); @@ -1444,10 +1553,15 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes } await UpdateSessionOptionsForModeAsync(session, config, cancellationToken).ConfigureAwait(false); + if (registrationId is not null) + { + session.SetGitHubTokenProviderRegistration(registrationId); + registrationTransferred = true; + } } catch (Exception ex) { - session.RemoveFromClient(); + session?.Unregister(); if (ex is not OperationCanceledException) { LoggingHelpers.LogTiming(_logger, LogLevel.Warning, ex, @@ -1457,12 +1571,19 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes } throw; } + finally + { + if (!registrationTransferred && registrationId is not null) + { + UnregisterGitHubTokenProvider(registrationId); + } + } LoggingHelpers.LogTiming(_logger, LogLevel.Debug, null, "CopilotClient.ResumeSessionAsync complete. Elapsed={Elapsed}, SessionId={SessionId}", totalTimestamp, sessionId); - return session; + return session!; } /// @@ -1622,7 +1743,10 @@ public async Task DeleteSessionAsync(string sessionId, CancellationToken cancell throw new InvalidOperationException($"Failed to delete session {sessionId}: {response.Error}"); } - RemoveSession(sessionId); + if (_sessions.TryRemove(sessionId, out var session)) + { + session.ReleaseGitHubTokenProviderRegistration(); + } } /// @@ -1871,13 +1995,15 @@ private static string FormatCliExitedMessage(string message, string stderrOutput private static IOException CreateCliExitedException(string message, StringBuilder stderrBuffer) { - string stderrOutput; + return new IOException(FormatCliExitedMessage(message, GetStderrOutput(stderrBuffer))); + } + + private static string GetStderrOutput(StringBuilder stderrBuffer) + { lock (stderrBuffer) { - stderrOutput = stderrBuffer.ToString().Trim(); + return stderrBuffer.ToString().Trim(); } - - return new IOException(FormatCliExitedMessage(message, stderrOutput)); } private Task EnsureConnectedAsync(CancellationToken cancellationToken) @@ -1904,25 +2030,94 @@ await Rpc.SessionFs.SetProviderAsync( /// /// Builds the client-global RPC handler bag at construction time. Registers /// the LLM inference provider adapter and/or the GitHub telemetry adapter - /// depending on which options are configured; returns null when no - /// client-global API is configured so the registration is skipped entirely. + /// depending on which options are configured. The GitHub token dispatcher is + /// always registered because providers are configured per session. /// private ClientGlobalApiHandlers? BuildClientGlobalApis() { var handler = _options.RequestHandler; var onGitHubTelemetry = _options.OnGitHubTelemetry; - if (handler is null && onGitHubTelemetry is null) - { - return null; - } - return new ClientGlobalApiHandlers { LlmInference = handler is null ? null : new LlmInferenceAdapter(handler, () => _serverRpc), GitHubTelemetry = onGitHubTelemetry is null ? null : new GitHubTelemetryAdapter(onGitHubTelemetry, _logger), + GitHubToken = new GitHubTokenAdapter(this), }; } + private static void ValidateGitHubTokenConfig(SessionConfigBase config) + { + if (config.GitHubToken is not null && config.GitHubTokenProvider is not null) + { + throw new ArgumentException( + $"{nameof(SessionConfigBase.GitHubToken)} and {nameof(SessionConfigBase.GitHubTokenProvider)} cannot be used together.", + nameof(config)); + } + } + + private string? RegisterGitHubTokenProvider( + Func>? provider) + { + if (provider is null) + { + return null; + } + + var registrationId = Guid.NewGuid().ToString(); + if (!_gitHubTokenProviders.TryAdd(registrationId, provider)) + { + throw new InvalidOperationException("Failed to register GitHub token provider."); + } + return registrationId; + } + + internal void UnregisterGitHubTokenProvider(string registrationId) + => _gitHubTokenProviders.TryRemove(registrationId, out _); + + private void ClearGitHubTokenProviders() => _gitHubTokenProviders.Clear(); + + private sealed class GitHubTokenAdapter(CopilotClient client) : IGitHubTokenHandler + { + public async Task GetTokenAsync( + GitHubTokenAcquireRequest request, + CancellationToken cancellationToken = default) + { + if (!client._gitHubTokenProviders.TryGetValue(request.RegistrationId, out var provider)) + { + throw new InvalidOperationException( + $"Unknown GitHub token provider registration ID '{request.RegistrationId}'."); + } + + var reason = request.Reason == GitHubTokenAcquireReason.Initial + ? GitHubTokenRequestReason.Initial + : request.Reason == GitHubTokenAcquireReason.Refresh + ? GitHubTokenRequestReason.Refresh + : throw new InvalidOperationException($"Unknown GitHub token request reason '{request.Reason}'."); + var result = await provider(new GitHubTokenProviderArgs + { + Host = request.Host, + SessionId = request.SessionId, + Reason = reason, + }).ConfigureAwait(false); + + if (result is { Cancelled: true }) + { + return new GitHubTokenAcquireResultCancelled(); + } + if (result?.Token is not { } token) + { + throw new InvalidOperationException( + "GitHub token provider returned neither a token nor cancellation."); + } + return new GitHubTokenAcquireResultToken + { + AccessToken = token.AccessToken, + TokenType = token.TokenType, + ExpiresIn = token.ExpiresIn, + }; + } + } + /// /// Tells the runtime to route its outbound model-layer requests through this /// client's LLM inference provider. No-op when interception is not configured. @@ -1987,7 +2182,11 @@ [new ConnectHandshakeRequest( // handler is registered (mirrors the runtime, which reads this flag on the // `connect` handshake so the first session's un-replayable `session.start` // event is forwarded). Also sent on session.create/resume for older CLIs. - _options.OnGitHubTelemetry != null ? true : null)], + _options.OnGitHubTelemetry != null ? true : null, + // Declare the integrating application's identity so the runtime attributes the + // telemetry it emits on this connection to a consistent surface instead + // of its own build. Null when the app didn't supply it. + ConnectHandshakeClientInfo.From(_options.ClientInfo))], connection.StderrBuffer, cancellationToken); serverVersion = (int)connectResponse.ProtocolVersion; @@ -2059,17 +2258,19 @@ private static void ApplyTelemetryEnvironment(IDictionary envir var tcpConnection = _connection as TcpRuntimeConnection; var useStdio = _connection is StdioRuntimeConnection; - // Use explicit path, COPILOT_CLI_PATH env var (from the connection's - // Environment, options.Environment, or process env), or bundled runtime - no PATH fallback - var envCliPath = - (childProcessConnection.Environment is not null && childProcessConnection.Environment.TryGetValue("COPILOT_CLI_PATH", out var connEnvValue) ? connEnvValue : null) - ?? (options.Environment is not null && options.Environment.TryGetValue("COPILOT_CLI_PATH", out var envValue) ? envValue : null) - ?? System.Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"); - var cliPath = childProcessConnection.Path - ?? envCliPath - ?? GetBundledCliPath(out var searchedPath) - ?? throw new InvalidOperationException($"Copilot runtime not found at '{searchedPath}'. Ensure the SDK NuGet package was restored correctly or provide an explicit RuntimeConnection.ForStdio(path: ...) / RuntimeConnection.ForTcp(path: ...)."); - var cliPathSource = childProcessConnection.Path is not null ? "Options" : envCliPath is not null ? "Environment" : "Bundled"; + // Explicit CLI paths preserve the legacy launch contract. Otherwise use + // the bundled native runtime pair. + var configuredEnvironment = childProcessConnection.Environment ?? options.Environment; + var envCliPath = configuredEnvironment is not null + ? configuredEnvironment.TryGetValue("COPILOT_CLI_PATH", out var configuredCliPath) ? configuredCliPath : null + : System.Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"); + var launch = childProcessConnection.Path is not null + ? new RuntimeLaunch(childProcessConnection.Path, "Options") + : envCliPath is not null + ? new RuntimeLaunch(envCliPath, "Environment") + : GetBundledRuntimeLaunch(); + var cliPath = launch.Executable; + var cliPathSource = launch.Source; var args = new List(); if (childProcessConnection.Args != null) @@ -2251,7 +2452,11 @@ private static void ApplyTelemetryEnvironment(IDictionary envir private static string? GetBundledCliPath(out string searchedPath) { - var binaryName = OperatingSystem.IsWindows() ? "copilot.exe" : "copilot"; + return GetBundledNativePath(OperatingSystem.IsWindows() ? "copilot.exe" : "copilot", out searchedPath); + } + + private static string? GetBundledNativePath(string binaryName, out string searchedPath) + { // Always use portable RID (e.g., linux-x64) to match the build-time placement, // since distro-specific RIDs (e.g., ubuntu.24.04-x64) are normalized at build time. var rid = GetPortableRid() @@ -2260,6 +2465,57 @@ private static void ApplyTelemetryEnvironment(IDictionary envir return File.Exists(searchedPath) ? searchedPath : null; } + private static RuntimeLaunch GetBundledRuntimeLaunch() + { + _ = GetBundledNativePath( + OperatingSystem.IsWindows() ? "copilot-runtime.exe" : "copilot-runtime", + out var searchedWrapper); + var directory = Path.GetDirectoryName(searchedWrapper)!; + var runtimeNode = Path.Combine(directory, "runtime.node"); + var explicitCliMarker = Path.Combine(directory, ExplicitBundledCliMarker); + if (!File.Exists(searchedWrapper) + && !File.Exists(runtimeNode) + && File.Exists(explicitCliMarker) + && GetBundledCliPath(out _) is { } explicitCli) + { + return new RuntimeLaunch(explicitCli, "Bundled explicit CLI"); + } + return ValidateRuntimePair(searchedWrapper, "Bundled runtime"); + } + + private static RuntimeLaunch ValidateRuntimePair(string wrapper, string source) + { + var runtimeNode = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(wrapper))!, "runtime.node"); + if (!File.Exists(wrapper)) + { + throw new InvalidOperationException($"Copilot runtime wrapper not found at '{wrapper}'."); + } + if (!File.Exists(runtimeNode)) + { + throw new InvalidOperationException( + $"Copilot runtime wrapper at '{wrapper}' is missing its adjacent runtime.node at '{runtimeNode}'."); + } + if (new FileInfo(wrapper).Length == 0 || new FileInfo(runtimeNode).Length == 0) + { + throw new InvalidOperationException("Copilot runtime wrapper and adjacent runtime.node must both be non-empty."); + } +#if NET8_0_OR_GREATER + if (!OperatingSystem.IsWindows()) + { + var mode = File.GetUnixFileMode(wrapper); + const UnixFileMode executeBits = + UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute; + if ((mode & executeBits) == 0) + { + File.SetUnixFileMode(wrapper, mode | executeBits); + } + } +#endif + return new RuntimeLaunch(wrapper, source); + } + + private sealed record RuntimeLaunch(string Executable, string Source); + private static string? GetPortableRid() { string os; @@ -2283,26 +2539,28 @@ private static void ApplyTelemetryEnvironment(IDictionary envir return arch != null ? $"{os}-{arch}" : null; } - private string ResolveCliPathForFfi() + private static string ResolveRuntimePathForExplicitCli(string cliPath) { - var envCliPath = _options.Environment is not null && _options.Environment.TryGetValue("COPILOT_CLI_PATH", out var envValue) - ? envValue - : System.Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"); - if (!string.IsNullOrEmpty(envCliPath)) + var fullEntrypoint = Path.GetFullPath(cliPath); + var directory = Path.GetDirectoryName(fullEntrypoint) + ?? throw new InvalidOperationException($"Could not determine directory for '{cliPath}'."); + var flatLibraryPath = Path.GetFullPath( + $"{directory}{Path.DirectorySeparatorChar}{FfiRuntimeHost.GetRuntimeLibraryFileName()}"); + if (File.Exists(flatLibraryPath)) { - return envCliPath; + return flatLibraryPath; } - - // Fall back to the bundled single-file CLI the same way stdio discovers it. - // It embeds its own Node and is spawned directly as `copilot --embedded-host`, - // with the sibling cdylib loaded in-process (FfiRuntimeHost.Create prefers the - // flat `libcopilot_runtime.so`/`copilot_runtime.dll` next to the CLI, falling - // back to the dev `prebuilds//runtime.node` layout). - var bundled = GetBundledCliPath(out var searchedPath); - return bundled - ?? throw new InvalidOperationException( - "In-process FFI hosting requires the Copilot CLI. Set the COPILOT_CLI_PATH " - + $"environment variable, or ensure the bundled CLI is present (looked in '{searchedPath}')."); + var adjacentPrebuildPath = Path.Combine(directory, "runtime.node"); + if (File.Exists(adjacentPrebuildPath)) + { + return adjacentPrebuildPath; + } + var prebuildsLibraryPath = Path.Combine( + directory, "prebuilds", GetNapiPrebuildsFolderOrThrow(), "runtime.node"); + return File.Exists(prebuildsLibraryPath) + ? prebuildsLibraryPath + : throw new InvalidOperationException( + $"FFI runtime library not found. Looked for '{flatLibraryPath}', '{adjacentPrebuildPath}', and '{prebuildsLibraryPath}'."); } /// @@ -2500,11 +2758,6 @@ private void RegisterSession(CopilotSession session) } } - private void RemoveSession(string sessionId) - { - _sessions.TryRemove(sessionId, out _); - } - /// /// Disposes the synchronously. /// @@ -2715,7 +2968,9 @@ internal record CreateSessionRequest( ContextTier? ContextTier, IList? Tools, bool? EnableCitations, + bool? EnableFileChangeTracking, SystemMessageConfig? SystemMessage, + AskUserVariant? AskUserVariant, IList? AvailableTools, IList? ExcludedTools, [property: JsonPropertyName("excludedBuiltinAgents")] IList? ExcludedBuiltInAgents, @@ -2759,10 +3014,12 @@ internal record CreateSessionRequest( string? Tracestate = null, ModelCapabilitiesOverride? ModelCapabilities = null, string? GitHubToken = null, + [property: JsonPropertyName("gitHubTokenProviderRegistrationId")] string? GitHubTokenProviderRegistrationId = null, RemoteSessionMode? RemoteSession = null, CloudSessionOptions? Cloud = null, IList? InstructionDirectories = null, IList? PluginDirectories = null, + [property: JsonPropertyName("disabledMcpServers")] IList? DisabledMcpServers = null, LargeToolOutputConfig? LargeOutput = null, ToolSearchConfig? ToolSearch = null, MemoryConfiguration? Memory = null, @@ -2776,8 +3033,10 @@ internal record CreateSessionRequest( IList? Providers = null, IList? Models = null, OptionsUpdateToolFilterPrecedence? ToolFilterPrecedence = null, + [property: JsonPropertyName("featureFlags")] IDictionary? FeatureFlags = null, [property: JsonPropertyName("expAssignments")] CopilotExpAssignmentResponse? ExpAssignments = null, [property: JsonPropertyName("enableManagedSettings")] bool? EnableManagedSettings = null, + [property: JsonPropertyName("managedSettings")] ManagedSettings? ManagedSettings = null, bool? EnableGitHubTelemetryForwarding = null, [property: JsonPropertyName("githubMcpToolConfig")] GitHubMcpToolConfig? GitHubMcpToolConfig = null, IList? AdditionalDirectories = null); @@ -2790,7 +3049,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) { @@ -2798,11 +3058,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); } } @@ -2823,7 +3085,9 @@ internal record ResumeSessionRequest( ContextTier? ContextTier, IList? Tools, bool? EnableCitations, + bool? EnableFileChangeTracking, SystemMessageConfig? SystemMessage, + AskUserVariant? AskUserVariant, IList? AvailableTools, IList? ExcludedTools, [property: JsonPropertyName("excludedBuiltinAgents")] IList? ExcludedBuiltInAgents, @@ -2868,10 +3132,12 @@ internal record ResumeSessionRequest( string? Tracestate = null, ModelCapabilitiesOverride? ModelCapabilities = null, string? GitHubToken = null, + [property: JsonPropertyName("gitHubTokenProviderRegistrationId")] string? GitHubTokenProviderRegistrationId = null, RemoteSessionMode? RemoteSession = null, bool? ContinuePendingWork = null, IList? InstructionDirectories = null, IList? PluginDirectories = null, + [property: JsonPropertyName("disabledMcpServers")] IList? DisabledMcpServers = null, LargeToolOutputConfig? LargeOutput = null, ToolSearchConfig? ToolSearch = null, MemoryConfiguration? Memory = null, @@ -2886,8 +3152,10 @@ internal record ResumeSessionRequest( IList? Providers = null, IList? Models = null, OptionsUpdateToolFilterPrecedence? ToolFilterPrecedence = null, + [property: JsonPropertyName("featureFlags")] IDictionary? FeatureFlags = null, [property: JsonPropertyName("expAssignments")] CopilotExpAssignmentResponse? ExpAssignments = null, [property: JsonPropertyName("enableManagedSettings")] bool? EnableManagedSettings = null, + [property: JsonPropertyName("managedSettings")] ManagedSettings? ManagedSettings = null, bool? EnableGitHubTelemetryForwarding = null, [property: JsonPropertyName("githubMcpToolConfig")] GitHubMcpToolConfig? GitHubMcpToolConfig = null, IList? AdditionalDirectories = null); @@ -2903,7 +3171,7 @@ internal record ResumeSessionResponse( internal record CommandWireDefinition( string Name, - string? Description); + string Description); internal record GetLastSessionIdResponse( string? SessionId); @@ -2929,7 +3197,45 @@ internal record GetSessionMetadataResponse( internal record ConnectHandshakeRequest( string? Token, - [property: JsonPropertyName("enableGitHubTelemetryForwarding")] bool? EnableGitHubTelemetryForwarding = null); + [property: JsonPropertyName("enableGitHubTelemetryForwarding")] bool? EnableGitHubTelemetryForwarding = null, + [property: JsonPropertyName("clientInfo")] ConnectHandshakeClientInfo? ClientInfo = null); + + internal record ConnectHandshakeClientInfo( + [property: JsonPropertyName("editorName")] string? EditorName = null, + [property: JsonPropertyName("editorVersion")] string? EditorVersion = null, + [property: JsonPropertyName("extensionName")] string? ExtensionName = null, + [property: JsonPropertyName("extensionVersion")] string? ExtensionVersion = null) + { + /// + /// Maps the public onto the connect wire + /// shape, dropping empty fields. Returns when no + /// identity was supplied so the handshake omits clientInfo and the + /// runtime keeps its default attribution. + /// + public static ConnectHandshakeClientInfo? From(CopilotClientInfo? info) + { + if (info is null) + { + return null; + } + + var editorName = NullIfEmpty(info.ApplicationName); + var editorVersion = NullIfEmpty(info.ApplicationVersion); + var extensionName = NullIfEmpty(info.IntegrationName); + var extensionVersion = NullIfEmpty(info.IntegrationVersion); + if (editorName is null && editorVersion is null && extensionName is null && extensionVersion is null) + { + return null; + } + + return new ConnectHandshakeClientInfo(editorName, editorVersion, extensionName, extensionVersion); + } + + private static string? NullIfEmpty(string? value) => string.IsNullOrEmpty(value) ? null : value; + } + + internal record BuiltinPluginDirectoriesRequest( + string[] Paths); internal record SetForegroundSessionRequest( string SessionId); @@ -2966,6 +3272,8 @@ internal record HooksInvokeResponse( [JsonSerializable(typeof(GetSessionMetadataRequest))] [JsonSerializable(typeof(GetSessionMetadataResponse))] [JsonSerializable(typeof(ConnectHandshakeRequest))] + [JsonSerializable(typeof(ConnectHandshakeClientInfo))] + [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 e22296bccd..ca62ccc5d7 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/FfiRuntimeHost.cs b/dotnet/src/FfiRuntimeHost.cs index a838b9fd17..cc3bccae92 100644 --- a/dotnet/src/FfiRuntimeHost.cs +++ b/dotnet/src/FfiRuntimeHost.cs @@ -17,11 +17,9 @@ namespace GitHub.Copilot; /// and communicating over stdio/TCP. /// /// -/// The Rust host_start export spawns the residual TypeScript worker itself — -/// typically the packaged single-file CLI (copilot --embedded-host, which embeds -/// its own Node) or, for dev, node dist-cli/index.js --embedded-host — so the .NET -/// host never launches Node directly. JSON-RPC frames are pumped across the ABI: writes go -/// to connection_write; inbound frames arrive on a native callback that feeds +/// The Rust host_start export constructs the server synchronously in this +/// process. JSON-RPC frames are pumped across the ABI: writes go to +/// connection_write; inbound frames arrive on a native callback that feeds /// . /// /// The native interop layer has two implementations selected by target framework. On @@ -41,7 +39,7 @@ internal sealed partial class FfiRuntimeHost : IDisposable private const string LibraryName = "copilot_runtime"; private readonly ILogger _logger; - private readonly string _cliEntrypoint; + private readonly string? _cliEntrypoint; private readonly string _libraryPath; private readonly IReadOnlyDictionary? _environment; private readonly IReadOnlyList _args; @@ -53,7 +51,7 @@ internal sealed partial class FfiRuntimeHost : IDisposable private uint _connectionId; private bool _disposed; - private FfiRuntimeHost(string libraryPath, string cliEntrypoint, IReadOnlyDictionary? environment, IReadOnlyList args, ILogger logger) + private FfiRuntimeHost(string libraryPath, string? cliEntrypoint, IReadOnlyDictionary? environment, IReadOnlyList args, ILogger logger) { _libraryPath = libraryPath; _cliEntrypoint = cliEntrypoint; @@ -70,35 +68,22 @@ private FfiRuntimeHost(string libraryPath, string cliEntrypoint, IReadOnlyDictio ?? throw new InvalidOperationException("FfiRuntimeHost has not been started."); /// - /// Loads the cdylib next to the given CLI entrypoint and prepares the FFI host. - /// The entrypoint is either the packaged single-file CLI binary (e.g. - /// runtimes/<rid>/native/copilot) or, for dev, a .js file (e.g. - /// dist-cli/index.js) launched via node. The cdylib is resolved - /// relative to the entrypoint directory, preferring the flat, natural - /// shared-library name the .NET build emits (e.g. libcopilot_runtime.so) - /// and falling back to the dev tarball layout - /// prebuilds/<prebuildsFolder>/runtime.node, where - /// is the napi-rs - /// <node-platform>-<arch> folder name (e.g. win32-x64). + /// Loads the runtime cdylib and prepares the FFI host. /// - public static FfiRuntimeHost Create(string cliEntrypoint, string prebuildsFolder, IReadOnlyDictionary? environment, IReadOnlyList args, ILogger logger) + public static FfiRuntimeHost Create(string libraryPath, string? cliEntrypoint, IReadOnlyDictionary? environment, IReadOnlyList args, ILogger logger) { - var fullEntrypoint = Path.GetFullPath(cliEntrypoint); - var distDir = Path.GetDirectoryName(fullEntrypoint) - ?? throw new InvalidOperationException($"Could not determine directory for '{cliEntrypoint}'."); - - // Bundled .NET layout: flat, natural shared-library name next to the CLI. - var flatLibraryPath = Path.Combine(distDir, GetRuntimeLibraryFileName()); - // Dev/tarball layout: dist-cli/prebuilds/-/runtime.node. - var prebuildsLibraryPath = Path.Combine(distDir, "prebuilds", prebuildsFolder, "runtime.node"); - - var libraryPath = File.Exists(flatLibraryPath) ? flatLibraryPath - : File.Exists(prebuildsLibraryPath) ? prebuildsLibraryPath - : throw new InvalidOperationException( - $"FFI runtime library not found. Looked for '{flatLibraryPath}' and '{prebuildsLibraryPath}'."); - - PrepareNativeLibrary(libraryPath); - return new FfiRuntimeHost(libraryPath, fullEntrypoint, environment, args, logger); + var fullLibraryPath = Path.GetFullPath(libraryPath); + if (!File.Exists(fullLibraryPath)) + { + throw new InvalidOperationException($"FFI runtime library not found at '{fullLibraryPath}'."); + } + PrepareNativeLibrary(fullLibraryPath); + return new FfiRuntimeHost( + fullLibraryPath, + cliEntrypoint is null ? null : Path.GetFullPath(cliEntrypoint), + environment, + args, + logger); } /// @@ -106,7 +91,7 @@ public static FfiRuntimeHost Create(string cliEntrypoint, string prebuildsFolder /// emitted by the .NET build (the .node file renamed to what the Rust cdylib /// would be called on this OS). /// - private static string GetRuntimeLibraryFileName() + internal static string GetRuntimeLibraryFileName() { if (OperatingSystem.IsWindows()) return "copilot_runtime.dll"; if (OperatingSystem.IsMacOS()) return "libcopilot_runtime.dylib"; @@ -114,14 +99,11 @@ private static string GetRuntimeLibraryFileName() } /// - /// Starts the in-process runtime: spawns the CLI worker via the Rust host, - /// waits for readiness, and opens the FFI JSON-RPC connection. + /// Starts the in-process Rust runtime and opens the FFI JSON-RPC connection. /// public async Task StartAsync(CancellationToken cancellationToken) { - // host_start blocks until the worker connects back and signals readiness - // (up to ~30s), and connection_open must run outside any async runtime, so - // perform the blocking FFI handshake on a background thread. + // Keep synchronous native startup off the caller's async context. await Task.Run(() => { var argvJson = BuildArgvJson(_cliEntrypoint, _args); @@ -131,7 +113,7 @@ await Task.Run(() => if (_serverId == 0) { throw new InvalidOperationException( - $"copilot_runtime_host_start failed (library '{_libraryPath}', entrypoint '{_cliEntrypoint}')."); + $"copilot_runtime_host_start failed (library '{_libraryPath}')."); } _connectionId = NativeOpenConnection(_serverId); @@ -154,24 +136,22 @@ await Task.Run(() => } } - private static byte[] BuildArgvJson(string cliEntrypoint, IReadOnlyList args) + private static byte[] BuildArgvJson(string? cliEntrypoint, IReadOnlyList args) { - // A .js entrypoint (dev / dist-cli) is launched via node; the packaged - // single-file CLI binary embeds its own Node and is invoked directly. - var isJsFile = cliEntrypoint.EndsWith(".js", StringComparison.OrdinalIgnoreCase); using var stream = new MemoryStream(); using (var writer = new Utf8JsonWriter(stream)) { writer.WriteStartArray(); - if (isJsFile) + if (cliEntrypoint is not null) { - writer.WriteStringValue("node"); + if (cliEntrypoint.EndsWith(".js", StringComparison.OrdinalIgnoreCase)) + { + writer.WriteStringValue("node"); + } + writer.WriteStringValue(cliEntrypoint); + writer.WriteStringValue("--embedded-host"); + writer.WriteStringValue("--no-auto-update"); } - writer.WriteStringValue(cliEntrypoint); - writer.WriteStringValue("--embedded-host"); - // Pin the worker to the bundled pkg matching the loaded cdylib, instead of - // drifting to a newer version under the user's ~/.copilot/pkg (ABI skew). - writer.WriteStringValue("--no-auto-update"); foreach (var arg in args) { writer.WriteStringValue(arg); diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index 32778850b8..c4428b46f8 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -61,10 +61,35 @@ internal sealed class ConnectResult public string Version { get; set; } = string.Empty; } -/// Parameters for the `server.connect` handshake: an optional connection token and optional connection-level opt-ins (e.g. GitHub telemetry forwarding). +/// Identity of the integrating host, declared once on the `server.connect` handshake so telemetry from this connection is attributed to a single, consistent surface. All fields are optional; omit them to keep the default attribution. +[Experimental(Diagnostics.Experimental)] +internal sealed class ConnectClientInfo +{ + /// Name of the host editor, e.g. `"vscode"`. + [JsonPropertyName("editorName")] + public string? EditorName { get; set; } + + /// Version of the host editor, e.g. `"1.124.2"`. Ignored unless it looks like a version string. + [JsonPropertyName("editorVersion")] + public string? EditorVersion { get; set; } + + /// Name of the Copilot extension within the host, e.g. `"copilot-chat"`. + [JsonPropertyName("extensionName")] + public string? ExtensionName { get; set; } + + /// Version of the Copilot extension within the host, e.g. `"0.54.0"`. Ignored unless it looks like a version string. + [JsonPropertyName("extensionVersion")] + public string? ExtensionVersion { get; set; } +} + +/// Connection-level opt-ins for the `server.connect` handshake. Transport authentication is consumed by the native protocol boundary before dispatch. [Experimental(Diagnostics.Experimental)] internal sealed class ConnectRequest { + /// Identity of the integrating host. Optional; omit it to keep the default attribution. + [JsonPropertyName("clientInfo")] + public ConnectClientInfo? ClientInfo { get; set; } + /// 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,6 +99,69 @@ internal sealed class ConnectRequest public string? Token { get; set; } } +/// One server-discovered hook action from user, repository, plugin, or managed-policy configuration. +[Experimental(Diagnostics.Experimental)] +public sealed class DiscoveredHook +{ + /// Durable content hash used by hook enablement. Identical actions may intentionally share this key. Omitted when changing the user's disabled-hooks setting cannot change the action's current server-discovered state, including managed-policy hooks, session-start prompt actions, actions suppressed by disable-all settings, and projectless plugin actions that require project-directory expansion. + [JsonPropertyName("disableKey")] + public string? DisableKey { get; set; } + + /// Whether this action is enabled under the server-side discovery settings. Concrete sessions may differ because they can add session-specific directories, plugins, or trust. False when its disable key is present in the user's disabled-hooks setting or disable-all settings suppress the action. + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } + + /// Hook event that invokes this action. + [JsonPropertyName("hookType")] + public HookType HookType { get; set; } + + /// Deterministic identifier for this server-discovered action row. It remains stable while the project, origin, source, event, action content, and duplicate ordinal are unchanged. This is row identity, not the key persisted in disabledHooks. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + + /// Configuration tier that contributed this hook action. + [JsonPropertyName("origin")] + public HookOrigin Origin { get; set; } + + /// Input project path for which this server-side action was resolved. Set on every row returned for project-scoped discovery, including repeated user and policy actions. + [JsonPropertyName("projectPath")] + public string? ProjectPath { get; set; } + + /// Human-readable source label, such as a hook file path, settings source, or plugin name. + [JsonPropertyName("source")] + public string? Source { get; set; } +} + +/// Server-discovered hook actions and partial-load diagnostics from user, repository, plugin, and managed-policy sources. Concrete sessions may include additional session-specific hook sources. +[Experimental(Diagnostics.Experimental)] +public sealed class HooksDiscoverResult +{ + /// Errors for hook sources or actions that could not be loaded, making the result partially incomplete. Other valid actions are still returned. Project-resolution and repository-settings errors are prefixed with their project path. + [JsonPropertyName("errors")] + public IList Errors { get => field ??= []; set; } + + /// All discovered hook actions. Byte-identical actions remain separate rows even when they share a disable key. + [JsonPropertyName("hooks")] + public IList Hooks { get => field ??= []; set; } + + /// Non-fatal source-loading warnings. Discovery remains complete for the affected source, although the source had a recoverable issue. Repository-settings warnings are prefixed with their project path when attribution is available. + [JsonPropertyName("warnings")] + public IList Warnings { get => field ??= []; set; } +} + +/// Optional project paths and host-exclusion behavior for server-scoped hook discovery. +[Experimental(Diagnostics.Experimental)] +internal sealed class HooksDiscoverRequest +{ + /// When true, omit host-owned user and plugin hook rows and their diagnostics. Managed-policy hooks and trusted repository hooks remain visible, and host disabledHooks still contribute to each remaining row's effective enabled state. This filters sources rather than simulating a host with no settings. + [JsonPropertyName("excludeHostHooks")] + public bool? ExcludeHostHooks { get; set; } + + /// Optional project directory paths whose trusted repository and project-expanded plugin hooks should be discovered. When omitted or empty, user, managed-policy, and globally enabled installed or explicit plugin hooks are returned without project expansion. + [JsonPropertyName("projectPaths")] + public IList? ProjectPaths { get; set; } +} + /// Active server-driven promotion for a model, including its discount and optional expiry. [Experimental(Diagnostics.Experimental)] public sealed class ModelBillingPromo @@ -93,6 +181,10 @@ public sealed class ModelBillingPromo /// 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; } + + /// Whether the service asked hosts to give this promotion a prominent surface, such as a dedicated banner, in addition to listing it with the model. `true` requests that surface and `false` asks for the model list only. Absent means the service expressed no preference — for example a response that predates the field — so hosts should apply their own default rather than read it as `false`. + [JsonPropertyName("showBanner")] + public bool? ShowBanner { get; set; } } /// Long context tier pricing (available for models with extended context windows). @@ -111,6 +203,10 @@ public sealed class ModelBillingTokenPricesLongContext [JsonPropertyName("cacheReadPrice")] public double? CacheReadPrice { get; set; } + /// AI Credits cost per billing batch of 1-hour cache-write (cache creation) tokens. + [JsonPropertyName("cacheWrite1hPrice")] + public double? CacheWrite1hPrice { get; set; } + /// AI Credits cost per billing batch of cache-write (cache creation) tokens. [JsonPropertyName("cacheWritePrice")] public double? CacheWritePrice { get; set; } @@ -156,6 +252,10 @@ public sealed class ModelBillingTokenPrices [JsonPropertyName("cacheReadPrice")] public double? CacheReadPrice { get; set; } + /// AI Credits cost per billing batch of 1-hour cache-write (cache creation) tokens. + [JsonPropertyName("cacheWrite1hPrice")] + public double? CacheWrite1hPrice { get; set; } + /// AI Credits cost per billing batch of cache-write (cache creation) tokens. [JsonPropertyName("cacheWritePrice")] public double? CacheWritePrice { get; set; } @@ -274,6 +374,19 @@ public sealed class ModelCapabilities public ModelCapabilitiesSupports? Supports { get; set; } } +/// A service-published message about a model, carrying a stable machine-readable code alongside human-readable text. +[Experimental(Diagnostics.Experimental)] +public sealed class ModelMessage +{ + /// Stable machine-readable identifier for the message, such as `client_version_deprecated`. Hosts can key custom presentation off this; unrecognized codes should fall back to displaying `message`. + [JsonPropertyName("code")] + public string Code { get; set; } = string.Empty; + + /// Human-readable message text intended for display to the user. + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; +} + /// Policy state (if applicable). [Experimental(Diagnostics.Experimental)] public sealed class ModelPolicy @@ -287,6 +400,15 @@ public sealed class ModelPolicy public string? Terms { get; set; } } +/// Service-published warning text that hosts should display when presenting a model. +[Experimental(Diagnostics.Experimental)] +public sealed class ModelWarningText +{ + /// Data-retention warning for the model. The text may contain Markdown links and should be rendered as Markdown when supported. + [JsonPropertyName("dataRetention")] + public string? DataRetention { get; set; } +} + /// Copilot model metadata, including identifier, display name, capabilities, policy, billing, reasoning efforts, and picker categories. [Experimental(Diagnostics.Experimental)] public sealed class Model @@ -299,10 +421,18 @@ 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; + /// Informational notices the service published for this model, such as an upcoming change or a recommended alternative. Present only when the service published at least one notice. Hosts should surface these without implying anything is wrong with the model. + [JsonPropertyName("infoMessages")] + public IList? InfoMessages { get; set; } + /// Model capability category for grouping in the model picker. [JsonPropertyName("modelPickerCategory")] public ModelPickerCategory? ModelPickerCategory { get; set; } @@ -319,9 +449,21 @@ public sealed class Model [JsonPropertyName("policy")] public ModelPolicy? Policy { get; set; } + /// Context-window tiers this model offers, when the provider advertises them independently of tiered token pricing. Copilot models carry their tiers in `billing.tokenPrices`; a provider that has no pricing to publish (an agent host reached over AHP, for example) declares them here instead, so the model picker can still offer the tier toggle. + [JsonPropertyName("supportedContextTiers")] + public IList? SupportedContextTiers { get; set; } + /// Supported reasoning effort levels (only present if model supports reasoning effort). [JsonPropertyName("supportedReasoningEfforts")] public IList? SupportedReasoningEfforts { get; set; } + + /// Warnings the service published for this model, such as a deprecated client version. Present only when the service published at least one warning. The model remains usable; hosts should surface these as advisory rather than blocking. + [JsonPropertyName("warningMessages")] + public IList? WarningMessages { get; set; } + + /// Warning text the service requires hosts to surface for this model. Present only when the service published at least one warning. + [JsonPropertyName("warningText")] + public ModelWarningText? WarningText { get; set; } } /// List of Copilot models available to the resolved user, including capabilities and billing metadata. @@ -337,16 +479,20 @@ public sealed class ModelList [Experimental(Diagnostics.Experimental)] internal sealed class 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. + /// GitHub token accepted for compatibility with existing SDK clients. When provided, resolves this token instead of using the current account. [JsonPropertyName("gitHubToken")] public string? GitHubToken { get; set; } + + /// Opaque account identifier returned by `account.getAllUsers`. When omitted, the current account is used. + [JsonPropertyName("selectionId")] + public string? SelectionId { 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. + /// Well-known runtime model ID suitable for provider or provider-model metadata. 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; } @@ -453,12 +599,16 @@ public sealed class AccountGetQuotaResult [Experimental(Diagnostics.Experimental)] internal sealed class AccountGetQuotaRequest { - /// GitHub token for per-user quota lookup. When provided, resolves this token to determine the user's quota instead of using the global auth. + /// GitHub token accepted for compatibility with existing SDK clients. When provided, resolves this token instead of using the current account. [JsonPropertyName("gitHubToken")] public string? GitHubToken { get; set; } + + /// Opaque account identifier returned by `account.getAllUsers`. When omitted, the current account is used. + [JsonPropertyName("selectionId")] + public string? SelectionId { get; set; } } -/// Initial authentication info for the session. +/// Authentication credentials accepted only at native protocol ingress. Runtime outputs use credential-free `AuthIdentity` metadata. /// Polymorphic base type discriminated by type. [Experimental(Diagnostics.Experimental)] [JsonPolymorphic( @@ -467,6 +617,7 @@ internal sealed class AccountGetQuotaRequest [JsonDerivedType(typeof(AuthInfoHmac), "hmac")] [JsonDerivedType(typeof(AuthInfoEnv), "env")] [JsonDerivedType(typeof(AuthInfoToken), "token")] +[JsonDerivedType(typeof(AuthInfoTokenProvider), "token-provider")] [JsonDerivedType(typeof(AuthInfoCopilotApiToken), "copilot-api-token")] [JsonDerivedType(typeof(AuthInfoUser), "user")] [JsonDerivedType(typeof(AuthInfoGhCli), "gh-cli")] @@ -483,23 +634,23 @@ public partial class AuthInfo [Experimental(Diagnostics.Experimental)] public sealed class CopilotUserResponseEndpoints { - /// Gets or sets the api value. + /// Copilot API endpoint URL. [JsonPropertyName("api")] public string? Api { get; set; } - /// Gets or sets the exp value. + /// Experimental-service endpoint URL. [JsonPropertyName("exp")] public string? Exp { get; set; } - /// Gets or sets the origin-tracker value. + /// Origin-tracker endpoint URL. [JsonPropertyName("origin-tracker")] public string? OriginTracker { get; set; } - /// Gets or sets the proxy value. + /// Copilot proxy endpoint URL. [JsonPropertyName("proxy")] public string? Proxy { get; set; } - /// Gets or sets the telemetry value. + /// Copilot telemetry endpoint URL. [JsonPropertyName("telemetry")] public string? Telemetry { get; set; } } @@ -507,11 +658,11 @@ public sealed class CopilotUserResponseEndpoints /// RPC data type for CopilotUserResponseOrganizationListItem operations. public sealed class CopilotUserResponseOrganizationListItem { - /// Gets or sets the login value. + /// GitHub login of the organization. [JsonPropertyName("login")] public string? Login { get; set; } - /// Gets or sets the name value. + /// Display name of the organization. [JsonPropertyName("name")] public string? Name { get; set; } } @@ -801,7 +952,7 @@ public sealed class CopilotUserResponse public bool? TokenBasedBilling { get; set; } } -/// Authentication-info variant for GitHub-internal HMAC auth, carrying the public GitHub host and HMAC secret. +/// Authentication-info input variant for GitHub-internal HMAC auth, carrying the public GitHub host and HMAC secret. /// The hmac variant of . [Experimental(Diagnostics.Experimental)] public partial class AuthInfoHmac : AuthInfo @@ -824,7 +975,7 @@ public partial class AuthInfoHmac : AuthInfo public required string Host { get; set; } } -/// Authentication-info variant for a token sourced from an environment variable, with host, optional login, token, and env var name. +/// Authentication-info input variant for a token sourced from an environment variable, with host, optional login, token, and env var name. /// The env variant of . [Experimental(Diagnostics.Experimental)] public partial class AuthInfoEnv : AuthInfo @@ -856,7 +1007,7 @@ public partial class AuthInfoEnv : AuthInfo public required string Token { get; set; } } -/// Authentication-info variant for SDK-configured token authentication, carrying host and the secret token value. +/// Authentication-info input variant for SDK-configured token authentication, carrying host and the secret token value. /// The token variant of . [Experimental(Diagnostics.Experimental)] public partial class AuthInfoToken : AuthInfo @@ -874,11 +1025,39 @@ public partial class AuthInfoToken : AuthInfo [JsonPropertyName("host")] public required string Host { get; set; } + /// Opaque native GitHub credential registration backing this token identity, when applicable. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("registrationId")] + public string? RegistrationId { get; set; } + /// The token value itself. Treat as a secret. [JsonPropertyName("token")] public required string Token { get; set; } } +/// Authentication-info variant backed by an SDK GitHub token callback. It carries routing metadata but never a plaintext token. +/// The token-provider variant of . +[Experimental(Diagnostics.Experimental)] +public partial class AuthInfoTokenProvider : AuthInfo +{ + /// + [JsonIgnore] + public override string Type => "token-provider"; + + /// Snapshot of the authenticated user's Copilot subscription info, if known. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("copilotUser")] + public CopilotUserResponse? CopilotUser { get; set; } + + /// Authentication host. + [JsonPropertyName("host")] + public required string Host { get; set; } + + /// Opaque SDK callback registration identifier. + [JsonPropertyName("registrationId")] + public required string RegistrationId { get; set; } +} + /// Authentication-info variant for direct Copilot API token auth sourced from environment variables, with public GitHub host. /// The copilot-api-token variant of . [Experimental(Diagnostics.Experimental)] @@ -921,7 +1100,7 @@ public partial class AuthInfoUser : AuthInfo public required string Login { get; set; } } -/// Authentication-info variant for GitHub CLI credentials, carrying host, login, and the `gh auth token` value. +/// Authentication-info input variant for GitHub CLI credentials, carrying host, login, and the `gh auth token` value. /// The gh-cli variant of . [Experimental(Diagnostics.Experimental)] public partial class AuthInfoGhCli : AuthInfo @@ -948,7 +1127,7 @@ public partial class AuthInfoGhCli : AuthInfo public required string Token { get; set; } } -/// Authentication-info variant for API-key authentication to a non-GitHub LLM provider, carrying the secret `apiKey` and host. +/// Authentication-info input variant for API-key authentication to a non-GitHub LLM provider, carrying the secret `apiKey` and host. /// The api-key variant of . [Experimental(Diagnostics.Experimental)] public partial class AuthInfoApiKey : AuthInfo @@ -984,7 +1163,7 @@ public sealed class AccountGetCurrentAuthResult public AuthInfo? AuthInfo { get; set; } } -/// Authenticated account entry returned by `account.getAllUsers`, with auth info and an optional associated token. +/// Authenticated account entry returned by `account.getAllUsers`. [Experimental(Diagnostics.Experimental)] public sealed class AccountAllUsers { @@ -992,6 +1171,10 @@ public sealed class AccountAllUsers [JsonPropertyName("authInfo")] public AuthInfo AuthInfo { get => field ??= new(); set; } + /// Opaque identifier accepted by account and model selection APIs. + [JsonPropertyName("selectionId")] + public string? SelectionId { get; set; } + /// Associated token, if available. [JsonPropertyName("token")] public string? Token { get; set; } @@ -1006,7 +1189,7 @@ public sealed class AccountLoginResult public bool StoredInVault { get; set; } } -/// Credentials to store after successful authentication. +/// Credentials to validate and store. Omit login to resolve the authenticated user from the token. [Experimental(Diagnostics.Experimental)] internal sealed class AccountLoginRequest { @@ -1014,9 +1197,9 @@ internal sealed class AccountLoginRequest [JsonPropertyName("host")] public string Host { get; set; } = string.Empty; - /// User login/username. + /// User login/username. When omitted, the runtime validates the token and resolves the login from GitHub. [JsonPropertyName("login")] - public string Login { get; set; } = string.Empty; + public string? Login { get; set; } /// GitHub authentication token. [JsonPropertyName("token")] @@ -1038,7 +1221,11 @@ internal sealed class AccountLogoutRequest { /// Authentication information for the user to log out. [JsonPropertyName("authInfo")] - public AuthInfo AuthInfo { get => field ??= new(); set; } + public AuthInfo? AuthInfo { get; set; } + + /// Opaque account identifier returned by `account.getAllUsers`. + [JsonPropertyName("selectionId")] + public string? SelectionId { get; set; } } /// Confirmation that the secret values were registered. @@ -1109,9348 +1296,16036 @@ internal sealed class McpDiscoverRequest public string? WorkingDirectory { get; set; } } -/// User-configured MCP servers, keyed by server name. +/// Outcome of an mcp.planInstall call: either a normalised plan, or one typed refusal. Nothing is written in either case. +/// Polymorphic base type discriminated by kind. [Experimental(Diagnostics.Experimental)] -public sealed class McpConfigList +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(McpPlanInstallResultPlanned), "planned")] +[JsonDerivedType(typeof(McpPlanInstallResultNegotiationRefused), "negotiation-refused")] +[JsonDerivedType(typeof(McpPlanInstallResultHandleRejected), "handle-rejected")] +[JsonDerivedType(typeof(McpPlanInstallResultInvalidRequest), "invalid-request")] +[JsonDerivedType(typeof(McpPlanInstallResultAuthenticationRequired), "authentication-required")] +[JsonDerivedType(typeof(McpPlanInstallResultPolicyRejected), "policy-rejected")] +[JsonDerivedType(typeof(McpPlanInstallResultNetworkFailure), "network-failure")] +[JsonDerivedType(typeof(McpPlanInstallResultUnsafeRetrieval), "unsafe-retrieval")] +[JsonDerivedType(typeof(McpPlanInstallResultMalformedCard), "malformed-card")] +[JsonDerivedType(typeof(McpPlanInstallResultContractViolation), "contract-violation")] +[JsonDerivedType(typeof(McpPlanInstallResultUnavailableTransport), "unavailable-transport")] +[JsonDerivedType(typeof(McpPlanInstallResultNotInstallable), "not-installable")] +[JsonDerivedType(typeof(McpPlanInstallResultUnavailable), "unavailable")] +public partial class McpPlanInstallResult { - /// All MCP servers from user config, keyed by name. - [JsonPropertyName("servers")] - public IDictionary Servers { get => field ??= new Dictionary(); set; } + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; } -/// MCP server name and configuration to add to user configuration. + +/// The protocol version and capability set the runtime actually honoured for a successful catalog operation. [Experimental(Diagnostics.Experimental)] -internal sealed class McpConfigAddRequest +public sealed class CatalogNegotiatedContract { - /// MCP server configuration (stdio process or remote HTTP/SSE). - [JsonPropertyName("config")] - public JsonElement Config { get; set; } + /// Wire features the runtime understood for this operation. Always a superset of the caller's required features, because any shortfall is a refusal instead. Operation availability remains a separate typed result. + [JsonPropertyName("grantedCapabilities")] + public IList GrantedCapabilities { get => field ??= []; set; } - /// Unique name for the MCP server. - [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; + /// Protocol version of the runtime that served the request. + [JsonPropertyName("runtimeProtocolVersion")] + public long RuntimeProtocolVersion { get; set; } } -/// MCP server name and replacement configuration to write to user configuration. +/// One change applying the plan would make, described rather than serialised so the configuration payload stays behind the runtime boundary. [Experimental(Diagnostics.Experimental)] -internal sealed class McpConfigUpdateRequest +public sealed class McpPlanConfigurationChange { - /// MCP server configuration (stdio process or remote HTTP/SSE). - [JsonPropertyName("config")] - public JsonElement Config { get; set; } + /// Names of the configuration fields the change would set, without their values. + [JsonPropertyName("changedFields")] + public IList ChangedFields { get => field ??= []; set; } - /// Name of the MCP server to update. - [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] + /// Configuration key the change applies to. [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; + [JsonPropertyName("configKey")] + public string ConfigKey { get; set; } = string.Empty; + + /// Whether the change would create a new entry or modify an existing one. + [JsonPropertyName("operation")] + public McpPlanConfigurationOperation Operation { get; set; } + + /// Scope the change would be written to. + [JsonPropertyName("scope")] + public McpPlanScope Scope { get; set; } + + /// Secret placeholders the written configuration would reference. The constrained placeholder type cannot carry a literal secret value. + [JsonPropertyName("secretReferences")] + public IList SecretReferences { get => field ??= []; set; } } -/// MCP server name to remove from user configuration. +/// Normalised identity of the MCP server a plan targets, independent of how the card spelled it. [Experimental(Diagnostics.Experimental)] -internal sealed class McpConfigRemoveRequest +public sealed class McpPlanResourceIdentity { - /// Name of the MCP server to remove. - [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] + /// Canonical, normalised name of the server, for example `io.github.owner/server`. [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; -} + [JsonPropertyName("canonicalName")] + public string CanonicalName { get; set; } = string.Empty; -/// MCP server names to enable for new sessions. -[Experimental(Diagnostics.Experimental)] -internal sealed class 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. - [JsonPropertyName("names")] - public IList Names { get => field ??= []; set; } + /// Registry identifier of the server, when it came from a registry. + [JsonPropertyName("registryId")] + public string? RegistryId { get; set; } + + /// Local configuration key the server would be recorded under. + [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; + + /// Version advertised by the card, when it declares one. + [JsonPropertyName("version")] + public string? Version { get; set; } } -/// MCP server names to disable for new sessions. +/// Outcome of evaluating the planned server against registry and enterprise policy. Evaluation is read-only. [Experimental(Diagnostics.Experimental)] -internal sealed class McpConfigDisableRequest +public sealed class McpPlanPolicyResult { - /// 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. - [JsonPropertyName("names")] - public IList Names { get => field ??= []; set; } + /// What policy decided for this server. + [JsonPropertyName("decision")] + public McpPlanPolicyDecision Decision { get; set; } + + /// Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. + [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(1000)] + [JsonPropertyName("reason")] + public string? Reason { get; set; } + + /// Which authority produced the decision. + [JsonPropertyName("source")] + public McpPlanPolicySource Source { get; set; } } -/// Installed plugin that contributes a discovered extension. +/// Semantic digest of a strictly parsed and schema-validated JSON MCP card. Both URL-backed and embedded cards are canonicalised with RFC 8785 JSON Canonicalization Scheme, encoded as UTF-8, and hashed with SHA-256. [Experimental(Diagnostics.Experimental)] -public sealed class DiscoveredExtensionPlugin +public sealed class CardDigest { - /// Installed plugin name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Digest algorithm and canonical representation. + [JsonPropertyName("algorithm")] + public CardDigestAlgorithm Algorithm { get; set; } + + /// SHA-256 digest of the RFC 8785 canonical UTF-8 bytes, encoded as exactly 64 lowercase hexadecimal characters. + [RegularExpression("^[0-9a-f]{64}$")] + [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(64)] + [MaxLength(64)] + [JsonPropertyName("value")] + public string Value { get; set; } = string.Empty; } -/// Discovered extension metadata and persistent enablement state. +/// Provenance of the exact validated JSON MCP card content bound privately to a completed plan and its opaque handle. [Experimental(Diagnostics.Experimental)] -public sealed class DiscoveredExtension +public sealed class McpPlanProvenance { - /// 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; + /// Authority associated with the validated card, without path, query, or credentials. Inert untrusted data. + [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("authority")] + public string Authority { 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; + /// Semantic digest of the exact validated JSON content bound to the plan handle. + [JsonPropertyName("cardDigest")] + public CardDigest CardDigest { get => field ??= new(); set; } - /// Containing plugin metadata for plugin-contributed extensions. - [JsonPropertyName("plugin")] - public DiscoveredExtensionPlugin? Plugin { get; set; } + /// JSON MCP media type the validated card was interpreted as. + [JsonPropertyName("mediaType")] + public McpServerCardMediaType MediaType { get; set; } - /// Discovery source. - [JsonPropertyName("source")] - public DiscoveredExtensionSource Source { get; set; } + /// ISO 8601 timestamp at which the runtime completed strict parsing and schema validation of the card content. + [JsonPropertyName("validatedAt")] + public string ValidatedAt { get; set; } = string.Empty; } -/// Extensions discovered from persisted Copilot home state and their effective loading mode. Launch-scoped additional plugins are not included. +/// Where a plan would be written. [Experimental(Diagnostics.Experimental)] -public sealed class DiscoveredExtensions +public sealed class McpPlanTarget { - /// Discovered user and enabled installed-plugin extensions from persisted Copilot home state. - [JsonPropertyName("extensions")] - public IList Extensions { get => field ??= []; set; } + /// Configuration key the server would be recorded under within that scope. + [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("configKey")] + public string ConfigKey { get; set; } = string.Empty; - /// Effective extension loading mode. Defaults to load_and_augment when unset. - [JsonPropertyName("mode")] - public DiscoveredExtensionMode Mode { get; set; } + /// Configuration scope the plan targets. + [JsonPropertyName("scope")] + public McpPlanScope Scope { get; set; } } -/// Source-qualified extension identifiers to persistently enable for future sessions. +/// One eligible way to run the server, represented as a tagged package or remote variant so package identity and endpoint states cannot contradict the install method. +/// Polymorphic base type discriminated by installMethod. [Experimental(Diagnostics.Experimental)] -internal sealed class DiscoveredExtensionsEnableRequest +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "installMethod", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(McpPlanTransportChoicePackage), "package")] +[JsonDerivedType(typeof(McpPlanTransportChoiceRemote), "remote")] +public partial class McpPlanTransportChoice { - /// Source-qualified user or plugin extension IDs to enable. - [JsonPropertyName("ids")] - public IList Ids { get => field ??= []; set; } + /// The type discriminator. + [JsonPropertyName("installMethod")] + public virtual string InstallMethod { get; set; } = string.Empty; } -/// Source-qualified extension identifiers to persistently disable for future sessions. + +/// One non-secret value a transport choice needs, represented as a scalar or enumerated variant so enum values cannot be missing or attached to another type. +/// Polymorphic base type discriminated by kind. [Experimental(Diagnostics.Experimental)] -internal sealed class DiscoveredExtensionsDisableRequest +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(McpPlanRequiredValueScalar), "scalar")] +[JsonDerivedType(typeof(McpPlanRequiredValueEnum), "enum")] +public partial class McpPlanRequiredValue { - /// Source-qualified user or plugin extension IDs to disable. - [JsonPropertyName("ids")] - public IList Ids { get => field ??= []; set; } + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; } -/// Information about an installed plugin tracked in global state. + +/// One non-secret scalar value a transport choice needs before it can be applied. +/// The scalar variant of . [Experimental(Diagnostics.Experimental)] -public sealed class InstalledPluginInfo +public partial class McpPlanRequiredValueScalar : McpPlanRequiredValue { - /// 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. - [JsonPropertyName("directSourceId")] - public string? DirectSourceId { get; set; } + /// + [JsonIgnore] + public override string Kind => "scalar"; - /// Whether the plugin is currently enabled for new sessions. - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } + /// Where the value is applied when the server is launched. + [JsonPropertyName("category")] + public required McpPlanValueCategory Category { get; set; } - /// Marketplace the plugin came from. Empty string ("") for direct repo / URL / local installs. - [JsonPropertyName("marketplace")] - public string Marketplace { get; set; } = string.Empty; + /// Default supplied by the card, when the value can be resolved without input. Presence is the authoritative indication that a default exists. Inert untrusted data. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("defaultValue")] + public string? DefaultValue { get; set; } - /// Plugin name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Human-readable explanation from the card. Inert untrusted text. + [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(1000)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("description")] + public string? Description { get; set; } - /// Installed version (when reported by the plugin manifest). - [JsonPropertyName("version")] - public string? Version { get; set; } -} + /// Whether the value may be supplied more than once. + [JsonPropertyName("isRepeated")] + public required bool IsRepeated { get; set; } -/// Plugins installed in user/global state. -[Experimental(Diagnostics.Experimental)] -public sealed class PluginListResult -{ - /// Installed plugins. - [JsonPropertyName("plugins")] - public IList Plugins { get => field ??= []; set; } + /// Key the value is supplied under. Inert untrusted data. + [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("key")] + public required string Key { get; set; } + + /// Whether the value must be present for the plan to be applicable. + [JsonPropertyName("required")] + public required bool Required { get; set; } + + /// Human-readable label from the card. Inert untrusted text. + [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(200)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("title")] + public string? Title { get; set; } + + /// Scalar type the value must conform to. + [JsonPropertyName("valueType")] + public required McpPlanScalarValueType ValueType { get; set; } } -/// Result of installing a plugin. +/// One enumerated non-secret value a transport choice needs before it can be applied. The permitted values are structurally required. +/// The enum variant of . [Experimental(Diagnostics.Experimental)] -public sealed class PluginInstallResult +public partial class McpPlanRequiredValueEnum : McpPlanRequiredValue { - /// Set when the install path is deprecated (e.g. direct repo / URL / local installs). Callers should surface this to end users. - [JsonPropertyName("deprecationWarning")] - public string? DeprecationWarning { get; set; } + /// + [JsonIgnore] + public override string Kind => "enum"; - /// The newly installed plugin's metadata. - [JsonPropertyName("plugin")] - public InstalledPluginInfo Plugin { get => field ??= new(); set; } + /// Where the value is applied when the server is launched. + [JsonPropertyName("category")] + public required McpPlanValueCategory Category { get; set; } - /// Optional post-install message provided by the plugin (e.g. setup instructions). - [JsonPropertyName("postInstallMessage")] - public string? PostInstallMessage { get; set; } + /// Default supplied by the card, when the value can be resolved without input. Presence is the authoritative indication that a default exists. Inert untrusted data. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("defaultValue")] + public string? DefaultValue { get; set; } - /// Number of skills discovered and installed from the plugin. - [JsonPropertyName("skillsInstalled")] - public long SkillsInstalled { get; set; } -} + /// Human-readable explanation from the card. Inert untrusted text. + [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(1000)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("description")] + public string? Description { get; set; } -/// Plugin source and optional working directory for relative-path resolution. -[Experimental(Diagnostics.Experimental)] -internal sealed class 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. - [JsonPropertyName("source")] - public string Source { get; set; } = string.Empty; + /// Non-empty permitted value set. Inert untrusted data. + [JsonPropertyName("enumValues")] + public required IList EnumValues { get; set; } - /// Working directory used to resolve relative local paths in `source`. Defaults to the server's current working directory. - [JsonPropertyName("workingDirectory")] - public string? WorkingDirectory { get; set; } + /// Whether the value may be supplied more than once. + [JsonPropertyName("isRepeated")] + public required bool IsRepeated { get; set; } + + /// Key the value is supplied under. Inert untrusted data. + [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("key")] + public required string Key { get; set; } + + /// Whether the value must be present for the plan to be applicable. + [JsonPropertyName("required")] + public required bool Required { get; set; } + + /// Human-readable label from the card. Inert untrusted text. + [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(200)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("title")] + public string? Title { get; set; } + + /// Discriminator: the value must be one of `enumValues`. + [JsonPropertyName("valueType")] + public required McpPlanEnumValueType ValueType { get; set; } } -/// Name (or spec) of the plugin to uninstall. +/// A secret a transport choice needs, referenced by placeholder. No secret value ever appears in a plan, and the placeholder resolves against the keychain only when a plan is applied. [Experimental(Diagnostics.Experimental)] -internal sealed class PluginsUninstallRequest +public sealed class McpPlanSecretPlaceholder { - /// Stable source identity for a direct (non-marketplace) install. Disambiguates uninstall when multiple installed plugins share the same name. - [JsonPropertyName("directSourceId")] - public string? DirectSourceId { get; set; } + /// Key the secret is supplied under. Inert untrusted data. + [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("key")] + public string Key { get; set; } = string.Empty; - /// Plugin name or "plugin@marketplace" spec to uninstall. When ambiguous, prefer the fully-qualified spec. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// The runtime-assigned `${secret:<id>}` placeholder written into configuration in place of the value. + [JsonPropertyName("placeholder")] + public string Placeholder { get; set; } = string.Empty; + + /// Human-readable label from the card. Inert untrusted text. + [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(200)] + [JsonPropertyName("title")] + public string? Title { get; set; } } -/// Result of updating a single plugin. +/// An eligible local-package transport choice. Package identity is required and a remote endpoint cannot be represented. +/// The package variant of . [Experimental(Diagnostics.Experimental)] -public sealed class PluginUpdateResult +public partial class McpPlanTransportChoicePackage : McpPlanTransportChoice { - /// Version after the update, when reported by the plugin manifest. - [JsonPropertyName("newVersion")] - public string? NewVersion { get; set; } + /// + [JsonIgnore] + public override string InstallMethod => "package"; - /// Version that was previously installed, when available. - [JsonPropertyName("previousVersion")] - public string? PreviousVersion { get; set; } + /// Stable identifier for this choice within the plan, used to select it when the plan is applied. + [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(128)] + [JsonPropertyName("choiceId")] + public required string ChoiceId { get; set; } - /// Number of skills discovered and installed after the update. - [JsonPropertyName("skillsInstalled")] - public long SkillsInstalled { get; set; } + /// Package identifier. Inert untrusted data. + [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(512)] + [JsonPropertyName("packageIdentifier")] + public required string PackageIdentifier { get; set; } + + /// Packaging ecosystem, for example `oci` or `npm`. + [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(64)] + [JsonPropertyName("packageType")] + public required string PackageType { get; set; } + + /// Typed values this choice requires, excluding secrets. + [JsonPropertyName("requiredValues")] + public required IList RequiredValues { get; set; } + + /// Secrets this choice requires, referenced by placeholder only. + [JsonPropertyName("secretPlaceholders")] + public required IList SecretPlaceholders { get; set; } + + /// Local process transport this package choice would use. + [JsonPropertyName("transport")] + public required McpPlanPackageTransport Transport { get; set; } } -/// Name (or spec) of the plugin to update. +/// An eligible remote-endpoint transport choice. The endpoint is required and package identity cannot be represented. +/// The remote variant of . [Experimental(Diagnostics.Experimental)] -internal sealed class PluginsUpdateRequest +public partial class McpPlanTransportChoiceRemote : McpPlanTransportChoice { - /// Plugin name or "plugin@marketplace" spec to update. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string InstallMethod => "remote"; + + /// Stable identifier for this choice within the plan, used to select it when the plan is applied. + [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(128)] + [JsonPropertyName("choiceId")] + public required string ChoiceId { get; set; } + + /// Endpoint URL. Inert untrusted data. + [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(2048)] + [JsonPropertyName("endpoint")] + public required string Endpoint { get; set; } + + /// Typed values this choice requires, excluding secrets. + [JsonPropertyName("requiredValues")] + public required IList RequiredValues { get; set; } + + /// Secrets this choice requires, referenced by placeholder only. + [JsonPropertyName("secretPlaceholders")] + public required IList SecretPlaceholders { get; set; } + + /// Endpoint transport this remote choice would use. + [JsonPropertyName("transport")] + public required McpPlanRemoteTransport Transport { get; set; } } -/// Per-plugin result from updating all plugins, with versions, skills installed, success flag, and optional error. +/// A normalised, inert description of what installing an MCP server would involve. Carries no raw card, no install specification, and no secret value. [Experimental(Diagnostics.Experimental)] -public sealed class PluginUpdateAllEntry +public sealed class McpInstallPlan { - /// Error message (failure only). - [JsonPropertyName("error")] - public string? Error { get; set; } + /// The configuration changes installing would make, described rather than serialised, so the mutable configuration payload stays behind the runtime boundary. + [JsonPropertyName("configurationChanges")] + public IList ConfigurationChanges { get => field ??= []; set; } - /// Marketplace the plugin came from. Empty string ("") for direct installs. - [JsonPropertyName("marketplace")] - public string Marketplace { get; set; } = string.Empty; + /// Normalised identity of the server the plan would install. + [JsonPropertyName("identity")] + public McpPlanResourceIdentity Identity { get => field ??= new(); set; } - /// Plugin name that was updated. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Opaque, runtime-instance scoped, TTL-bound, single-use handle for this plan. Rejected when stale, replayed, or presented to a different runtime instance. Never logged. + [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("planHandle")] + public string PlanHandle { get; set; } = string.Empty; - /// Version after the update, when available. - [JsonPropertyName("newVersion")] - public string? NewVersion { get; set; } + /// ISO 8601 timestamp after which the plan handle is stale and will be rejected. Abandoning a plan needs no call: an unused handle simply expires, so cancellation before commit is side-effect free. + [JsonPropertyName("planHandleExpiresAt")] + public string PlanHandleExpiresAt { get; set; } = string.Empty; - /// Previously installed version, when available. - [JsonPropertyName("previousVersion")] - public string? PreviousVersion { get; set; } + /// Outcome of evaluating the server against registry and enterprise policy. + [JsonPropertyName("policy")] + public McpPlanPolicyResult Policy { get => field ??= new(); set; } - /// Number of skills installed after the update (success only). - [JsonPropertyName("skillsInstalled")] - public long? SkillsInstalled { get; set; } + /// Origin and semantic digest of the exact validated JSON MCP card content bound to this plan. + [JsonPropertyName("provenance")] + public McpPlanProvenance Provenance { get => field ??= new(); set; } - /// Whether the update succeeded for this plugin. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// Identifier of the choice the runtime would pick by default. Omitted when there is no eligible transport, or when the runtime expresses no preference. + [JsonPropertyName("recommendedTransportChoiceId")] + public string? RecommendedTransportChoiceId { get; set; } + + /// Whether applying this plan would require an MCP reload to take effect. Planning itself never reloads. + [JsonPropertyName("reloadRequired")] + public bool ReloadRequired { get; set; } + + /// Whether the plan cannot be applied without further input, because a required value has no default or a secret must be supplied. + [JsonPropertyName("requiresInteractiveConfiguration")] + public bool RequiresInteractiveConfiguration { get; set; } + + /// Configuration scope and key the plan would write to. + [JsonPropertyName("target")] + public McpPlanTarget Target { get => field ??= new(); set; } + + /// Every eligible transport, so a host can present an explicit choice. A completed plan always has at least one; when none is eligible, planning returns `CatalogUnavailableTransportError` instead. + [JsonPropertyName("transportChoices")] + public IList TransportChoices { get => field ??= []; set; } } -/// Result of updating all installed plugins. +/// A computed MCP install plan. Nothing has been applied: the plan describes what installing would change, and the plan handle is what a later apply operation would consume. +/// The planned variant of . [Experimental(Diagnostics.Experimental)] -public sealed class PluginUpdateAllResult +public partial class McpPlanInstallResultPlanned : McpPlanInstallResult { - /// Per-plugin update results in deterministic order. - [JsonPropertyName("results")] - public IList Results { get => field ??= []; set; } + /// + [JsonIgnore] + public override string Kind => "planned"; + + /// Protocol version and capabilities the runtime honoured. + [JsonPropertyName("negotiated")] + public required CatalogNegotiatedContract Negotiated { get; set; } + + /// The normalised plan. + [JsonPropertyName("plan")] + public required McpInstallPlan Plan { get; set; } } -/// Plugin names (or specs) to enable. +/// The caller's protocol version or required capabilities cannot be honoured. Returned instead of a partial or ambiguous success. +/// The negotiation-refused variant of . [Experimental(Diagnostics.Experimental)] -internal sealed class PluginsEnableRequest +public partial class McpPlanInstallResultNegotiationRefused : McpPlanInstallResult { - /// 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. - [JsonPropertyName("names")] - public IList Names { get => field ??= []; set; } + /// + [JsonIgnore] + public override string Kind => "negotiation-refused"; + + /// Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. + [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(1000)] + [JsonPropertyName("message")] + public required string Message { get; set; } + + /// Lowest caller protocol version this runtime will serve. + [JsonPropertyName("minimumSupportedProtocolVersion")] + public required long MinimumSupportedProtocolVersion { get; set; } + + /// Whether the version or the capability set was the problem. + [JsonPropertyName("reason")] + public required CatalogNegotiationRefusedReason Reason { get; set; } + + /// Protocol version of the runtime that refused the request. + [JsonPropertyName("runtimeProtocolVersion")] + public required long RuntimeProtocolVersion { get; set; } + + /// Every wire feature this runtime understands, so the caller can retry within that contract. This list does not imply that every deployment has enabled every operation. + [JsonPropertyName("supportedCapabilities")] + public required IList SupportedCapabilities { get; set; } + + /// The subset of the caller's bounded extensible capability identifiers this runtime cannot honour. + [JsonPropertyName("unsupportedCapabilities")] + public required IList UnsupportedCapabilities { get; set; } } -/// Plugin names (or specs) to disable. +/// A presented handle was not accepted. Handles are runtime-instance scoped, TTL-bound, and single-use, so each way of failing is reported distinctly. +/// The handle-rejected variant of . [Experimental(Diagnostics.Experimental)] -internal sealed class PluginsDisableRequest +public partial class McpPlanInstallResultHandleRejected : McpPlanInstallResult { - /// 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. - [JsonPropertyName("names")] - public IList Names { get => field ??= []; set; } + /// + [JsonIgnore] + public override string Kind => "handle-rejected"; + + /// Which kind of handle was presented. + [JsonPropertyName("handleType")] + public required CatalogHandleType HandleType { get; set; } + + /// Human-readable explanation, safe to surface. Never contains the handle itself, nor a query, URL, or secret. + [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(1000)] + [JsonPropertyName("message")] + public required string Message { get; set; } + + /// Why the handle was rejected. + [JsonPropertyName("reason")] + public required CatalogHandleRejectionReason Reason { get; set; } } -/// Registered marketplace summary. +/// The request was rejected before any work was done, because a bounded field fell outside its permitted range or a required field was unusable. +/// The invalid-request variant of . [Experimental(Diagnostics.Experimental)] -public sealed class MarketplaceInfo +public partial class McpPlanInstallResultInvalidRequest : McpPlanInstallResult { - /// True when this is a default marketplace shipped with the runtime. Defaults are not removable. - [JsonPropertyName("isDefault")] - public bool? IsDefault { get; set; } + /// + [JsonIgnore] + public override string Kind => "invalid-request"; - /// Marketplace name (matches the @marketplace suffix in plugin specs). - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Which request field was rejected. + [JsonPropertyName("field")] + public required CatalogInvalidRequestField Field { get; set; } - /// Human-readable description of where the marketplace data is fetched from (e.g. "GitHub: owner/repo"). - [JsonPropertyName("source")] - public string Source { get; set; } = string.Empty; + /// Human-readable explanation, safe to surface. Never echoes the offending value, nor a query, URL, handle, or secret. + [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(1000)] + [JsonPropertyName("message")] + public required string Message { get; set; } } -/// All registered marketplaces, including built-in defaults. +/// An optional catalog authentication exchange did not establish the caller's identity. Anonymous search remains supported; this refusal is reserved for an operation that cannot continue after the attempted exchange. It is distinct from `policy-rejected` and from a network failure, and the reason identifies the recovery action. +/// The authentication-required variant of . [Experimental(Diagnostics.Experimental)] -public sealed class MarketplaceListResult +public partial class McpPlanInstallResultAuthenticationRequired : McpPlanInstallResult { - /// Registered marketplaces. - [JsonPropertyName("marketplaces")] - public IList Marketplaces { get => field ??= []; set; } + /// + [JsonIgnore] + public override string Kind => "authentication-required"; + + /// Human-readable explanation, safe to surface. Never contains a credential or token, nor a query, URL, handle, or secret. + [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(1000)] + [JsonPropertyName("message")] + public required string Message { get; set; } + + /// Why authentication failed. Only an expired credential justifies attempting a silent refresh; an absent or rejected credential requires sign-in. + [JsonPropertyName("reason")] + public required CatalogAuthenticationRequiredReason Reason { get; set; } } -/// Result of registering a new marketplace. +/// Registry or enterprise policy refused the operation. +/// The policy-rejected variant of . [Experimental(Diagnostics.Experimental)] -public sealed class MarketplaceAddResult +public partial class McpPlanInstallResultPolicyRejected : McpPlanInstallResult { - /// Final name of the marketplace as resolved from its manifest. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "policy-rejected"; + + /// Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. + [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(1000)] + [JsonPropertyName("message")] + public required string Message { get; set; } + + /// Which authority produced the decision. + [JsonPropertyName("source")] + public required McpPlanPolicySource Source { get; set; } } -/// Marketplace source and optional working directory for relative-path resolution. +/// The runtime could not reach the catalog authority or retrieve a card. Covers being offline as well as transport-level failure. +/// The network-failure variant of . [Experimental(Diagnostics.Experimental)] -internal sealed class PluginsMarketplacesAddRequest +public partial class McpPlanInstallResultNetworkFailure : McpPlanInstallResult { - /// 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. - [JsonPropertyName("source")] - public string Source { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "network-failure"; - /// Working directory used to resolve relative local paths in `source`. Defaults to the server's current working directory. - [JsonPropertyName("workingDirectory")] - public string? WorkingDirectory { get; set; } + /// Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. + [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(1000)] + [JsonPropertyName("message")] + public required string Message { get; set; } + + /// Categorised failure, low cardinality so it can be aggregated without carrying a URL. + [JsonPropertyName("reason")] + public required CatalogNetworkFailureReason Reason { get; set; } + + /// Bounded cooldown in seconds before another catalog request should be attempted, when the authority supplied a numeric Retry-After value or the runtime applied its documented fallback. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("retryAfterSeconds")] + public int? RetryAfterSeconds { get; set; } + + /// HTTP status code, when the failure was a rejected response. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("statusCode")] + public int? StatusCode { get; set; } } -/// Outcome of the remove attempt, including dependent-plugin info when applicable. +/// Retrieval was refused by the runtime's hardened fetch boundary before any request left the process, or before a redirect was followed. +/// The unsafe-retrieval variant of . [Experimental(Diagnostics.Experimental)] -public sealed class MarketplaceRemoveResult +public partial class McpPlanInstallResultUnsafeRetrieval : McpPlanInstallResult { - /// Names of installed plugins that prevented removal. Populated only when `removed=false`. - [JsonPropertyName("dependentPlugins")] - public IList? DependentPlugins { get; set; } + /// + [JsonIgnore] + public override string Kind => "unsafe-retrieval"; - /// True when the marketplace was actually removed. False when removal was skipped because the marketplace has dependent plugins and `force` was not set. - [JsonPropertyName("removed")] - public bool Removed { get; set; } + /// Human-readable explanation, safe to surface. Never contains the refused URL, nor a query, handle, or secret. + [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(1000)] + [JsonPropertyName("message")] + public required string Message { get; set; } + + /// Which control refused the retrieval, low cardinality so it can be aggregated without carrying a URL. + [JsonPropertyName("reason")] + public required CatalogUnsafeRetrievalReason Reason { get; set; } } -/// Name of the marketplace to remove and an optional force flag. +/// A card could not be parsed or did not satisfy its declared media type's schema. +/// The malformed-card variant of . [Experimental(Diagnostics.Experimental)] -internal sealed class PluginsMarketplacesRemoveRequest +public partial class McpPlanInstallResultMalformedCard : McpPlanInstallResult { - /// 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. - [JsonPropertyName("force")] - public bool? Force { get; set; } + /// + [JsonIgnore] + public override string Kind => "malformed-card"; - /// Marketplace name to remove. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Media type the card was interpreted as, when it declared one this runtime recognises. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("mediaType")] + public CatalogMediaType? MediaType { get; set; } + + /// Human-readable explanation, safe to surface. Never echoes card content, nor a query, URL, handle, or secret. + [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(1000)] + [JsonPropertyName("message")] + public required string Message { get; set; } + + /// How the card failed validation. + [JsonPropertyName("reason")] + public required CatalogMalformedCardReason Reason { get; set; } } -/// Plugin entry advertised by a marketplace. +/// An upstream catalog response broke the wire contract. Most importantly, every result must carry exactly one of a URL or embedded data: a result carrying both, or neither, is refused here rather than being guessed at. +/// The contract-violation variant of . [Experimental(Diagnostics.Experimental)] -public sealed class MarketplacePluginInfo +public partial class McpPlanInstallResultContractViolation : McpPlanInstallResult { - /// Short description from the marketplace catalog, when present. - [JsonPropertyName("description")] - public string? Description { get; set; } + /// + [JsonIgnore] + public override string Kind => "contract-violation"; - /// Plugin name as listed in the marketplace catalog. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Human-readable explanation, safe to surface. Never echoes response content, nor a query, URL, handle, or secret. + [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(1000)] + [JsonPropertyName("message")] + public required string Message { get; set; } + + /// Which rule the response broke. + [JsonPropertyName("reason")] + public required CatalogContractViolationReason Reason { get; set; } } -/// Plugins advertised by the marketplace. +/// No transport this runtime can use is available for the requested server. +/// The unavailable-transport variant of . [Experimental(Diagnostics.Experimental)] -public sealed class MarketplaceBrowseResult +public partial class McpPlanInstallResultUnavailableTransport : McpPlanInstallResult { - /// Plugins advertised by the marketplace. - [JsonPropertyName("plugins")] - public IList Plugins { get => field ??= []; set; } + /// + [JsonIgnore] + public override string Kind => "unavailable-transport"; + + /// Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. + [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(1000)] + [JsonPropertyName("message")] + public required string Message { get; set; } + + /// Why no transport could be offered. + [JsonPropertyName("reason")] + public required CatalogUnavailableTransportReason Reason { get; set; } } -/// Name of the marketplace whose plugin catalog to fetch. +/// The candidate is discoverable but cannot be installed. `application/ai-skill` resolves here, because it stays searchable while remaining typed non-installable. +/// The not-installable variant of . [Experimental(Diagnostics.Experimental)] -internal sealed class PluginsMarketplacesBrowseRequest +public partial class McpPlanInstallResultNotInstallable : McpPlanInstallResult { - /// Marketplace name to browse. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "not-installable"; + + /// Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. + [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(1000)] + [JsonPropertyName("message")] + public required string Message { get; set; } + + /// Why the candidate cannot be installed. + [JsonPropertyName("reason")] + public required CatalogNotInstallableReason Reason { get; set; } } -/// Per-marketplace refresh result, including marketplace name, success flag, and optional failure error. +/// The operation is not available on this runtime. Distinct from a network failure: nothing was attempted. +/// The unavailable variant of . [Experimental(Diagnostics.Experimental)] -public sealed class MarketplaceRefreshEntry +public partial class McpPlanInstallResultUnavailable : McpPlanInstallResult { - /// Error message (failure only). - [JsonPropertyName("error")] - public string? Error { get; set; } + /// + [JsonIgnore] + public override string Kind => "unavailable"; - /// Marketplace name that was refreshed. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. + [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(1000)] + [JsonPropertyName("message")] + public required string Message { get; set; } - /// Whether the refresh succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// Why the operation is unavailable. + [JsonPropertyName("reason")] + public required CatalogUnavailableReason Reason { get; set; } } -/// Result of refreshing one or more marketplace catalogs. +/// The protocol version and capability set a caller requires, supplied on every catalog request so negotiation cannot be skipped by omission. [Experimental(Diagnostics.Experimental)] -public sealed class MarketplaceRefreshResult +public sealed class CatalogClientContract { - /// Per-marketplace refresh results in deterministic order. - [JsonPropertyName("results")] - public IList Results { get => field ??= []; set; } + /// SDK protocol version the caller was generated against. A caller below the runtime's minimum supported version is refused rather than served a partial result. + [JsonPropertyName("protocolVersion")] + public long ProtocolVersion { get; set; } + + /// Wire features the caller requires the runtime to understand. Identifiers are bounded but extensible so a newer caller can negotiate with an older runtime. Requiring an unknown feature yields a typed refusal listing what is understood, never a partial grant. A grant does not promise that a deployment has enabled the operation; typed unavailable results report that separately. + [JsonPropertyName("requiredCapabilities")] + public IList RequiredCapabilities { get => field ??= []; set; } } -/// RPC data type for PluginsMarketplacesRefresh operations. +/// What an install plan is computed from: a candidate handle from a previous search, or a card supplied directly. +/// Polymorphic base type discriminated by kind. [Experimental(Diagnostics.Experimental)] -internal sealed class PluginsMarketplacesRefreshRequest +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(McpPlanInstallSourceCandidate), "candidate")] +[JsonDerivedType(typeof(McpPlanInstallSourceCard), "card")] +public partial class McpPlanInstallSource { - /// Marketplace name to refresh. When omitted, every registered marketplace is refreshed. - [JsonPropertyName("name")] - public string? Name { get; set; } + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; } -/// Server-side skill metadata, including name, description, source, enabled/invocable state, path, project path, and argument hint. + +/// Plan from a candidate returned by a previous catalog search. +/// The candidate variant of . [Experimental(Diagnostics.Experimental)] -public sealed class ServerSkill +public partial class McpPlanInstallSourceCandidate : McpPlanInstallSource { - /// Optional freeform hint describing the skill's expected arguments, from the `argument-hint` frontmatter field. - [JsonPropertyName("argumentHint")] - public string? ArgumentHint { get; set; } + /// + [JsonIgnore] + public override string Kind => "candidate"; - /// Description of what the skill does. - [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; + /// Single-use candidate handle. Consumed by this call, so a replay of the same handle is rejected. + [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(256)] + [JsonPropertyName("candidateHandle")] + public required string CandidateHandle { get; set; } - /// Whether the skill is currently enabled (based on global config). - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } + /// The runtime- or authority-minted `searchId` returned with the search that produced this candidate. A search implementation binds it to private candidate-handle context; a planning implementation must verify that context before returning a plan. The unavailable planning implementation in this contract layer validates presence but does not claim the verification has occurred. It identifies a search rather than a person and must never be joined with user identity to re-identify anyone. + [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(64)] + [JsonPropertyName("searchId")] + public required string SearchId { get; set; } +} - /// Unique identifier for the skill. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; +/// A card supplied directly by the caller. Exactly one of a URL or embedded data, encoded structurally so neither both nor neither can be expressed. +/// Polymorphic base type discriminated by kind. +[Experimental(Diagnostics.Experimental)] +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(McpServerCardReferenceUrl), "url")] +[JsonDerivedType(typeof(McpServerCardReferenceEmbedded), "embedded")] +public partial class McpServerCardReference +{ + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} - /// Absolute path to the skill file. - [JsonPropertyName("path")] - public string? Path { get; set; } - /// The project path this skill belongs to (only for project/inherited skills). - [JsonPropertyName("projectPath")] - public string? ProjectPath { get; set; } +/// An MCP server card to be retrieved from a URL through the runtime's hardened fetch boundary. +/// The url variant of . +[Experimental(Diagnostics.Experimental)] +public partial class McpServerCardReferenceUrl : McpServerCardReference +{ + /// + [JsonIgnore] + public override string Kind => "url"; - /// Source location type (e.g., project, personal-copilot, plugin, builtin). - [JsonPropertyName("source")] - public SkillSource Source { get; set; } + /// Media type the card is expected to conform to. + [JsonPropertyName("mediaType")] + public required McpServerCardMediaType MediaType { get; set; } - /// Whether the skill can be invoked by the user as a slash command. - [JsonPropertyName("userInvocable")] - public bool UserInvocable { get; set; } + /// Card URL. Retrieved only through the runtime's hardened boundary, with scheme, credential, address-range, redirect, timeout, and response-size controls applied. Never logged. + [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(2048)] + [JsonPropertyName("url")] + public required string Url { get; set; } } -/// Skills discovered across global and project sources. +/// An MCP server card supplied inline as an inert document. +/// The embedded variant of . [Experimental(Diagnostics.Experimental)] -public sealed class ServerSkillList +public partial class McpServerCardReferenceEmbedded : McpServerCardReference { - /// 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. - [JsonPropertyName("errors")] - public IList? Errors { get; set; } + /// + [JsonIgnore] + public override string Kind => "embedded"; - /// All discovered skills across all sources. - [JsonPropertyName("skills")] - public IList Skills { get => field ??= []; set; } + /// The card document verbatim, treated as inert untrusted bytes. The runtime parses and validates it; the host is not expected to interpret it. Never logged. + [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(1048576)] + [JsonPropertyName("data")] + public required string Data { get; set; } + + /// Media type the card is expected to conform to. + [JsonPropertyName("mediaType")] + public required McpServerCardMediaType MediaType { get; set; } } -/// Optional project paths and additional skill directories to include in discovery. +/// Plan from a card supplied directly by the caller, without a preceding search. +/// The card variant of . [Experimental(Diagnostics.Experimental)] -internal sealed class SkillsDiscoverRequest +public partial class McpPlanInstallSourceCard : McpPlanInstallSource { - /// When true, omit skills from the host's global sources (personal, custom, plugin, and built-in), returning only project-scoped skills. For multitenant deployments. - [JsonPropertyName("excludeHostSkills")] - public bool? ExcludeHostSkills { get; set; } - - /// Optional list of project directory paths to scan for project-scoped skills. - [JsonPropertyName("projectPaths")] - public IList? ProjectPaths { get; set; } + /// + [JsonIgnore] + public override string Kind => "card"; - /// Optional list of additional skill directory paths to include. - [JsonPropertyName("skillDirectories")] - public IList? SkillDirectories { get; set; } + /// The card to plan from: exactly one of a URL or embedded data. + [JsonPropertyName("card")] + public required McpServerCardReference Card { get; set; } } -/// Canonical directory where skills can be discovered or created, with scope, preference, and optional project path. +/// A side-effect-free request for an MCP install plan. Computing a plan never writes configuration, stores a secret, or reloads MCP servers. [Experimental(Diagnostics.Experimental)] -public sealed class SkillDiscoveryPath +internal sealed class McpPlanInstallRequest { - /// Absolute path of the create/discovery target (may not exist on disk yet). - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; - - /// 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. - [JsonPropertyName("preferredForCreation")] - public bool PreferredForCreation { get; set; } - - /// The input project path this directory was derived from (only for project scope). - [JsonPropertyName("projectPath")] - public string? ProjectPath { get; set; } + /// Protocol version and capabilities the caller requires. + [JsonPropertyName("contract")] + public CatalogClientContract Contract { get => field ??= new(); set; } - /// Which tier this directory belongs to. + /// Configuration scope the plan targets. Defaults to user scope when omitted. [JsonPropertyName("scope")] - public SkillDiscoveryScope Scope { get; set; } + public McpPlanScope? Scope { get; set; } + + /// What to plan: either a candidate handle from a previous search, or a card supplied directly. + [JsonPropertyName("source")] + public McpPlanInstallSource Source { get => field ??= new(); set; } } -/// Canonical locations where skills can be created so the runtime will recognize them. +/// User-configured MCP servers, keyed by server name. [Experimental(Diagnostics.Experimental)] -public sealed class SkillDiscoveryPathList +public sealed class McpConfigList { - /// Canonical skill create/discovery directories, in priority order. - [JsonPropertyName("paths")] - public IList Paths { get => field ??= []; set; } + /// All MCP servers from user config, keyed by name. + [JsonPropertyName("servers")] + public IDictionary Servers { get => field ??= new Dictionary(); set; } } -/// Optional project paths to enumerate. +/// MCP server name and configuration to add to user configuration. [Experimental(Diagnostics.Experimental)] -internal sealed class SkillsGetDiscoveryPathsRequest +internal sealed class McpConfigAddRequest { - /// When true, omit the host's personal and custom skill directories, leaving only project directories. For multitenant deployments. - [JsonPropertyName("excludeHostSkills")] - public bool? ExcludeHostSkills { get; set; } + /// MCP server configuration (stdio process or remote HTTP/SSE). + [JsonPropertyName("config")] + public JsonElement Config { get; set; } - /// Optional list of project directory paths. When omitted or empty, only personal and custom directories are returned. - [JsonPropertyName("projectPaths")] - public IList? ProjectPaths { get; set; } + /// Unique name for the MCP server. + [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; } -/// Skill names to mark as disabled in global configuration, replacing any previous list. +/// MCP server name and replacement configuration to write to user configuration. [Experimental(Diagnostics.Experimental)] -internal sealed class SkillsConfigSetDisabledSkillsRequest +internal sealed class McpConfigUpdateRequest { - /// List of skill names to disable. - [JsonPropertyName("disabledSkills")] - public IList DisabledSkills { get => field ??= []; set; } + /// MCP server configuration (stdio process or remote HTTP/SSE). + [JsonPropertyName("config")] + public JsonElement Config { get; set; } + + /// Name of the MCP server to update. + [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; } -/// Agent metadata, including identifiers, display details, source, tools, model, MCP servers, skills, and file path. +/// MCP server name to remove from user configuration. [Experimental(Diagnostics.Experimental)] -public sealed class AgentInfo +internal sealed class McpConfigRemoveRequest { - /// Description of the agent's purpose. - [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; - - /// Human-readable display name. - [JsonPropertyName("displayName")] - public string DisplayName { get; set; } = string.Empty; + /// Name of the MCP server to remove. + [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; +} - /// Stable identifier for selection. For most agents this is the same as `name`; for plugin/builtin agents it may differ. Always populated; defaults to `name` when no distinct id was assigned. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; +/// MCP server names to enable for new sessions. +[Experimental(Diagnostics.Experimental)] +internal sealed class 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. + [JsonPropertyName("names")] + public IList Names { get => field ??= []; set; } +} - /// MCP server configurations attached to this agent, keyed by server name. Server config shape mirrors the MCP `mcpServers` schema. - [Experimental(Diagnostics.Experimental)] - [JsonPropertyName("mcpServers")] - public IDictionary? McpServers { get; set; } +/// MCP server names to disable for new sessions. +[Experimental(Diagnostics.Experimental)] +internal sealed class 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. + [JsonPropertyName("names")] + public IList Names { get => field ??= []; set; } +} - /// 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; } +/// 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; +} - /// Name of the agent. Use `id` as the stable selection identifier. +/// 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 local file path of the agent definition. Only set for file-based agents loaded from disk; remote agents do not have a path. + /// Absolute path to the extension entry module, suitable for revealing it in a file manager. [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; } + public string Path { get; set; } = string.Empty; - /// Skill names preloaded into this agent's context. Omitted means none. - [JsonPropertyName("skills")] - public IList? Skills { get; set; } + /// Containing plugin metadata for plugin-contributed extensions. + [JsonPropertyName("plugin")] + public DiscoveredExtensionPlugin? Plugin { get; set; } - /// Where the agent definition was loaded from. + /// Discovery source. [JsonPropertyName("source")] - public AgentInfoSource? Source { get; set; } + public DiscoveredExtensionSource Source { get; set; } +} - /// Allowed tool names for this agent. Empty array means none; omitted means inherit defaults. - [JsonPropertyName("tools")] - public IList? Tools { 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; } - /// Whether the agent can be selected directly by the user. Agents marked `false` are subagent-only. - [JsonPropertyName("userInvocable")] - public bool? UserInvocable { get; set; } + /// Effective extension loading mode. Defaults to load_and_augment when unset. + [JsonPropertyName("mode")] + public DiscoveredExtensionMode Mode { get; set; } } -/// Agents discovered across user, project, plugin, and remote sources. +/// Source-qualified extension identifiers to persistently enable for future sessions. [Experimental(Diagnostics.Experimental)] -public sealed class ServerAgentList +internal sealed class DiscoveredExtensionsEnableRequest { - /// All discovered agents across all sources. - [JsonPropertyName("agents")] - public IList Agents { get => field ??= []; set; } + /// Source-qualified user or plugin extension IDs to enable. + [JsonPropertyName("ids")] + public IList Ids { get => field ??= []; set; } } -/// Optional project paths to include in agent discovery. +/// Source-qualified extension identifiers to persistently disable for future sessions. [Experimental(Diagnostics.Experimental)] -internal sealed class AgentsDiscoverRequest +internal sealed class DiscoveredExtensionsDisableRequest { - /// When true, omit the host's agents (the user-level agent directory and all plugin agents), leaving only project and remote agents. For multitenant deployments. - [JsonPropertyName("excludeHostAgents")] - public bool? ExcludeHostAgents { get; set; } + /// Source-qualified user or plugin extension IDs to disable. + [JsonPropertyName("ids")] + public IList Ids { get => field ??= []; set; } +} - /// Optional list of project directory paths to scan for project-scoped agents. When omitted or empty, only user/plugin/remote-independent agents are returned (no project scan). - [JsonPropertyName("projectPaths")] - public IList? ProjectPaths { get; set; } +/// Outcome of a catalog.search call: either bounded inert candidates, or one typed refusal. Never a partial success. +/// Polymorphic base type discriminated by kind. +[Experimental(Diagnostics.Experimental)] +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(CatalogSearchResultSucceeded), "succeeded")] +[JsonDerivedType(typeof(CatalogSearchResultNegotiationRefused), "negotiation-refused")] +[JsonDerivedType(typeof(CatalogSearchResultUnsupportedKind), "unsupported-kind")] +[JsonDerivedType(typeof(CatalogSearchResultInvalidRequest), "invalid-request")] +[JsonDerivedType(typeof(CatalogSearchResultAuthenticationRequired), "authentication-required")] +[JsonDerivedType(typeof(CatalogSearchResultPolicyRejected), "policy-rejected")] +[JsonDerivedType(typeof(CatalogSearchResultNetworkFailure), "network-failure")] +[JsonDerivedType(typeof(CatalogSearchResultUnsafeRetrieval), "unsafe-retrieval")] +[JsonDerivedType(typeof(CatalogSearchResultMalformedCard), "malformed-card")] +[JsonDerivedType(typeof(CatalogSearchResultContractViolation), "contract-violation")] +[JsonDerivedType(typeof(CatalogSearchResultUnavailable), "unavailable")] +public partial class CatalogSearchResult +{ + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; } -/// Canonical directory where custom agents can be discovered or created, with scope, preference, and optional project path. + +/// One inert catalog result, represented as an MCP server or discovery-only AI skill variant so kind, media type, provenance, and installability cannot contradict each other. +/// Polymorphic base type discriminated by kind. [Experimental(Diagnostics.Experimental)] -public sealed class AgentDiscoveryPath +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(CatalogCandidateMcpServer), "mcp-server")] +[JsonDerivedType(typeof(CatalogCandidateAiSkill), "ai-skill")] +public partial class CatalogCandidate { - /// Absolute path of the search/create directory (may not exist on disk yet). - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} - /// Whether this is the canonical directory to create a new agent in its tier. At most one entry per tier is preferred. - [JsonPropertyName("preferredForCreation")] - public bool PreferredForCreation { get; set; } - /// The input project path this directory was derived from (only for project scope). - [JsonPropertyName("projectPath")] - public string? ProjectPath { get; set; } +/// Where and when an MCP server catalog reference was observed. Discovery provenance deliberately carries no content digest because search does not establish the exact validated content a later plan will bind. +[Experimental(Diagnostics.Experimental)] +public sealed class CatalogMcpServerCandidateProvenance +{ + /// Host of the catalog authority that advertised the reference, without path, query, or credentials. Inert untrusted data. + [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("authority")] + public string Authority { get; set; } = string.Empty; - /// Which tier this directory belongs to. - [JsonPropertyName("scope")] - public AgentDiscoveryPathScope Scope { get; set; } + /// JSON MCP media type advertised for the referenced card. + [JsonPropertyName("mediaType")] + public McpServerCardMediaType MediaType { get; set; } + + /// ISO 8601 timestamp at which the runtime observed the catalog reference. This is not a retrieval or validation timestamp. + [JsonPropertyName("observedAt")] + public string ObservedAt { get; set; } = string.Empty; } -/// Canonical locations where custom agents can be created so the runtime will recognize them. +/// Where a candidate's card came from. Exactly one of a URL or embedded data: the union has no variant carrying both, and no variant carrying neither, so the rule holds structurally rather than by validation. +/// Polymorphic base type discriminated by kind. [Experimental(Diagnostics.Experimental)] -public sealed class AgentDiscoveryPathList +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(CatalogCandidateSourceUrl), "url")] +[JsonDerivedType(typeof(CatalogCandidateSourceEmbedded), "embedded")] +public partial class CatalogCandidateSource { - /// Canonical agent create/discovery directories, in priority order. - [JsonPropertyName("paths")] - public IList Paths { get => field ??= []; set; } + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; } -/// Optional project paths to include when enumerating agent discovery directories. + +/// Candidate whose card is retrieved from a URL through the runtime's hardened fetch boundary. +/// The url variant of . [Experimental(Diagnostics.Experimental)] -internal sealed class AgentsGetDiscoveryPathsRequest +public partial class CatalogCandidateSourceUrl : CatalogCandidateSource { - /// When true, omit the host's user-level agent directory, leaving only project directories. For multitenant deployments (mirrors `discover`'s `excludeHostAgents`). - [JsonPropertyName("excludeHostAgents")] - public bool? ExcludeHostAgents { get; set; } + /// + [JsonIgnore] + public override string Kind => "url"; - /// Optional list of project directory paths. When omitted or empty, only the user-level directory is returned. - [JsonPropertyName("projectPaths")] - public IList? ProjectPaths { get; set; } + /// Card URL as advertised. Inert untrusted data: the runtime retrieves it only through its own hardened boundary, and it is never logged. + [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("url")] + public required string Url { get; set; } } -/// Loaded instruction source for a session, including path, content, category, location, applicability, and optional description. +/// Candidate whose card reference arrived inline. The document and its content-derived properties stay behind the runtime boundary. +/// The embedded variant of . [Experimental(Diagnostics.Experimental)] -public sealed class InstructionSource +public partial class CatalogCandidateSourceEmbedded : CatalogCandidateSource { - /// Glob pattern(s) from frontmatter — when set, this instruction applies only to matching files. - [JsonPropertyName("applyTo")] - public IList? ApplyTo { get; set; } - - /// Raw content of the instruction file. - [JsonPropertyName("content")] - public string Content { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "embedded"; +} - /// When true, this source starts disabled and must be toggled on by the user. - [JsonPropertyName("defaultDisabled")] - public bool? DefaultDisabled { get; set; } +/// An inert MCP server catalog result. Every free-text field is untrusted external data and must never be treated as an instruction, and the handle is the only way to refer to the candidate in a later operation. +/// The mcp-server variant of . +[Experimental(Diagnostics.Experimental)] +public partial class CatalogCandidateMcpServer : CatalogCandidate +{ + /// + [JsonIgnore] + public override string Kind => "mcp-server"; - /// Short description (body after frontmatter) for use in instruction tables. + /// Description taken verbatim from the card. Inert untrusted text. + [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(1000)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("description")] public string? Description { get; set; } - /// Unique identifier for this source (used for toggling). - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// Display name taken verbatim from the card. Inert untrusted text. + [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(200)] + [JsonPropertyName("displayName")] + public required string DisplayName { get; set; } - /// Human-readable label. - [JsonPropertyName("label")] - public string Label { get; set; } = string.Empty; + /// Opaque, runtime-instance scoped, TTL-bound, single-use handle for this candidate. Carries no readable information and is rejected when stale, replayed, or presented to a different runtime instance. Never logged. + [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("handle")] + public required string Handle { get; set; } - /// Where this source lives — used for UI grouping. - [JsonPropertyName("location")] - public InstructionSourceLocation Location { get; set; } + /// ISO 8601 timestamp after which the handle is stale and will be rejected. + [JsonPropertyName("handleExpiresAt")] + public required string HandleExpiresAt { get; set; } - /// 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; } + /// Whether this MCP server can be planned for installation, and if policy prevents it. + [JsonPropertyName("installability")] + public required CatalogMcpServerInstallability Installability { get; set; } - /// File path relative to repo or absolute for home. - [JsonPropertyName("sourcePath")] - public string SourcePath { get; set; } = string.Empty; + /// JSON MCP media type of the underlying card. + [JsonPropertyName("mediaType")] + public required McpServerCardMediaType MediaType { get; set; } - /// Category of instruction source — used for merge logic. - [JsonPropertyName("type")] - public InstructionSourceType Type { get; set; } -} + /// Where the catalog reference was observed, without the card itself or any content digest. + [JsonPropertyName("provenance")] + public required CatalogMcpServerCandidateProvenance Provenance { get; set; } -/// Instruction sources discovered across user, repository, and plugin sources. -[Experimental(Diagnostics.Experimental)] -public sealed class ServerInstructionSourceList -{ - /// All discovered instruction sources. - [JsonPropertyName("sources")] - public IList Sources { get => field ??= []; set; } + /// Publisher taken verbatim from the card. Inert untrusted text. + [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(200)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("publisher")] + public string? Publisher { get; set; } + + /// Where the card came from: exactly one of a URL or embedded data, encoded as a tagged union so neither both nor neither can be represented. + [JsonPropertyName("source")] + public required CatalogCandidateSource Source { get; set; } } -/// Optional project paths to include in instruction discovery. +/// Where and when an AI skill catalog reference was observed. Discovery provenance deliberately carries no content digest because search does not establish the exact validated content a later plan will bind. [Experimental(Diagnostics.Experimental)] -internal sealed class InstructionsDiscoverRequest +public sealed class CatalogAiSkillCandidateProvenance { - /// 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. - [JsonPropertyName("excludeHostInstructions")] - public bool? ExcludeHostInstructions { get; set; } + /// Host of the catalog authority that advertised the reference, without path, query, or credentials. Inert untrusted data. + [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("authority")] + public string Authority { get; set; } = string.Empty; - /// 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). - [JsonPropertyName("projectPaths")] - public IList? ProjectPaths { get; set; } + /// Media type advertised for the referenced AI skill card. + [JsonPropertyName("mediaType")] + public string MediaType { get; set; } = string.Empty; + + /// ISO 8601 timestamp at which the runtime observed the catalog reference. This is not a retrieval or validation timestamp. + [JsonPropertyName("observedAt")] + public string ObservedAt { get; set; } = string.Empty; } -/// Canonical file or directory where custom instructions can be discovered or created, with location, kind, preference, and project path. +/// An inert AI skill catalog result. AI skills are discovery-only and cannot be represented as installable through this surface. +/// The ai-skill variant of . [Experimental(Diagnostics.Experimental)] -public sealed class InstructionDiscoveryPath +public partial class CatalogCandidateAiSkill : CatalogCandidate { - /// Whether the target is a single file or a directory of instruction files. - [JsonPropertyName("kind")] - public InstructionDiscoveryPathKind Kind { get; set; } - - /// Which tier this target belongs to. - [JsonPropertyName("location")] - public InstructionDiscoveryPathLocation Location { get; set; } + /// + [JsonIgnore] + public override string Kind => "ai-skill"; - /// Absolute path of the file or directory (may not exist on disk yet). - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// Description taken verbatim from the card. Inert untrusted text. + [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(1000)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("description")] + public string? Description { get; set; } - /// Whether this is the canonical target to create new instructions in its tier. At most one entry per tier is preferred. - [JsonPropertyName("preferredForCreation")] - public bool PreferredForCreation { get; set; } + /// Display name taken verbatim from the card. Inert untrusted text. + [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(200)] + [JsonPropertyName("displayName")] + public required string DisplayName { get; set; } - /// The input project path this target was derived from (only for repository targets). - [JsonPropertyName("projectPath")] - public string? ProjectPath { get; set; } -} + /// Opaque, runtime-instance scoped, TTL-bound, single-use handle for this candidate. Carries no readable information and is rejected when stale, replayed, or presented to a different runtime instance. Never logged. + [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("handle")] + public required string Handle { get; set; } -/// Canonical files and directories where custom instructions can be created so the runtime will recognize them. -[Experimental(Diagnostics.Experimental)] -public sealed class InstructionDiscoveryPathList -{ - /// Canonical instruction create/discovery files and directories, in priority order. - [JsonPropertyName("paths")] - public IList Paths { get => field ??= []; set; } -} + /// ISO 8601 timestamp after which the handle is stale and will be rejected. + [JsonPropertyName("handleExpiresAt")] + public required string HandleExpiresAt { get; set; } -/// Optional project paths to include when enumerating instruction discovery targets. -[Experimental(Diagnostics.Experimental)] -internal sealed class InstructionsGetDiscoveryPathsRequest -{ - /// When true, omit the host's user-level instruction targets, leaving only repository targets. For multitenant deployments (mirrors `discover`'s `excludeHostInstructions`). - [JsonPropertyName("excludeHostInstructions")] - public bool? ExcludeHostInstructions { get; set; } + /// AI skills are discovery-only and cannot be installed through this surface. + [JsonPropertyName("installability")] + public required string Installability { get; set; } - /// Optional list of project directory paths. When omitted or empty, only the user-level targets are returned. - [JsonPropertyName("projectPaths")] - public IList? ProjectPaths { get; set; } -} + /// Media type of the underlying AI skill card. + [JsonPropertyName("mediaType")] + public required string MediaType { get; set; } -/// A literal choice the command input accepts, with a human-facing description. -[Experimental(Diagnostics.Experimental)] -public sealed class SlashCommandInputChoice -{ - /// Human-readable description shown alongside the choice. - [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; + /// Where the catalog reference was observed, without the card itself or any content digest. + [JsonPropertyName("provenance")] + public required CatalogAiSkillCandidateProvenance Provenance { get; set; } - /// The literal choice value (e.g. 'on', 'off', 'show'). - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Publisher taken verbatim from the card. Inert untrusted text. + [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(200)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("publisher")] + public string? Publisher { get; set; } + + /// Where the card came from: exactly one of a URL or embedded data, encoded as a tagged union so neither both nor neither can be represented. + [JsonPropertyName("source")] + public required CatalogCandidateSource Source { get; set; } } -/// Optional unstructured input hint. +/// A completed catalog search: inert candidate summaries, each carrying a single-use handle. +/// The succeeded variant of . [Experimental(Diagnostics.Experimental)] -public sealed class SlashCommandInput +public partial class CatalogSearchResultSucceeded : CatalogSearchResult { - /// Optional literal choices the input accepts, each with a human-facing description; clients may render these as selectable options. - [JsonPropertyName("choices")] - public IList? Choices { get; set; } + /// + [JsonIgnore] + public override string Kind => "succeeded"; - /// Optional completion hint for the input (e.g. 'directory' for filesystem path completion). - [JsonPropertyName("completion")] - public SlashCommandInputCompletion? Completion { get; set; } + /// Matching candidates, never more than the requested limit. All text is inert untrusted data. + [JsonPropertyName("candidates")] + public required IList Candidates { get; set; } - /// Hint to display when command input has not been provided. - [JsonPropertyName("hint")] - public string Hint { get; set; } = string.Empty; + /// Protocol version and capabilities the runtime honoured. + [JsonPropertyName("negotiated")] + public required CatalogNegotiatedContract Negotiated { get; set; } - /// When true, clients should pass the full text after the command name as a single argument rather than splitting on whitespace. - [JsonPropertyName("preserveMultilineInput")] - public bool? PreserveMultilineInput { get; set; } + /// Pseudonymous identifier for this search, issued by the runtime or by the catalog authority it queried and never by the caller, so it cannot be forged or replayed to attribute an install to a search that never happened. Always present on a success, so a result set can be tied to the installs it leads to. It identifies a search rather than a person: it is derived from no user, account, device, or query data, and must never be joined with user identity to re-identify anyone. + [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(64)] + [JsonPropertyName("searchId")] + public required string SearchId { get; set; } - /// When true, the command requires non-empty input; clients should render the input hint as required. - [JsonPropertyName("required")] - public bool? Required { get; set; } + /// Whether further matches existed beyond the requested limit. + [JsonPropertyName("truncated")] + public required bool Truncated { get; set; } } -/// Slash-command metadata with name, aliases, description, kind, input hint, execution allowance, and schedulability. +/// The caller's protocol version or required capabilities cannot be honoured. Returned instead of a partial or ambiguous success. +/// The negotiation-refused variant of . [Experimental(Diagnostics.Experimental)] -public sealed class SlashCommandInfo +public partial class CatalogSearchResultNegotiationRefused : CatalogSearchResult { - /// Canonical aliases without leading slashes. - [JsonPropertyName("aliases")] - public IList? Aliases { get; set; } - - /// Whether the command may run while an agent turn is active. - [JsonPropertyName("allowDuringAgentExecution")] - public bool AllowDuringAgentExecution { get; set; } + /// + [JsonIgnore] + public override string Kind => "negotiation-refused"; - /// Human-readable command description. - [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; + /// Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. + [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(1000)] + [JsonPropertyName("message")] + public required string Message { get; set; } - /// Whether the command is experimental. - [JsonPropertyName("experimental")] - public bool? Experimental { get; set; } + /// Lowest caller protocol version this runtime will serve. + [JsonPropertyName("minimumSupportedProtocolVersion")] + public required long MinimumSupportedProtocolVersion { get; set; } - /// Optional unstructured input hint. - [JsonPropertyName("input")] - public SlashCommandInput? Input { get; set; } + /// Whether the version or the capability set was the problem. + [JsonPropertyName("reason")] + public required CatalogNegotiationRefusedReason Reason { get; set; } - /// Coarse command category for grouping and behavior: runtime built-in, skill-backed command, or SDK/client-owned command. - [JsonPropertyName("kind")] - public SlashCommandKind Kind { get; set; } + /// Protocol version of the runtime that refused the request. + [JsonPropertyName("runtimeProtocolVersion")] + public required long RuntimeProtocolVersion { get; set; } - /// Canonical command name without a leading slash. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Every wire feature this runtime understands, so the caller can retry within that contract. This list does not imply that every deployment has enabled every operation. + [JsonPropertyName("supportedCapabilities")] + public required IList SupportedCapabilities { get; set; } - /// Whether the command may be the target of `/every` / `/after` schedules. Resolution happens at every tick, so only set this when the command is safe to re-invoke and produces an agent prompt. - [JsonPropertyName("schedulable")] - public bool? Schedulable { get; set; } + /// The subset of the caller's bounded extensible capability identifiers this runtime cannot honour. + [JsonPropertyName("unsupportedCapabilities")] + public required IList UnsupportedCapabilities { get; set; } } -/// Slash commands available in the session, after applying any include/exclude filters. +/// The request asked for a candidate kind this runtime does not serve. +/// The unsupported-kind variant of . [Experimental(Diagnostics.Experimental)] -public sealed class CommandList +public partial class CatalogSearchResultUnsupportedKind : CatalogSearchResult { - /// Commands available in this session. - [JsonPropertyName("commands")] - public IList Commands { get => field ??= []; set; } -} + /// + [JsonIgnore] + public override string Kind => "unsupported-kind"; -/// A single user setting's effective value alongside its default, so consumers can render settings left at their default. -[Experimental(Diagnostics.Experimental)] -public sealed class UserSettingMetadata -{ - /// The centrally-known default for this setting (null when no default is registered). - [JsonPropertyName("default")] - public JsonElement Default { get; set; } + /// Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. + [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(1000)] + [JsonPropertyName("message")] + public required string Message { get; set; } - /// 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. - [JsonPropertyName("isDefault")] - public bool IsDefault { get; set; } + /// The kinds from the request that are not supported. + [JsonPropertyName("requestedKinds")] + public required IList RequestedKinds { get; set; } - /// The effective value: the user's value if set, otherwise the default. - [JsonPropertyName("value")] - public JsonElement Value { get; set; } + /// Every candidate kind this runtime can serve. + [JsonPropertyName("supportedKinds")] + public required IList SupportedKinds { get; set; } } -/// 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. +/// The request was rejected before any work was done, because a bounded field fell outside its permitted range or a required field was unusable. +/// The invalid-request variant of . [Experimental(Diagnostics.Experimental)] -public sealed class UserSettingsGetResult +public partial class CatalogSearchResultInvalidRequest : CatalogSearchResult { - /// Every known user setting keyed by setting name, each with its effective value, default, and whether it is at the default. - [JsonPropertyName("settings")] - public IDictionary Settings { get => field ??= new Dictionary(); set; } + /// + [JsonIgnore] + public override string Kind => "invalid-request"; + + /// Which request field was rejected. + [JsonPropertyName("field")] + public required CatalogInvalidRequestField Field { get; set; } + + /// Human-readable explanation, safe to surface. Never echoes the offending value, nor a query, URL, handle, or secret. + [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(1000)] + [JsonPropertyName("message")] + public required string Message { get; set; } } -/// Outcome of writing user settings. +/// An optional catalog authentication exchange did not establish the caller's identity. Anonymous search remains supported; this refusal is reserved for an operation that cannot continue after the attempted exchange. It is distinct from `policy-rejected` and from a network failure, and the reason identifies the recovery action. +/// The authentication-required variant of . [Experimental(Diagnostics.Experimental)] -public sealed class UserSettingsSetResult +public partial class CatalogSearchResultAuthenticationRequired : CatalogSearchResult { - /// 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. - [JsonPropertyName("shadowedKeys")] - public IList ShadowedKeys { get => field ??= []; set; } + /// + [JsonIgnore] + public override string Kind => "authentication-required"; + + /// Human-readable explanation, safe to surface. Never contains a credential or token, nor a query, URL, handle, or secret. + [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(1000)] + [JsonPropertyName("message")] + public required string Message { get; set; } + + /// Why authentication failed. Only an expired credential justifies attempting a silent refresh; an absent or rejected credential requires sign-in. + [JsonPropertyName("reason")] + public required CatalogAuthenticationRequiredReason Reason { get; set; } } -/// 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. +/// Registry or enterprise policy refused the operation. +/// The policy-rejected variant of . [Experimental(Diagnostics.Experimental)] -internal sealed class UserSettingsSetRequest +public partial class CatalogSearchResultPolicyRejected : CatalogSearchResult { - /// Partial user settings to write, as a free-form object keyed by setting name. - [JsonPropertyName("settings")] - public JsonElement Settings { get; set; } + /// + [JsonIgnore] + public override string Kind => "policy-rejected"; + + /// Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. + [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(1000)] + [JsonPropertyName("message")] + public required string Message { get; set; } + + /// Which authority produced the decision. + [JsonPropertyName("source")] + public required McpPlanPolicySource Source { get; set; } } -/// Indicates whether the calling client was registered as the session filesystem provider. +/// The runtime could not reach the catalog authority or retrieve a card. Covers being offline as well as transport-level failure. +/// The network-failure variant of . [Experimental(Diagnostics.Experimental)] -public sealed class SessionFsSetProviderResult +public partial class CatalogSearchResultNetworkFailure : CatalogSearchResult { - /// Whether the provider was set successfully. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// + [JsonIgnore] + public override string Kind => "network-failure"; + + /// Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. + [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(1000)] + [JsonPropertyName("message")] + public required string Message { get; set; } + + /// Categorised failure, low cardinality so it can be aggregated without carrying a URL. + [JsonPropertyName("reason")] + public required CatalogNetworkFailureReason Reason { get; set; } + + /// Bounded cooldown in seconds before another catalog request should be attempted, when the authority supplied a numeric Retry-After value or the runtime applied its documented fallback. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("retryAfterSeconds")] + public int? RetryAfterSeconds { get; set; } + + /// HTTP status code, when the failure was a rejected response. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("statusCode")] + public int? StatusCode { get; set; } } -/// Optional capabilities declared by the provider. +/// Retrieval was refused by the runtime's hardened fetch boundary before any request left the process, or before a redirect was followed. +/// The unsafe-retrieval variant of . [Experimental(Diagnostics.Experimental)] -public sealed class SessionFsSetProviderCapabilities +public partial class CatalogSearchResultUnsafeRetrieval : CatalogSearchResult { - /// Whether the provider supports SQLite query/exists operations. - [JsonPropertyName("sqlite")] - public bool? Sqlite { get; set; } + /// + [JsonIgnore] + public override string Kind => "unsafe-retrieval"; + + /// Human-readable explanation, safe to surface. Never contains the refused URL, nor a query, handle, or secret. + [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(1000)] + [JsonPropertyName("message")] + public required string Message { get; set; } + + /// Which control refused the retrieval, low cardinality so it can be aggregated without carrying a URL. + [JsonPropertyName("reason")] + public required CatalogUnsafeRetrievalReason Reason { get; set; } } -/// Initial working directory, session-state path layout, and path conventions used to register the calling SDK client as the session filesystem provider. +/// A card could not be parsed or did not satisfy its declared media type's schema. +/// The malformed-card variant of . [Experimental(Diagnostics.Experimental)] -internal sealed class SessionFsSetProviderRequest +public partial class CatalogSearchResultMalformedCard : CatalogSearchResult { - /// Optional capabilities declared by the provider. - [JsonPropertyName("capabilities")] - public SessionFsSetProviderCapabilities? Capabilities { get; set; } + /// + [JsonIgnore] + public override string Kind => "malformed-card"; - /// Path conventions used by this filesystem. - [JsonPropertyName("conventions")] - public SessionFsSetProviderConventions Conventions { get; set; } + /// Media type the card was interpreted as, when it declared one this runtime recognises. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("mediaType")] + public CatalogMediaType? MediaType { get; set; } - /// Initial working directory for sessions. - [JsonPropertyName("initialCwd")] - public string InitialCwd { get; set; } = string.Empty; + /// Human-readable explanation, safe to surface. Never echoes card content, nor a query, URL, handle, or secret. + [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(1000)] + [JsonPropertyName("message")] + public required string Message { get; set; } - /// Path within each session's SessionFs where the runtime stores files for that session. - [JsonPropertyName("sessionStatePath")] - public string SessionStatePath { get; set; } = string.Empty; + /// How the card failed validation. + [JsonPropertyName("reason")] + public required CatalogMalformedCardReason Reason { get; set; } } -/// Indicates whether the calling client was registered as the LLM inference provider. +/// An upstream catalog response broke the wire contract. Most importantly, every result must carry exactly one of a URL or embedded data: a result carrying both, or neither, is refused here rather than being guessed at. +/// The contract-violation variant of . [Experimental(Diagnostics.Experimental)] -public sealed class LlmInferenceSetProviderResult +public partial class CatalogSearchResultContractViolation : CatalogSearchResult { - /// Whether the provider was set successfully. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// + [JsonIgnore] + public override string Kind => "contract-violation"; + + /// Human-readable explanation, safe to surface. Never echoes response content, nor a query, URL, handle, or secret. + [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(1000)] + [JsonPropertyName("message")] + public required string Message { get; set; } + + /// Which rule the response broke. + [JsonPropertyName("reason")] + public required CatalogContractViolationReason Reason { get; set; } } -/// Whether the start frame was accepted. +/// The operation is not available on this runtime. Distinct from a network failure: nothing was attempted. +/// The unavailable variant of . [Experimental(Diagnostics.Experimental)] -public sealed class LlmInferenceHttpResponseStartResult +public partial class CatalogSearchResultUnavailable : CatalogSearchResult { - /// True when the response start was matched to a pending request; false when unknown. - [JsonPropertyName("accepted")] - public bool Accepted { get; set; } + /// + [JsonIgnore] + public override string Kind => "unavailable"; + + /// Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. + [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(1000)] + [JsonPropertyName("message")] + public required string Message { get; set; } + + /// Why the operation is unavailable. + [JsonPropertyName("reason")] + public required CatalogUnavailableReason Reason { get; set; } } -/// Response head. +/// A bounded catalog search. Both the query length and the result count are capped by the schema so a caller cannot request an unbounded scan. [Experimental(Diagnostics.Experimental)] -internal sealed class LlmInferenceHttpResponseStartRequest +internal sealed class CatalogSearchRequest { - /// Gets or sets the headers value. - [JsonPropertyName("headers")] - public IDictionary> Headers { get => field ??= new Dictionary>(); set; } + /// Protocol version and capabilities the caller requires. + [JsonPropertyName("contract")] + public CatalogClientContract Contract { get => field ??= new(); set; } - /// Matches the requestId from the originating httpRequestStart frame. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// Restrict results to these candidate kinds. When omitted, every kind the runtime supports is searched. + [JsonPropertyName("kinds")] + public IList? Kinds { get; set; } - /// HTTP status code. - [JsonPropertyName("status")] - public long Status { get; set; } + /// Maximum number of candidates to return. Defaults to 10 when omitted. + [JsonPropertyName("limit")] + public int? Limit { get; set; } - /// Optional HTTP status reason phrase. - [JsonPropertyName("statusText")] - public string? StatusText { get; set; } + /// Free-text search query. Persisted as tool input for session continuity, but omitted from telemetry. + [RegularExpression("\\S")] + [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(256)] + [JsonPropertyName("query")] + public string Query { get; set; } = string.Empty; } -/// Whether the chunk was accepted. +/// Information about an installed plugin tracked in global state. [Experimental(Diagnostics.Experimental)] -public sealed class LlmInferenceHttpResponseChunkResult +public sealed class InstalledPluginInfo { - /// True when the chunk was matched to a pending request; false when unknown. - [JsonPropertyName("accepted")] - public bool Accepted { get; set; } + /// 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. + [JsonPropertyName("directSourceId")] + public string? DirectSourceId { get; set; } + + /// Whether the plugin is currently enabled for new sessions. + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } + + /// Absolute path of the marketplace directory a live plugin was resolved from. Present only on live, never-persisted records — a plugin belonging to a directory/local marketplace, which is loaded from its real directory on every pass instead of a copy under the installed-plugins cache. Its presence is what marks a listed plugin as live: such a plugin is always present on disk, so `enabled` is its only meaningful state and it is never "not installed". + [JsonPropertyName("installedFrom")] + public string? InstalledFrom { get; set; } + + /// Marketplace the plugin came from. Empty string ("") for direct repo / URL / local installs. + [JsonPropertyName("marketplace")] + public string Marketplace { get; set; } = string.Empty; + + /// Plugin name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Installed version (when reported by the plugin manifest). + [JsonPropertyName("version")] + public string? Version { get; set; } } -/// Set to terminate the response with a transport-level failure. Implies end-of-stream; any further chunks for this requestId are ignored. +/// Plugins installed in user/global state. [Experimental(Diagnostics.Experimental)] -public sealed class LlmInferenceHttpResponseChunkError +public sealed class PluginListResult { - /// Optional machine-readable error code. - [JsonPropertyName("code")] - public string? Code { get; set; } - - /// Human-readable failure description. - [JsonPropertyName("message")] - public string Message { get; set; } = string.Empty; + /// Installed plugins. + [JsonPropertyName("plugins")] + public IList Plugins { get => field ??= []; set; } } -/// A response body chunk or terminal error. +/// Result of installing a plugin. [Experimental(Diagnostics.Experimental)] -internal sealed class LlmInferenceHttpResponseChunkRequest +public sealed class PluginInstallResult { - /// When true, `data` is base64-encoded bytes. When absent or false, `data` is UTF-8 text. - [JsonPropertyName("binary")] - public bool? Binary { get; set; } - - /// 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). - [JsonPropertyName("data")] - public string Data { get; set; } = string.Empty; + /// Set when the install path is deprecated (e.g. direct repo / URL / local installs). Callers should surface this to end users. + [JsonPropertyName("deprecationWarning")] + public string? DeprecationWarning { get; set; } - /// 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. - [JsonPropertyName("end")] - public bool? End { get; set; } + /// The newly installed plugin's metadata. + [JsonPropertyName("plugin")] + public InstalledPluginInfo Plugin { get => field ??= new(); set; } - /// Set to terminate the response with a transport-level failure. Implies end-of-stream; any further chunks for this requestId are ignored. - [JsonPropertyName("error")] - public LlmInferenceHttpResponseChunkError? Error { get; set; } + /// Optional post-install message provided by the plugin (e.g. setup instructions). + [JsonPropertyName("postInstallMessage")] + public string? PostInstallMessage { get; set; } - /// Matches the requestId from the originating httpRequestStart frame. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// Number of skills discovered and installed from the plugin. + [JsonPropertyName("skillsInstalled")] + public long SkillsInstalled { get; set; } } -/// Pre-resolved working-directory context for session startup. +/// Plugin source and optional working directory for relative-path resolution. [Experimental(Diagnostics.Experimental)] -public sealed class SessionContext +internal sealed class PluginsInstallRequest { - /// Active git branch. - [JsonPropertyName("branch")] - public string? Branch { get; set; } - - /// Most recent working directory for this session. - [JsonPropertyName("cwd")] - public string Cwd { get; set; } = string.Empty; - - /// Git repository root, if the cwd was inside a git repo. - [JsonPropertyName("gitRoot")] - public string? GitRoot { get; set; } - - /// Repository host type. - [JsonPropertyName("hostType")] - public SessionContextHostType? HostType { get; set; } + /// 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. + [JsonPropertyName("source")] + public string Source { get; set; } = string.Empty; - /// Repository slug in `owner/name` form, when known. - [JsonPropertyName("repository")] - public string? Repository { get; set; } + /// Working directory used to resolve relative local paths in `source`. Defaults to the server's current working directory. + [JsonPropertyName("workingDirectory")] + public string? WorkingDirectory { get; set; } } -/// GitHub repository the remote session belongs to. +/// Name (or spec) of the plugin to uninstall. [Experimental(Diagnostics.Experimental)] -public sealed class RemoteSessionMetadataRepository +internal sealed class PluginsUninstallRequest { - /// Branch associated with the remote session. - [JsonPropertyName("branch")] - public string Branch { get; set; } = string.Empty; + /// Stable source identity for a direct (non-marketplace) install. Disambiguates uninstall when multiple installed plugins share the same name. + [JsonPropertyName("directSourceId")] + public string? DirectSourceId { get; set; } - /// Repository name. + /// Plugin name or "plugin@marketplace" spec to uninstall. When ambiguous, prefer the fully-qualified spec. [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; - - /// Repository owner. - [JsonPropertyName("owner")] - public string Owner { get; set; } = string.Empty; } -/// Remote session metadata for the session to hand off (typically obtained from `sessions.list` with `source: "remote"`). +/// Result of updating a single plugin. [Experimental(Diagnostics.Experimental)] -public sealed class RemoteSessionMetadataValue +public sealed class PluginUpdateResult { - /// Most recent working directory context. - [JsonPropertyName("context")] - public SessionContext? Context { get; set; } + /// Version after the update, when reported by the plugin manifest. + [JsonPropertyName("newVersion")] + public string? NewVersion { get; set; } - /// Always true for remote sessions. - [JsonPropertyName("isRemote")] - public bool IsRemote { get; set; } + /// Version that was previously installed, when available. + [JsonPropertyName("previousVersion")] + public string? PreviousVersion { get; set; } - /// Last-modified time as an ISO 8601 timestamp. - [JsonPropertyName("modifiedTime")] - public string ModifiedTime { get; set; } = string.Empty; + /// Number of skills discovered and installed after the update. + [JsonPropertyName("skillsInstalled")] + public long SkillsInstalled { get; set; } +} - /// Optional human-friendly name set via /rename. +/// Name (or spec) of the plugin to update. +[Experimental(Diagnostics.Experimental)] +internal sealed class PluginsUpdateRequest +{ + /// Plugin name or "plugin@marketplace" spec to update. [JsonPropertyName("name")] - public string? Name { get; set; } - - /// Pull request number associated with the session. - [JsonPropertyName("pullRequestNumber")] - public long? PullRequestNumber { get; set; } - - /// Backing remote session IDs (most recent first). - [JsonPropertyName("remoteSessionIds")] - public IList RemoteSessionIds { get => field ??= []; set; } - - /// GitHub repository the remote session belongs to. - [JsonPropertyName("repository")] - public RemoteSessionMetadataRepository Repository { get => field ??= new(); set; } + public string Name { get; set; } = string.Empty; +} - /// Original remote resource identifier (task ID or PR node ID). - [JsonPropertyName("resourceId")] - public string? ResourceId { get; set; } +/// Per-plugin result from updating all plugins, with versions, skills installed, success flag, and optional error. +[Experimental(Diagnostics.Experimental)] +public sealed class PluginUpdateAllEntry +{ + /// Error message (failure only). + [JsonPropertyName("error")] + public string? Error { get; set; } - /// Stable session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Marketplace the plugin came from. Empty string ("") for direct installs. + [JsonPropertyName("marketplace")] + public string Marketplace { get; set; } = string.Empty; - /// Deadline (ISO 8601) at which a CLI remote session becomes stale without further heartbeats. - [JsonPropertyName("staleAt")] - public string? StaleAt { get; set; } + /// Plugin name that was updated. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// Session creation time as an ISO 8601 timestamp. - [JsonPropertyName("startTime")] - public string StartTime { get; set; } = string.Empty; + /// Version after the update, when available. + [JsonPropertyName("newVersion")] + public string? NewVersion { get; set; } - /// Server-side task state returned by GitHub. - [JsonPropertyName("state")] - public string? State { get; set; } + /// Previously installed version, when available. + [JsonPropertyName("previousVersion")] + public string? PreviousVersion { get; set; } - /// Short summary of the session, when one has been derived. - [JsonPropertyName("summary")] - public string? Summary { get; set; } + /// Number of skills installed after the update (success only). + [JsonPropertyName("skillsInstalled")] + public long? SkillsInstalled { get; set; } - /// Whether the remote task originated from CCA or CLI `--remote`. - [JsonPropertyName("taskType")] - public RemoteSessionMetadataTaskType? TaskType { get; set; } + /// Whether the update succeeded for this plugin. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// `sessions.open` handoff progress update with step, status, and optional message. +/// Result of updating all installed plugins. [Experimental(Diagnostics.Experimental)] -public sealed class SessionsOpenProgress +public sealed class PluginUpdateAllResult { - /// Optional step message. - [JsonPropertyName("message")] - public string? Message { get; set; } - - /// Step status. - [JsonPropertyName("status")] - public SessionsOpenProgressStatus Status { get; set; } - - /// Handoff step. - [JsonPropertyName("step")] - public SessionsOpenProgressStep Step { get; set; } + /// Per-plugin update results in deterministic order. + [JsonPropertyName("results")] + public IList Results { get => field ??= []; set; } } -/// Result of opening a session. +/// Plugin names (or specs) to enable. [Experimental(Diagnostics.Experimental)] -public sealed class SessionOpenResult +internal sealed class PluginsEnableRequest { - /// Remote session metadata, present when status is `connected`. - [JsonPropertyName("metadata")] - public RemoteSessionMetadataValue? Metadata { get; set; } + /// 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. + [JsonPropertyName("names")] + public IList Names { get => field ??= []; set; } +} - /// Handoff progress steps, present when status is `handed_off`. - [JsonPropertyName("progress")] - public IList? Progress { get; set; } +/// Plugin names (or specs) to disable. +[Experimental(Diagnostics.Experimental)] +internal sealed class 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. + [JsonPropertyName("names")] + public IList Names { get => field ??= []; set; } +} - /// Remote session ID, present when status is `connected`. - [JsonPropertyName("remoteSessionId")] - public string? RemoteSessionId { get; set; } +/// Trusted built-in plugin directories to use for this runtime process. +[Experimental(Diagnostics.Experimental)] +internal sealed class PluginsBuiltinSetRequest +{ + /// Complete replacement set of trusted built-in plugin directories. Every entry must be an absolute local filesystem path no longer than 4096 characters. + [JsonPropertyName("paths")] + public IList Paths { get => field ??= []; 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; } +/// Registered marketplace summary. +[Experimental(Diagnostics.Experimental)] +public sealed class MarketplaceInfo +{ + /// True when this is a default marketplace shipped with the runtime. Defaults are not removable. + [JsonPropertyName("isDefault")] + public bool? IsDefault { get; set; } - /// Opened session ID. Omitted when status is `not_found`. - [JsonPropertyName("sessionId")] - public string? SessionId { get; set; } + /// Marketplace name (matches the @marketplace suffix in plugin specs). + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// Startup prompts queued by user-level hook configs at session creation. Only populated when status is `created`; resumed sessions return an empty array. - [JsonPropertyName("startupPrompts")] - public IList? StartupPrompts { get; set; } + /// Human-readable description of where the marketplace data is fetched from (e.g. "GitHub: owner/repo"). + [JsonPropertyName("source")] + public string Source { get; set; } = string.Empty; +} - /// Outcome of the open request. - [JsonPropertyName("status")] - public SessionsOpenStatus Status { get; set; } +/// All registered marketplaces, including built-in defaults. +[Experimental(Diagnostics.Experimental)] +public sealed class MarketplaceListResult +{ + /// Registered marketplaces. + [JsonPropertyName("marketplaces")] + public IList Marketplaces { get => field ??= []; set; } } -/// Identifier and optional friendly name assigned to the newly forked session. +/// Result of registering a new marketplace. [Experimental(Diagnostics.Experimental)] -public sealed class SessionsForkResult +public sealed class MarketplaceAddResult { - /// Friendly name assigned to the forked session, if any. + /// Final name of the marketplace as resolved from its manifest. [JsonPropertyName("name")] - public string? Name { get; set; } - - /// The new forked session's ID. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; } -/// Source session identifier to fork from, optional event-ID boundary, and optional friendly name for the new session. +/// Marketplace source and optional working directory for relative-path resolution. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionsForkRequest +internal sealed class PluginsMarketplacesAddRequest { - /// Optional friendly name to assign to the forked session. - [JsonPropertyName("name")] - public string? Name { get; set; } + /// 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. + [JsonPropertyName("source")] + public string Source { get; set; } = string.Empty; - /// Source session ID to fork from. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Working directory used to resolve relative local paths in `source`. Defaults to the server's current working directory. + [JsonPropertyName("workingDirectory")] + public string? WorkingDirectory { get; set; } +} - /// Optional event ID boundary. When provided, the fork includes only events before this ID (exclusive). When omitted, all events are included. - [JsonPropertyName("toEventId")] - public string? ToEventId { get; set; } +/// Outcome of the remove attempt, including dependent-plugin info when applicable. +[Experimental(Diagnostics.Experimental)] +public sealed class MarketplaceRemoveResult +{ + /// Names of installed plugins that prevented removal. Populated only when `removed=false`. + [JsonPropertyName("dependentPlugins")] + public IList? DependentPlugins { get; set; } + + /// True when the marketplace was actually removed. False when removal was skipped because the marketplace has dependent plugins and `force` was not set. + [JsonPropertyName("removed")] + public bool Removed { get; set; } } -/// Repository associated with the connected remote session. +/// Name of the marketplace to remove and an optional force flag. [Experimental(Diagnostics.Experimental)] -public sealed class ConnectedRemoteSessionMetadataRepository +internal sealed class PluginsMarketplacesRemoveRequest { - /// Branch associated with the remote session. - [JsonPropertyName("branch")] - public string Branch { get; set; } = string.Empty; + /// 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. + [JsonPropertyName("force")] + public bool? Force { get; set; } - /// Repository name. + /// Marketplace name to remove. [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; - - /// Repository owner or organization login. - [JsonPropertyName("owner")] - public string Owner { get; set; } = string.Empty; } -/// Metadata for a connected remote session. +/// Plugin entry advertised by a marketplace. [Experimental(Diagnostics.Experimental)] -public sealed class ConnectedRemoteSessionMetadata +public sealed class MarketplacePluginInfo { - /// Neutral SDK discriminator for the connected remote session kind. - [JsonPropertyName("kind")] - public ConnectedRemoteSessionMetadataKind Kind { get; set; } - - /// Last session update time as an ISO 8601 string. - [JsonPropertyName("modifiedTime")] - public DateTimeOffset ModifiedTime { get; set; } + /// Short description from the marketplace catalog, when present. + [JsonPropertyName("description")] + public string? Description { get; set; } - /// Optional friendly session name. + /// Plugin name as listed in the marketplace catalog. [JsonPropertyName("name")] - public string? Name { get; set; } - - /// Pull request number associated with the session. - [JsonPropertyName("pullRequestNumber")] - public long? PullRequestNumber { get; set; } - - /// Repository associated with the connected remote session. - [JsonPropertyName("repository")] - public ConnectedRemoteSessionMetadataRepository Repository { get => field ??= new(); set; } - - /// Original remote resource identifier. - [JsonPropertyName("resourceId")] - public string? ResourceId { get; set; } + public string Name { get; set; } = string.Empty; +} - /// SDK session ID for the connected remote session. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; +/// Plugins advertised by the marketplace. +[Experimental(Diagnostics.Experimental)] +public sealed class MarketplaceBrowseResult +{ + /// Plugins advertised by the marketplace. + [JsonPropertyName("plugins")] + public IList Plugins { get => field ??= []; set; } +} - /// Remote session staleness deadline as an ISO 8601 string. - [JsonPropertyName("staleAt")] - public DateTimeOffset? StaleAt { get; set; } +/// Name of the marketplace whose plugin catalog to fetch. +[Experimental(Diagnostics.Experimental)] +internal sealed class PluginsMarketplacesBrowseRequest +{ + /// Marketplace name to browse. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; +} - /// Session start time as an ISO 8601 string. - [JsonPropertyName("startTime")] - public DateTimeOffset StartTime { get; set; } +/// Per-marketplace refresh result, including marketplace name, success flag, and optional failure error. +[Experimental(Diagnostics.Experimental)] +public sealed class MarketplaceRefreshEntry +{ + /// Error message (failure only). + [JsonPropertyName("error")] + public string? Error { get; set; } - /// Remote session state returned by the backing service. - [JsonPropertyName("state")] - public string? State { get; set; } + /// Marketplace name that was refreshed. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// Optional session summary. - [JsonPropertyName("summary")] - public string? Summary { get; set; } + /// Whether the refresh succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// Remote session connection result. +/// Result of refreshing one or more marketplace catalogs. [Experimental(Diagnostics.Experimental)] -public sealed class RemoteSessionConnectionResult +public sealed class MarketplaceRefreshResult { - /// Metadata for a connected remote session. - [JsonPropertyName("metadata")] - public ConnectedRemoteSessionMetadata Metadata { get => field ??= new(); set; } - - /// SDK session ID for the connected remote session. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Per-marketplace refresh results in deterministic order. + [JsonPropertyName("results")] + public IList Results { get => field ??= []; set; } } -/// Remote session connection parameters. +/// RPC data type for PluginsMarketplacesRefresh operations. [Experimental(Diagnostics.Experimental)] -internal sealed class ConnectRemoteSessionParams +internal sealed class PluginsMarketplacesRefreshRequest { - /// Session ID to connect to. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Marketplace name to refresh. When omitted, every registered marketplace is refreshed. + [JsonPropertyName("name")] + public string? Name { get; set; } } -/// Local or remote session metadata entry. Narrow on `isRemote` to access source-specific fields. -/// Data type discriminated by isRemote. +/// Server-side skill metadata, including name, description, source, enabled/invocable state, path, project path, and argument hint. [Experimental(Diagnostics.Experimental)] -public partial class SessionListEntry +public sealed class ServerSkill { - /// The boolean discriminator. - [JsonPropertyName("isRemote")] - public bool IsRemote { get; set; } - - /// Runtime client name that created/last resumed this session. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("clientName")] - public string? ClientName { get; set; } - - /// Pre-resolved working-directory context for session startup. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("context")] - public SessionContext? Context { get; set; } + /// Optional freeform hint describing the skill's expected arguments, from the `argument-hint` frontmatter field. + [JsonPropertyName("argumentHint")] + public string? ArgumentHint { get; set; } - /// True for detached maintenance sessions that should be hidden from normal resume lists. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("isDetached")] - public bool? IsDetached { get; set; } + /// Canonical slash command name used to invoke the skill, without the leading '/'. + [JsonPropertyName("commandName")] + public string? CommandName { get; set; } - /// GitHub task ID, when this local session is bound to one. Only present for local sessions exported to remote control. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("mcTaskId")] - public string? McTaskId { get; set; } + /// Description of what the skill does. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; - /// Last-modified time of the session's persisted state, as ISO 8601. - [JsonPropertyName("modifiedTime")] - public required string ModifiedTime { get; set; } + /// Whether the skill is currently enabled (based on global config). + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } - /// Optional human-friendly name set via /rename. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + /// Unique identifier for the skill. [JsonPropertyName("name")] - public string? Name { get; set; } + public string Name { get; set; } = string.Empty; - /// Pull request number associated with the session. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("pullRequestNumber")] - public long? PullRequestNumber { get; set; } + /// Absolute path to the skill file. + [JsonPropertyName("path")] + public string? Path { get; set; } - /// Backing remote session IDs (most recent first). - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("remoteSessionIds")] - public IList? RemoteSessionIds { get; set; } - - /// GitHub repository the remote session belongs to. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("repository")] - public RemoteSessionMetadataRepository? Repository { get; set; } - - /// Original remote resource identifier (task ID or PR node ID). - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("resourceId")] - public string? ResourceId { get; set; } - - /// Stable session identifier. - [JsonPropertyName("sessionId")] - public required string SessionId { get; set; } - - /// Deadline (ISO 8601) at which a CLI remote session becomes stale without further heartbeats. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("staleAt")] - public string? StaleAt { get; set; } + /// The project path this skill belongs to (only for project/inherited skills). + [JsonPropertyName("projectPath")] + public string? ProjectPath { get; set; } - /// Session creation time as an ISO 8601 timestamp. - [JsonPropertyName("startTime")] - public required string StartTime { get; set; } + /// Source location type (e.g., project, personal-copilot, plugin, builtin). + [JsonPropertyName("source")] + public SkillSource Source { get; set; } - /// Server-side task state returned by GitHub. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("state")] - public string? State { get; set; } + /// Whether the skill can be invoked by the user as a slash command. + [JsonPropertyName("userInvocable")] + public bool UserInvocable { get; set; } +} - /// Short summary of the session, when one has been derived. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("summary")] - public string? Summary { get; set; } +/// Skills discovered across global and project sources. +[Experimental(Diagnostics.Experimental)] +public sealed class 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. + [JsonPropertyName("errors")] + public IList? Errors { get; set; } - /// Whether the remote task originated from CCA or CLI `--remote`. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("taskType")] - public RemoteSessionMetadataTaskType? TaskType { get; set; } + /// All discovered skills across all sources. + [JsonPropertyName("skills")] + public IList Skills { get => field ??= []; set; } } -/// Sessions matching the filter, ordered most-recently-modified first. +/// Optional project paths and additional skill directories to include in discovery. [Experimental(Diagnostics.Experimental)] -public sealed class SessionList +internal sealed class SkillsDiscoverRequest { - /// Sessions ordered most-recently-modified first. Discriminated by `isRemote`. - [JsonPropertyName("sessions")] - public IList Sessions { get => field ??= []; set; } + /// When true, omit skills from the host's global sources (personal, custom, plugin, and built-in), returning only project-scoped skills. For multitenant deployments. + [JsonPropertyName("excludeHostSkills")] + public bool? ExcludeHostSkills { get; set; } + + /// Optional list of project directory paths to scan for project-scoped skills. + [JsonPropertyName("projectPaths")] + public IList? ProjectPaths { get; set; } + + /// Optional list of additional skill directory paths to include. + [JsonPropertyName("skillDirectories")] + public IList? SkillDirectories { get; set; } } -/// Optional filter applied to the returned sessions. +/// Canonical directory where skills can be discovered or created, with scope, preference, and optional project path. [Experimental(Diagnostics.Experimental)] -public sealed class SessionListFilter +public sealed class SkillDiscoveryPath { - /// Match sessions whose context.branch equals this value. - [JsonPropertyName("branch")] - public string? Branch { get; set; } + /// Absolute path of the create/discovery target (may not exist on disk yet). + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; - /// Match sessions whose context.cwd equals this value. - [JsonPropertyName("cwd")] - public string? Cwd { get; set; } + /// 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. + [JsonPropertyName("preferredForCreation")] + public bool PreferredForCreation { get; set; } - /// Match sessions whose context.gitRoot equals this value. - [JsonPropertyName("gitRoot")] - public string? GitRoot { get; set; } + /// The input project path this directory was derived from (only for project scope). + [JsonPropertyName("projectPath")] + public string? ProjectPath { get; set; } - /// Match sessions whose context.repository equals this value. - [JsonPropertyName("repository")] - public string? Repository { get; set; } + /// Which tier this directory belongs to. + [JsonPropertyName("scope")] + public SkillDiscoveryScope Scope { get; set; } } -/// Optional source filter, metadata-load limit, and context filter applied to the returned sessions. +/// Canonical locations where skills can be created so the runtime will recognize them. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionsListRequest +public sealed class SkillDiscoveryPathList { - /// Optional filter applied to the returned sessions. - [JsonPropertyName("filter")] - public SessionListFilter? Filter { get; set; } + /// Canonical skill create/discovery directories, in priority order. + [JsonPropertyName("paths")] + public IList Paths { get => field ??= []; set; } +} - /// When true, include detached maintenance sessions. Defaults to false for user-facing session lists. - [JsonPropertyName("includeDetached")] - public bool? IncludeDetached { get; set; } +/// Optional project paths to enumerate. +[Experimental(Diagnostics.Experimental)] +internal sealed class SkillsGetDiscoveryPathsRequest +{ + /// When true, omit the host's personal and custom skill directories, leaving only project directories. For multitenant deployments. + [JsonPropertyName("excludeHostSkills")] + public bool? ExcludeHostSkills { get; set; } - /// 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). - [JsonPropertyName("metadataLimit")] - public long? MetadataLimit { get; set; } + /// Optional list of project directory paths. When omitted or empty, only personal and custom directories are returned. + [JsonPropertyName("projectPaths")] + public IList? ProjectPaths { get; set; } +} - /// Which session sources to include. Defaults to `local` for backward compatibility. - [JsonPropertyName("source")] - public SessionSource? Source { get; set; } +/// Skill names to mark as disabled in global configuration, replacing any previous list. +[Experimental(Diagnostics.Experimental)] +internal sealed class SkillsConfigSetDisabledSkillsRequest +{ + /// List of skill names to disable. + [JsonPropertyName("disabledSkills")] + public IList DisabledSkills { get => field ??= []; set; } +} - /// 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. - [JsonPropertyName("throwOnError")] - public bool? ThrowOnError { get; set; } +/// Adds or removes a single skill from the global disabled list, leaving every other entry untouched. +[Experimental(Diagnostics.Experimental)] +internal sealed class SkillsConfigSetSkillDisabledRequest +{ + /// True to disable the skill, false to enable it. + [JsonPropertyName("disabled")] + public bool Disabled { get; set; } + + /// Name of the skill to add to or remove from the disabled list. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; } -/// Persisted local session metadata, including identifiers, timestamps, summary/name, client, context, detached state, and task ID. +/// Agent metadata, including identifiers, display details, source, tools, model, models, MCP servers, skills, and file path. [Experimental(Diagnostics.Experimental)] -public sealed class LocalSessionMetadataValue +public sealed class AgentInfo { - /// Runtime client name that created/last resumed this session. - [JsonPropertyName("clientName")] - public string? ClientName { get; set; } + /// Description of the agent's purpose. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; - /// Pre-resolved working-directory context for session startup. - [JsonPropertyName("context")] - public SessionContext? Context { get; set; } + /// Human-readable display name. + [JsonPropertyName("displayName")] + public string DisplayName { get; set; } = string.Empty; - /// True for detached maintenance sessions that should be hidden from normal resume lists. - [JsonPropertyName("isDetached")] - public bool? IsDetached { get; set; } + /// Stable identifier for selection. For most agents this is the same as `name`; for plugin/builtin agents it may differ. Always populated; defaults to `name` when no distinct id was assigned. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; - /// Always false for local sessions. - [JsonPropertyName("isRemote")] - public bool IsRemote { get; set; } + /// MCP server configurations attached to this agent, keyed by server name. Server config shape mirrors the MCP `mcpServers` schema. + [Experimental(Diagnostics.Experimental)] + [JsonPropertyName("mcpServers")] + public IDictionary? McpServers { 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; } + /// 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; } - /// Last-modified time of the session's persisted state, as ISO 8601. - [JsonPropertyName("modifiedTime")] - public string ModifiedTime { get; set; } = string.Empty; + /// Whether authored models are preferences or required constraints. + [JsonPropertyName("modelPolicy")] + public AgentModelPolicy? ModelPolicy { get; set; } - /// Optional human-friendly name set via /rename. + /// Authored preferred model ids for this agent, in priority order. Runtime model selection chooses the first available model; omitted means no authored preference. + [JsonPropertyName("models")] + public IList? Models { get; set; } + + /// Name of the agent. Use `id` as the stable selection identifier. [JsonPropertyName("name")] - public string? Name { get; set; } + public string Name { get; set; } = string.Empty; - /// Stable session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Absolute local file path of the agent definition. Only set for file-based agents loaded from disk; remote agents do not have a path. + [JsonPropertyName("path")] + public string? Path { get; set; } - /// Session creation time as an ISO 8601 timestamp. - [JsonPropertyName("startTime")] - public string StartTime { get; set; } = string.Empty; + /// 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; } - /// Short summary of the session, when one has been derived. - [JsonPropertyName("summary")] - public string? Summary { get; set; } + /// Skill names preloaded into this agent's context. Omitted means none. + [JsonPropertyName("skills")] + public IList? Skills { get; set; } + + /// Where the agent definition was loaded from. + [JsonPropertyName("source")] + public AgentInfoSource? Source { get; set; } + + /// Allowed tool names for this agent. Empty array means none; omitted means inherit defaults. + [JsonPropertyName("tools")] + public IList? Tools { get; set; } + + /// Whether the agent can be selected directly by the user. Agents marked `false` are subagent-only. + [JsonPropertyName("userInvocable")] + public bool? UserInvocable { get; set; } } -/// Persisted local session metadata when the session exists. +/// Agents discovered across user, project, plugin, and remote sources. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionsGetMetadataResult +public sealed class ServerAgentList { - /// Local session metadata, omitted when the session does not exist. - [JsonPropertyName("session")] - public LocalSessionMetadataValue? Session { get; set; } + /// All discovered agents across all sources. + [JsonPropertyName("agents")] + public IList Agents { get => field ??= []; set; } } -/// Session ID whose persisted metadata should be read. +/// Optional project paths to include in agent discovery. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionsGetMetadataRequest +internal sealed class AgentsDiscoverRequest { - /// Session ID to inspect. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// When true, omit the host's agents (the user-level agent directory and all plugin agents), leaving only project and remote agents. For multitenant deployments. + [JsonPropertyName("excludeHostAgents")] + public bool? ExcludeHostAgents { get; set; } + + /// Optional list of project directory paths to scan for project-scoped agents. When omitted or empty, only user/plugin/remote-independent agents are returned (no project scan). + [JsonPropertyName("projectPaths")] + public IList? ProjectPaths { get; set; } } -/// Recent local session IDs that contain user-visible history. +/// Canonical directory where custom agents can be discovered or created, with scope, preference, and optional project path. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionsListNonEmptySessionIdsResult +public sealed class AgentDiscoveryPath { - /// Session IDs ordered newest-first. - [JsonPropertyName("sessionIds")] - public IList SessionIds { get => field ??= []; set; } -} + /// Absolute path of the search/create directory (may not exist on disk yet). + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; -/// 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; } -} + /// Whether this is the canonical directory to create a new agent in its tier. At most one entry per tier is preferred. + [JsonPropertyName("preferredForCreation")] + public bool PreferredForCreation { get; set; } -/// ID of the local session bound to the given GitHub task, or omitted when none. -[Experimental(Diagnostics.Experimental)] -public sealed class SessionsFindByTaskIDResult -{ - /// Omitted when no local session is bound to that GitHub task. - [JsonPropertyName("sessionId")] - public string? SessionId { get; set; } -} + /// The input project path this directory was derived from (only for project scope). + [JsonPropertyName("projectPath")] + public string? ProjectPath { get; set; } -/// GitHub task ID to look up. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionsFindByTaskIDRequest -{ - /// GitHub task ID to look up. - [JsonPropertyName("taskId")] - public string TaskId { get; set; } = string.Empty; + /// Which tier this directory belongs to. + [JsonPropertyName("scope")] + public AgentDiscoveryPathScope Scope { get; set; } } -/// Session ID matching the prefix, omitted when no unique match exists. +/// Canonical locations where custom agents can be created so the runtime will recognize them. [Experimental(Diagnostics.Experimental)] -public sealed class SessionsFindByPrefixResult +public sealed class AgentDiscoveryPathList { - /// Omitted when no unique session matches the prefix (no match or ambiguous). - [JsonPropertyName("sessionId")] - public string? SessionId { get; set; } + /// Canonical agent create/discovery directories, in priority order. + [JsonPropertyName("paths")] + public IList Paths { get => field ??= []; set; } } -/// UUID prefix to resolve to a unique session ID. +/// Optional project paths to include when enumerating agent discovery directories. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionsFindByPrefixRequest +internal sealed class AgentsGetDiscoveryPathsRequest { - /// 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. - [JsonPropertyName("prefix")] - public string Prefix { get; set; } = string.Empty; -} + /// When true, omit the host's user-level agent directory, leaving only project directories. For multitenant deployments (mirrors `discover`'s `excludeHostAgents`). + [JsonPropertyName("excludeHostAgents")] + public bool? ExcludeHostAgents { get; set; } -/// Most-relevant session ID for the supplied context, or omitted when no sessions exist. -[Experimental(Diagnostics.Experimental)] -public sealed class SessionsGetLastForContextResult -{ - /// Most-relevant session ID for the supplied context, or omitted when no sessions exist. - [JsonPropertyName("sessionId")] - public string? SessionId { get; set; } + /// Optional list of project directory paths. When omitted or empty, only the user-level directory is returned. + [JsonPropertyName("projectPaths")] + public IList? ProjectPaths { get; set; } } -/// Optional working-directory context used to score session relevance. +/// Loaded instruction source for a session, including path, content, category, location, applicability, and optional description. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionsGetLastForContextRequest +public sealed class InstructionSource { - /// Optional working-directory context used to score session relevance. When omitted the most-recently-modified session wins. - [JsonPropertyName("context")] - public SessionContext? Context { get; set; } -} + /// Glob pattern(s) from frontmatter — when set, this instruction applies only to matching files. + [JsonPropertyName("applyTo")] + public IList? ApplyTo { get; set; } -/// Absolute path to the session's events.jsonl file on disk. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionsGetEventFilePathResult -{ - /// Absolute path to the session's events.jsonl file. - [JsonPropertyName("filePath")] - public string FilePath { get; set; } = string.Empty; -} + /// Raw content of the instruction file. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; -/// Session ID whose event-log file path to compute. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionsGetEventFilePathRequest -{ - /// Session ID whose event-log file path to compute. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// When true, this source starts disabled and must be toggled on by the user. + [JsonPropertyName("defaultDisabled")] + public bool? DefaultDisabled { get; set; } -/// Map of sessionId -> on-disk size in bytes for each session's workspace directory. -[Experimental(Diagnostics.Experimental)] -public sealed class SessionSizes -{ - /// Map of sessionId -> on-disk size in bytes for the session's workspace directory. - [JsonPropertyName("sizes")] - public IDictionary Sizes { get => field ??= new Dictionary(); set; } -} + /// Short description (body after frontmatter) for use in instruction tables. + [JsonPropertyName("description")] + public string? Description { get; set; } -/// Session IDs from the input set that are currently in use by another process. -[Experimental(Diagnostics.Experimental)] -public sealed class SessionsCheckInUseResult -{ - /// Session IDs from the input set that are currently held by another running process via an alive lock file. - [JsonPropertyName("inUse")] - public IList InUse { get => field ??= []; set; } -} + /// Unique identifier for this source (used for toggling). + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; -/// Session IDs to test for live in-use locks. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionsCheckInUseRequest -{ - /// Session IDs to test for live in-use locks. - [JsonPropertyName("sessionIds")] - public IList SessionIds { get => field ??= []; set; } -} + /// Human-readable label. + [JsonPropertyName("label")] + public string Label { get; set; } = string.Empty; -/// The session's persisted remote-steerable flag, or omitted when no value has been persisted. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionsGetPersistedRemoteSteerableResult -{ - /// The session's persisted remote-steerable flag if recorded; omitted when no value has been persisted. - [JsonPropertyName("remoteSteerable")] - public bool? RemoteSteerable { get; set; } + /// Where this source lives — used for UI grouping. + [JsonPropertyName("location")] + public InstructionSourceLocation Location { get; set; } + + /// 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; } + + /// File path relative to repo or absolute for home. + [JsonPropertyName("sourcePath")] + public string SourcePath { get; set; } = string.Empty; + + /// Category of instruction source — used for merge logic. + [JsonPropertyName("type")] + public InstructionSourceType Type { get; set; } } -/// Session ID to look up the persisted remote-steerable flag for. +/// Instruction sources discovered across user, repository, and plugin sources. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionsGetPersistedRemoteSteerableRequest +public sealed class ServerInstructionSourceList { - /// Session ID to look up the persisted remote-steerable flag for. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// All discovered instruction sources. + [JsonPropertyName("sources")] + public IList Sources { get => field ??= []; set; } } -/// 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. +/// Optional project paths to include in instruction discovery. [Experimental(Diagnostics.Experimental)] -public sealed class SessionsCloseResult +internal sealed class 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. + [JsonPropertyName("excludeHostInstructions")] + public bool? ExcludeHostInstructions { get; set; } + + /// 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). + [JsonPropertyName("projectPaths")] + public IList? ProjectPaths { get; set; } } -/// Session ID to close. +/// Canonical file or directory where custom instructions can be discovered or created, with location, kind, preference, and project path. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionsCloseRequest +public sealed class InstructionDiscoveryPath { - /// Session ID to close. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Whether the target is a single file or a directory of instruction files. + [JsonPropertyName("kind")] + public InstructionDiscoveryPathKind Kind { get; set; } + + /// Which tier this target belongs to. + [JsonPropertyName("location")] + public InstructionDiscoveryPathLocation Location { get; set; } + + /// Absolute path of the file or directory (may not exist on disk yet). + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; + + /// Whether this is the canonical target to create new instructions in its tier. At most one entry per tier is preferred. + [JsonPropertyName("preferredForCreation")] + public bool PreferredForCreation { get; set; } + + /// The input project path this target was derived from (only for repository targets). + [JsonPropertyName("projectPath")] + public string? ProjectPath { get; set; } } -/// Map of sessionId -> bytes freed by removing the session's workspace directory. +/// Canonical files and directories where custom instructions can be created so the runtime will recognize them. [Experimental(Diagnostics.Experimental)] -public sealed class SessionBulkDeleteResult +public sealed class InstructionDiscoveryPathList { - /// 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). - [JsonPropertyName("freedBytes")] - public IDictionary FreedBytes { get => field ??= new Dictionary(); set; } + /// Canonical instruction create/discovery files and directories, in priority order. + [JsonPropertyName("paths")] + public IList Paths { get => field ??= []; set; } } -/// Session IDs to close, deactivate, and delete from disk. +/// Optional project paths to include when enumerating instruction discovery targets. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionsBulkDeleteRequest +internal sealed class InstructionsGetDiscoveryPathsRequest { - /// Session IDs to close, deactivate, and delete from disk. - [JsonPropertyName("sessionIds")] - public IList SessionIds { get => field ??= []; set; } + /// When true, omit the host's user-level instruction targets, leaving only repository targets. For multitenant deployments (mirrors `discover`'s `excludeHostInstructions`). + [JsonPropertyName("excludeHostInstructions")] + public bool? ExcludeHostInstructions { get; set; } + + /// Optional list of project directory paths. When omitted or empty, only the user-level targets are returned. + [JsonPropertyName("projectPaths")] + public IList? ProjectPaths { get; set; } } -/// Session ID to delete from disk. +/// A literal choice the command input accepts, with a human-facing description. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionsDeleteRequest +public sealed class SlashCommandInputChoice { - /// Session ID to delete. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Human-readable description shown alongside the choice. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; - /// Internal resolved session directory path to delete. - [JsonPropertyName("sessionPath")] - public string? SessionPath { get; set; } + /// The literal choice value (e.g. 'on', 'off', 'show'). + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; } -/// Outcome of the prune operation: deleted IDs, dry-run candidates, skipped IDs, total bytes freed, and the dry-run flag. +/// Optional unstructured input hint. [Experimental(Diagnostics.Experimental)] -public sealed class SessionPruneResult +public sealed class SlashCommandInput { - /// Session IDs that would be deleted in dry-run mode (always empty otherwise). - [JsonPropertyName("candidates")] - public IList Candidates { get => field ??= []; set; } - - /// Session IDs that were deleted (always empty in dry-run mode). - [JsonPropertyName("deleted")] - public IList Deleted { get => field ??= []; set; } + /// Optional literal choices the input accepts, each with a human-facing description; clients may render these as selectable options. + [JsonPropertyName("choices")] + public IList? Choices { get; set; } - /// True when no deletions were actually performed. - [JsonPropertyName("dryRun")] - public bool DryRun { get; set; } + /// Optional completion hint for the input (e.g. 'directory' for filesystem path completion). + [JsonPropertyName("completion")] + public SlashCommandInputCompletion? Completion { get; set; } - /// Total bytes freed (actual when not dry-run, projected when dry-run). - [JsonPropertyName("freedBytes")] - public long FreedBytes { get; set; } + /// Hint to display when command input has not been provided. + [JsonPropertyName("hint")] + public string Hint { get; set; } = string.Empty; - /// Session IDs that were skipped (e.g., named sessions). - [JsonPropertyName("skipped")] - public IList Skipped { get => field ??= []; set; } + /// When true, clients should pass the full text after the command name as a single argument rather than splitting on whitespace. + [JsonPropertyName("preserveMultilineInput")] + public bool? PreserveMultilineInput { get; set; } + + /// When true, the command requires non-empty input; clients should render the input hint as required. + [JsonPropertyName("required")] + public bool? Required { get; set; } } -/// Age threshold and optional flags controlling which old sessions are pruned (or simulated when dryRun is true). +/// Slash-command metadata with name, aliases, description, kind, input hint, execution allowance, and schedulability. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionsPruneOldRequest +public sealed class SlashCommandInfo { - /// When true, only report what would be deleted without performing any deletion. - [JsonPropertyName("dryRun")] - public bool? DryRun { get; set; } + /// Canonical aliases without leading slashes. + [JsonPropertyName("aliases")] + public IList? Aliases { get; set; } - /// Session IDs that should never be considered for pruning. - [JsonPropertyName("excludeSessionIds")] - public IList? ExcludeSessionIds { get; set; } + /// Whether the command may run while an agent turn is active. + [JsonPropertyName("allowDuringAgentExecution")] + public bool AllowDuringAgentExecution { get; set; } - /// When true, named sessions (set via /rename) are also eligible for pruning. - [JsonPropertyName("includeNamed")] - public bool? IncludeNamed { get; set; } + /// Human-readable command description. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; - /// Delete sessions whose modifiedTime is at least this many days old. - [JsonPropertyName("olderThanDays")] - public long OlderThanDays { get; set; } + /// Whether the command is experimental. + [JsonPropertyName("experimental")] + public bool? Experimental { get; set; } + + /// Optional unstructured input hint. + [JsonPropertyName("input")] + public SlashCommandInput? Input { get; set; } + + /// Coarse command category for grouping and behavior: runtime built-in, skill-backed command, or SDK/client-owned command. + [JsonPropertyName("kind")] + public SlashCommandKind Kind { get; set; } + + /// Canonical command name without a leading slash. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Whether the command may be the target of `/every` / `/after` schedules. Resolution happens at every tick, so only set this when the command is safe to re-invoke and produces an agent prompt. + [JsonPropertyName("schedulable")] + public bool? Schedulable { get; set; } } -/// Flush a session's pending events to disk. No-op when no writer exists for the session (e.g., already closed). +/// Slash commands available in the session, after applying any include/exclude filters. [Experimental(Diagnostics.Experimental)] -public sealed class SessionsSaveResult +public sealed class CommandList { + /// Commands available in this session. + [JsonPropertyName("commands")] + public IList Commands { get => field ??= []; set; } } -/// Session ID whose pending events should be flushed to disk. +/// A single user setting's effective value alongside its default, so consumers can render settings left at their default. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionsSaveRequest +public sealed class UserSettingMetadata { - /// Session ID whose pending events should be flushed to disk. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// The centrally-known default for this setting (null when no default is registered). + [JsonPropertyName("default")] + public JsonElement Default { get; set; } + + /// 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. + [JsonPropertyName("isDefault")] + public bool IsDefault { get; set; } + + /// The effective value: the user's value if set, otherwise the default. + [JsonPropertyName("value")] + public JsonElement Value { get; set; } } -/// 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. +/// 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. [Experimental(Diagnostics.Experimental)] -public sealed class SessionsReleaseLockResult +public sealed class UserSettingsGetResult { + /// Every known user setting keyed by setting name, each with its effective value, default, and whether it is at the default. + [JsonPropertyName("settings")] + public IDictionary Settings { get => field ??= new Dictionary(); set; } } -/// Session ID whose in-use lock should be released. +/// Outcome of writing user settings. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionsReleaseLockRequest +public sealed class UserSettingsSetResult { - /// Session ID whose in-use lock should be released. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// 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. + [JsonPropertyName("shadowedKeys")] + public IList ShadowedKeys { get => field ??= []; set; } } -/// The enriched metadata records, with summary and context fields backfilled where available. Sessions confirmed empty and unnamed are omitted. +/// 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. [Experimental(Diagnostics.Experimental)] -public sealed class SessionEnrichMetadataResult +internal sealed class UserSettingsSetRequest { - /// Enriched records, with summary and context backfilled. Sessions confirmed empty and unnamed may be omitted. - [JsonPropertyName("sessions")] - public IList Sessions { get => field ??= []; set; } + /// Partial user settings to write, as a free-form object keyed by setting name. + [JsonPropertyName("settings")] + public JsonElement Settings { get; set; } } -/// Session metadata records to enrich with summary and context information. +/// Validated device-managed settings discovered before a session exists. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionsEnrichMetadataRequest +public sealed class ManagedSettingsReadResult { - /// Session metadata records to enrich. Records that already have summary and context are returned unchanged. - [JsonPropertyName("sessions")] - public IList Sessions { get => field ??= []; set; } + /// 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; } } -/// 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. +/// Indicates whether the calling client was registered as the session filesystem provider. [Experimental(Diagnostics.Experimental)] -public sealed class SessionsReloadPluginHooksResult +public sealed class SessionFsSetProviderResult { + /// Whether the provider was set successfully. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// Active session ID and an optional flag for deferring repo-level hooks until folder trust. +/// Optional capabilities declared by the provider. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionsReloadPluginHooksRequest +public sealed class SessionFsSetProviderCapabilities { - /// When true, skip repo-level hooks. Use before folder trust is confirmed; loadDeferredRepoHooks loads them post-trust. - [JsonPropertyName("deferRepoHooks")] - public bool? DeferRepoHooks { get; set; } - - /// Active session ID to reload hooks for. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Whether the provider supports SQLite query/exists operations. + [JsonPropertyName("sqlite")] + public bool? Sqlite { get; set; } } -/// Queued repo-level startup prompts and the total hook command count after loading. +/// Initial working directory, session-state path layout, and path conventions used to register the calling SDK client as the session filesystem provider. [Experimental(Diagnostics.Experimental)] -public sealed class SessionLoadDeferredRepoHooksResult +internal sealed class SessionFsSetProviderRequest { - /// 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. - [JsonPropertyName("hookCount")] - public long HookCount { get; set; } + /// Optional capabilities declared by the provider. + [JsonPropertyName("capabilities")] + public SessionFsSetProviderCapabilities? Capabilities { get; set; } - /// Repo-level startup prompts queued from repo hook configs. Empty on resume, when no repo configs were pending, or when disableAllHooks is set. - [JsonPropertyName("startupPrompts")] - public IList StartupPrompts { get => field ??= []; set; } + /// Path conventions used by this filesystem. + [JsonPropertyName("conventions")] + public SessionFsSetProviderConventions Conventions { get; set; } + + /// Initial working directory for sessions. + [JsonPropertyName("initialCwd")] + public string InitialCwd { get; set; } = string.Empty; + + /// Path within each session's SessionFs where the runtime stores files for that session. + [JsonPropertyName("sessionStatePath")] + public string SessionStatePath { get; set; } = string.Empty; } -/// Active session ID whose deferred repo-level hooks should be loaded. +/// Indicates whether the calling client was registered as the LLM inference provider. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionsLoadDeferredRepoHooksRequest +public sealed class LlmInferenceSetProviderResult { - /// Active session ID whose deferred repo-level hooks should be loaded. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Whether the provider was set successfully. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// 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. +/// Whether the start frame was accepted. [Experimental(Diagnostics.Experimental)] -public sealed class SessionsSetAdditionalPluginsResult +public sealed class LlmInferenceHttpResponseStartResult { + /// True when the response start was matched to a pending request; false when unknown. + [JsonPropertyName("accepted")] + public bool Accepted { get; set; } } -/// Installed plugin record from global state, with marketplace, version, install time, enabled state, cache path, and source. +/// Response head. [Experimental(Diagnostics.Experimental)] -public sealed class InstalledPlugin +internal sealed class LlmInferenceHttpResponseStartRequest { - /// Path where the plugin is cached locally. - [JsonPropertyName("cache_path")] - public string? CachePath { get; set; } - - /// Whether the plugin is currently enabled. - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } - - /// Installation timestamp. - [JsonPropertyName("installed_at")] - public string InstalledAt { get; set; } = string.Empty; - - /// Marketplace the plugin came from (empty string for direct repo installs). - [JsonPropertyName("marketplace")] - public string Marketplace { get; set; } = string.Empty; - - /// Plugin name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// HTTP response headers, preserving multiple values per name. + [JsonPropertyName("headers")] + public IDictionary> Headers { get => field ??= new Dictionary>(); set; } - /// Source for direct repo installs (when marketplace is empty). - [JsonPropertyName("source")] - public JsonElement? Source { get; set; } + /// Matches the requestId from the originating httpRequestStart frame. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; - /// 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; } + /// HTTP status code. + [JsonPropertyName("status")] + public long Status { get; set; } - /// Version installed (if available). - [JsonPropertyName("version")] - public string? Version { get; set; } + /// Optional HTTP status reason phrase. + [JsonPropertyName("statusText")] + public string? StatusText { get; set; } } -/// Manager-wide additional plugins to register; replaces any previously-configured set. +/// Whether the chunk was accepted. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionsSetAdditionalPluginsRequest +public sealed class LlmInferenceHttpResponseChunkResult { - /// Manager-wide additional plugins to register. Replaces any previously-configured set. Pass an empty array to clear. - [JsonPropertyName("plugins")] - public IList Plugins { get => field ??= []; set; } + /// True when the chunk was matched to a pending request; false when unknown. + [JsonPropertyName("accepted")] + public bool Accepted { get; set; } } -/// Dynamic-context board entry count, when available. +/// Set to terminate the response with a transport-level failure. Implies end-of-stream; any further chunks for this requestId are ignored. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionsGetBoardEntryCountResult +public sealed class LlmInferenceHttpResponseChunkError { - /// Board entry count, when available. - [JsonPropertyName("count")] - public long? Count { get; set; } + /// Optional machine-readable error code. + [JsonPropertyName("code")] + public string? Code { get; set; } + + /// Human-readable failure description. + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; } -/// Session ID whose board entry count should be returned. +/// A response body chunk or terminal error. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionsGetBoardEntryCountRequest +internal sealed class LlmInferenceHttpResponseChunkRequest { - /// Session ID whose board entry count should be returned. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// When true, `data` is base64-encoded bytes. When absent or false, `data` is UTF-8 text. + [JsonPropertyName("binary")] + public bool? Binary { get; set; } + + /// 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). + [JsonPropertyName("data")] + public string Data { get; set; } = string.Empty; + + /// 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. + [JsonPropertyName("end")] + public bool? End { get; set; } + + /// Set to terminate the response with a transport-level failure. Implies end-of-stream; any further chunks for this requestId are ignored. + [JsonPropertyName("error")] + public LlmInferenceHttpResponseChunkError? Error { get; set; } + + /// Matches the requestId from the originating httpRequestStart frame. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; } -/// State of the runtime-managed remote-control singleton. -/// Polymorphic base type discriminated by state. +/// Pre-resolved working-directory context for session startup. [Experimental(Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "state", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(RemoteControlStatusOff), "off")] -[JsonDerivedType(typeof(RemoteControlStatusConnecting), "connecting")] -[JsonDerivedType(typeof(RemoteControlStatusActive), "active")] -[JsonDerivedType(typeof(RemoteControlStatusError), "error")] -public partial class RemoteControlStatus +public sealed class SessionContext { - /// The type discriminator. - [JsonPropertyName("state")] - public virtual string State { get; set; } = string.Empty; -} + /// Active git branch. + [JsonPropertyName("branch")] + public string? Branch { get; set; } + /// Most recent working directory for this session. + [JsonPropertyName("cwd")] + public string Cwd { get; set; } = string.Empty; -/// Remote control is not connected. -/// The off variant of . -[Experimental(Diagnostics.Experimental)] -public partial class RemoteControlStatusOff : RemoteControlStatus -{ - /// - [JsonIgnore] - public override string State => "off"; + /// Git repository root, if the cwd was inside a git repo. + [JsonPropertyName("gitRoot")] + public string? GitRoot { get; set; } + + /// Repository host type. + [JsonPropertyName("hostType")] + public SessionContextHostType? HostType { get; set; } + + /// Repository slug in `owner/name` form, when known. + [JsonPropertyName("repository")] + public string? Repository { get; set; } } -/// Remote control is in the middle of initial setup. -/// The connecting variant of . +/// GitHub repository the remote session belongs to. [Experimental(Diagnostics.Experimental)] -public partial class RemoteControlStatusConnecting : RemoteControlStatus +public sealed class RemoteSessionMetadataRepository { - /// - [JsonIgnore] - public override string State => "connecting"; + /// Branch associated with the remote session. + [JsonPropertyName("branch")] + public string Branch { get; set; } = string.Empty; - /// Session id the connection is attaching to. - [JsonPropertyName("attachedSessionId")] - public required string AttachedSessionId { get; set; } + /// Repository name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Repository owner. + [JsonPropertyName("owner")] + public string Owner { get; set; } = string.Empty; } -/// Remote control is connected to a local session. -/// The active variant of . +/// Remote session metadata for the session to hand off (typically obtained from `sessions.list` with `source: "remote"`). [Experimental(Diagnostics.Experimental)] -public partial class RemoteControlStatusActive : RemoteControlStatus +public sealed class RemoteSessionMetadataValue { - /// - [JsonIgnore] - public override string State => "active"; + /// Most recent working directory context. + [JsonPropertyName("context")] + public SessionContext? Context { get; set; } - /// Session id remote control is pointed at. - [JsonPropertyName("attachedSessionId")] - public required string AttachedSessionId { get; set; } + /// Host-supplied human description of what the session is doing right now ("running tests", "waiting for approval"). Optional in the protocol and absent on hosts that do not publish it, so never rely on it -- it enriches `hostStatus`, it does not replace it. + [JsonPropertyName("hostActivity")] + public string? HostActivity { get; set; } - /// 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. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonInclude] - [JsonPropertyName("awaitingFirstMessage")] - internal bool? AwaitingFirstMessage { get; set; } + /// Live status as the owning host reports it in its session listing, so a row for a session running elsewhere can show that it is running. Absent for hosts that publish no such status (the cloud task managers), which read as idle. + [JsonPropertyName("hostStatus")] + public RemoteSessionHostStatus? HostStatus { get; set; } - /// MC frontend URL for this session, when known. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("frontendUrl")] - public string? FrontendUrl { get; set; } + /// Always true for remote sessions. + [JsonPropertyName("isRemote")] + public bool IsRemote { get; set; } - /// Whether the MC session may steer this session. - [JsonPropertyName("isSteerable")] - public required bool IsSteerable { get; set; } + /// Last-modified time as an ISO 8601 timestamp. + [JsonPropertyName("modifiedTime")] + public string ModifiedTime { get; set; } = string.Empty; - /// 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; } -} + /// Optional human-friendly name set via /rename. + [JsonPropertyName("name")] + public string? Name { get; set; } -/// The last setup attempt failed. The singleton is otherwise off. -/// The error variant of . -[Experimental(Diagnostics.Experimental)] -public partial class RemoteControlStatusError : RemoteControlStatus -{ - /// - [JsonIgnore] - public override string State => "error"; + /// Pull request number associated with the session. + [JsonPropertyName("pullRequestNumber")] + public long? PullRequestNumber { get; set; } - /// Session id the failing setup attempt targeted, when known. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("attachedSessionId")] - public string? AttachedSessionId { get; set; } + /// Backing remote session IDs (most recent first). + [JsonPropertyName("remoteSessionIds")] + public IList RemoteSessionIds { get => field ??= []; set; } - /// Human-readable error message from the last setup attempt. - [JsonPropertyName("error")] - public required string Error { get; set; } -} + /// GitHub repository the remote session belongs to. + [JsonPropertyName("repository")] + public RemoteSessionMetadataRepository Repository { get => field ??= new(); set; } -/// Wrapper for the singleton's current status. -[Experimental(Diagnostics.Experimental)] -public sealed class RemoteControlStatusResult -{ - /// State of the runtime-managed remote-control singleton. - [JsonPropertyName("status")] - public RemoteControlStatus Status { get => field ??= new(); set; } + /// Original remote resource identifier (task ID or PR node ID). + [JsonPropertyName("resourceId")] + public string? ResourceId { get; set; } + + /// Stable session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + /// Deadline (ISO 8601) at which a CLI remote session becomes stale without further heartbeats. + [JsonPropertyName("staleAt")] + public string? StaleAt { get; set; } + + /// Session creation time as an ISO 8601 timestamp. + [JsonPropertyName("startTime")] + public string StartTime { get; set; } = string.Empty; + + /// Server-side task state returned by GitHub. + [JsonPropertyName("state")] + public string? State { get; set; } + + /// Short summary of the session, when one has been derived. + [JsonPropertyName("summary")] + public string? Summary { get; set; } + + /// Whether the remote task originated from CCA or CLI `--remote`. + [JsonPropertyName("taskType")] + public RemoteSessionMetadataTaskType? TaskType { get; set; } } -/// Reattach to an existing MC session without creating a new one. +/// `sessions.open` handoff progress update with step, status, and optional message. [Experimental(Diagnostics.Experimental)] -public sealed class RemoteControlConfigExistingMcSession +public sealed class SessionsOpenProgress { - /// Existing MC session ID to reattach to. - [JsonPropertyName("mcSessionId")] - public string McSessionId { get; set; } = string.Empty; + /// Optional step message. + [JsonPropertyName("message")] + public string? Message { get; set; } - /// Existing MC task ID for the reattached session. - [JsonPropertyName("mcTaskId")] - public string McTaskId { get; set; } = string.Empty; + /// Step status. + [JsonPropertyName("status")] + public SessionsOpenProgressStatus Status { get; set; } + + /// Handoff step. + [JsonPropertyName("step")] + public SessionsOpenProgressStep Step { get; set; } } -/// Configuration for the runtime-managed remote-control singleton. +/// Result of opening a session. [Experimental(Diagnostics.Experimental)] -public sealed class RemoteControlConfig +public sealed class SessionOpenResult { - /// Reattach to an existing MC session without creating a new one. - [JsonPropertyName("existingMcSession")] - public RemoteControlConfigExistingMcSession? ExistingMcSession { get; set; } + /// Remote session metadata, present when status is `connected`. + [JsonPropertyName("metadata")] + public RemoteSessionMetadataValue? Metadata { get; set; } - /// Whether the user explicitly requested remote (vs. implicit session-sync). Controls warning surfacing for missing-repo cases. - [JsonPropertyName("explicit")] - public bool Explicit { get; set; } + /// Handoff progress steps, present when status is `handed_off`. + [JsonPropertyName("progress")] + public IList? Progress { get; set; } - /// Whether remote export should be enabled. - [JsonPropertyName("remote")] - public bool Remote { get; set; } + /// Remote session ID, present when status is `connected`. + [JsonPropertyName("remoteSessionId")] + public string? RemoteSessionId { get; set; } - /// When true, suppresses timeline messages on successful setup. - [JsonPropertyName("silent")] - public bool Silent { get; set; } + /// Opened session ID. Omitted when status is `not_found`. + [JsonPropertyName("sessionId")] + public string? SessionId { get; set; } - /// Whether the MC session may steer the local session (write mode). - [JsonPropertyName("steerable")] - public bool Steerable { get; set; } + /// Startup prompts queued by user-level hook configs at session creation. Only populated when status is `created`; resumed sessions return an empty array. + [JsonPropertyName("startupPrompts")] + public IList? StartupPrompts { get; set; } - /// Existing Mission Control task ID to attach the exported session to. - [JsonPropertyName("taskId")] - public string? TaskId { get; set; } + /// Outcome of the open request. + [JsonPropertyName("status")] + public SessionsOpenStatus Status { get; set; } } -/// Parameters for attaching the remote-control singleton to a session. +/// Identifier and optional friendly name assigned to the newly forked session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionsStartRemoteControlRequest +public sealed class SessionsForkResult { - /// Configuration for the runtime-managed remote-control singleton. - [JsonPropertyName("config")] - public RemoteControlConfig Config { get => field ??= new(); set; } + /// Friendly name assigned to the forked session, if any. + [JsonPropertyName("name")] + public string? Name { get; set; } - /// Local session id to attach remote control to. + /// The new forked session's ID. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Outcome of a transferRemoteControl call. +/// Source session identifier to fork from, optional event-ID boundary, and optional friendly name for the new session. [Experimental(Diagnostics.Experimental)] -public sealed class RemoteControlTransferResult +internal sealed class SessionsForkRequest { - /// State of the runtime-managed remote-control singleton. - [JsonPropertyName("status")] - public RemoteControlStatus Status { get => field ??= new(); set; } - - /// Whether the rebinding actually happened. - [JsonPropertyName("transferred")] - public bool Transferred { get; set; } -} + /// Optional friendly name to assign to the forked session. + [JsonPropertyName("name")] + public string? Name { get; set; } -/// Parameters for atomically rebinding the remote-control singleton. -[Experimental(Diagnostics.Experimental)] -internal sealed class 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). - [JsonPropertyName("expectedFromSessionId")] - public string? ExpectedFromSessionId { get; set; } + /// Source session ID to fork from. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// Local session id to point remote control at. - [JsonPropertyName("toSessionId")] - public string ToSessionId { get; set; } = string.Empty; + /// Optional event ID boundary. When provided, the fork includes only events before this ID (exclusive). When omitted, all events are included. + [JsonPropertyName("toEventId")] + public string? ToEventId { get; set; } } -/// Patch for the singleton's steering state. +/// Repository associated with the connected remote session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionsSetRemoteControlSteeringRequest +public sealed class ConnectedRemoteSessionMetadataRepository { - /// Target steering state. Today only `true` is actionable on the underlying exporter; `false` is reserved for future use. - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } -} + /// Branch associated with the remote session. + [JsonPropertyName("branch")] + public string Branch { get; set; } = string.Empty; -/// Outcome of a stopRemoteControl call. -[Experimental(Diagnostics.Experimental)] -public sealed class RemoteControlStopResult -{ - /// State of the runtime-managed remote-control singleton. - [JsonPropertyName("status")] - public RemoteControlStatus Status { get => field ??= new(); set; } + /// Repository name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// Whether the singleton was actually torn down by this call. - [JsonPropertyName("stopped")] - public bool Stopped { get; set; } + /// Repository owner or organization login. + [JsonPropertyName("owner")] + public string Owner { get; set; } = string.Empty; } -/// RPC data type for SessionsStopRemoteControl operations. +/// Metadata for a connected remote session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionsStopRemoteControlRequest +public sealed class ConnectedRemoteSessionMetadata { - /// When provided, the stop is rejected unless the singleton currently points at this session id (compare-and-swap semantics). - [JsonPropertyName("expectedSessionId")] - public string? ExpectedSessionId { get; set; } + /// Neutral SDK discriminator for the connected remote session kind. + [JsonPropertyName("kind")] + public ConnectedRemoteSessionMetadataKind Kind { get; set; } - /// When true, the singleton is unconditionally torn down regardless of `expectedSessionId`. Use during shutdown or explicit `/remote off`. - [JsonPropertyName("force")] - public bool? Force { get; set; } -} + /// Last session update time as an ISO 8601 string. + [JsonPropertyName("modifiedTime")] + public DateTimeOffset ModifiedTime { get; set; } -/// Handle for releasing the extension tool registration. -[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 friendly session name. + [JsonPropertyName("name")] + public string? Name { 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; } -} + /// Pull request number associated with the session. + [JsonPropertyName("pullRequestNumber")] + public long? PullRequestNumber { 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; } + /// Repository associated with the connected remote session. + [JsonPropertyName("repository")] + public ConnectedRemoteSessionMetadataRepository Repository { get => field ??= new(); set; } - /// Optional registration options. - [JsonPropertyName("options")] - public SessionsRegisterExtensionToolsOnSessionOptions? Options { get; set; } + /// Original remote resource identifier. + [JsonPropertyName("resourceId")] + public string? ResourceId { get; set; } - /// Session to register extension tools on. + /// SDK session ID for the connected remote session. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; + + /// Remote session staleness deadline as an ISO 8601 string. + [JsonPropertyName("staleAt")] + public DateTimeOffset? StaleAt { get; set; } + + /// Session start time as an ISO 8601 string. + [JsonPropertyName("startTime")] + public DateTimeOffset StartTime { get; set; } + + /// Remote session state returned by the backing service. + [JsonPropertyName("state")] + public string? State { get; set; } + + /// Optional session summary. + [JsonPropertyName("summary")] + public string? Summary { get; set; } } -/// Params to attach or detach an in-process ExtensionController delegate. +/// Remote session connection result. [Experimental(Diagnostics.Experimental)] -internal sealed class ConfigureSessionExtensionsParams +public sealed class RemoteSessionConnectionResult { - /// 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; } + /// Metadata for a connected remote session. + [JsonPropertyName("metadata")] + public ConnectedRemoteSessionMetadata Metadata { get => field ??= new(); set; } - /// Session to attach the extension controller delegate to. + /// SDK session ID for the connected remote session. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Outcome of an agentRegistry.spawn call. -/// Polymorphic base type discriminated by kind. +/// Remote session connection parameters. [Experimental(Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(AgentRegistrySpawnResultSpawned), "spawned")] -[JsonDerivedType(typeof(AgentRegistrySpawnResultSpawnError), "spawn-error")] -[JsonDerivedType(typeof(AgentRegistrySpawnResultRegistryTimeout), "registry-timeout")] -[JsonDerivedType(typeof(AgentRegistrySpawnResultValidationError), "validation-error")] -public partial class AgentRegistrySpawnResult +internal sealed class ConnectRemoteSessionParams { - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; + /// Session ID to connect to. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } - -/// Full registry entry for the spawned child. Lets the controller call `handleLiveTargetSelected(entry)` directly without re-reading the registry (avoids a TOCTOU window). +/// Local or remote session metadata entry. Narrow on `isRemote` to access source-specific fields. +/// Data type discriminated by isRemote. [Experimental(Diagnostics.Experimental)] -public sealed class AgentRegistryLiveTargetEntry +public partial class SessionListEntry { - /// Kind of attention required when status === "attention". Meaningful only when status === "attention". - [JsonPropertyName("attentionKind")] - public AgentRegistryLiveTargetEntryAttentionKind? AttentionKind { get; set; } + /// The boolean discriminator. + [JsonPropertyName("isRemote")] + public bool IsRemote { get; set; } - /// Git branch of the session (when known). - [JsonPropertyName("branch")] - public string? Branch { get; set; } + /// Runtime client name that created/last resumed this session. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("clientName")] + public string? ClientName { get; set; } - /// Copilot CLI version that wrote the entry. - [JsonPropertyName("copilotVersion")] - public string CopilotVersion { get; set; } = string.Empty; + /// Pre-resolved working-directory context for session startup. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("context")] + public SessionContext? Context { get; set; } - /// Working directory of the session (when known). - [JsonPropertyName("cwd")] - public string? Cwd { get; set; } + /// Host-supplied human description of what the session is doing right now ("running tests", "waiting for approval"). Optional in the protocol and absent on hosts that do not publish it, so never rely on it -- it enriches `hostStatus`, it does not replace it. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("hostActivity")] + public string? HostActivity { get; set; } - /// Bind host for the entry's JSON-RPC server. - [JsonPropertyName("host")] - public string Host { get; set; } = string.Empty; + /// Live status as the owning host reports it in its session listing, so a row for a session running elsewhere can show that it is running. Absent for hosts that publish no such status (the cloud task managers), which read as idle. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("hostStatus")] + public RemoteSessionHostStatus? HostStatus { get; set; } - /// Process kind tag for the registry entry. - [JsonPropertyName("kind")] - public AgentRegistryLiveTargetEntryKind Kind { get; set; } + /// True for detached maintenance sessions that should be hidden from normal resume lists. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("isDetached")] + public bool? IsDetached { get; set; } - /// Wall-clock milliseconds since the watcher last observed this entry (heartbeat freshness). - [JsonPropertyName("lastSeenMs")] - public long LastSeenMs { get; set; } + /// GitHub task ID, when this local session is bound to one. Only present for local sessions exported to remote control. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("mcTaskId")] + public string? McTaskId { get; set; } - /// How the most recent turn ended (clean vs aborted). Lets the renderer distinguish done from done_cancelled. - [JsonPropertyName("lastTerminalEvent")] - public AgentRegistryLiveTargetEntryLastTerminalEvent? LastTerminalEvent { get; set; } + /// Last-modified time of the session's persisted state, as ISO 8601. + [JsonPropertyName("modifiedTime")] + public required string ModifiedTime { get; set; } - /// Model identifier currently selected for the session. - [JsonPropertyName("model")] - public string? Model { get; set; } + /// Optional human-friendly name set via /rename. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("name")] + public string? Name { get; set; } - /// Operating-system pid of the process owning this entry. - [JsonPropertyName("pid")] - public long Pid { get; set; } + /// Pull request number associated with the session. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("pullRequestNumber")] + public long? PullRequestNumber { get; set; } - /// TCP port the entry's JSON-RPC server is listening on. - [JsonPropertyName("port")] - public long Port { get; set; } + /// Backing remote session IDs (most recent first). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("remoteSessionIds")] + public IList? RemoteSessionIds { get; set; } - /// Registry entry schema version (1 = ui-server, 2 = managed-server). - [JsonPropertyName("schemaVersion")] - public long SchemaVersion { get; set; } + /// GitHub repository the remote session belongs to. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("repository")] + public RemoteSessionMetadataRepository? Repository { get; set; } - /// Session ID of the foreground session for this entry. + /// Original remote resource identifier (task ID or PR node ID). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("resourceId")] + public string? ResourceId { get; set; } + + /// Stable session identifier. [JsonPropertyName("sessionId")] - public string? SessionId { get; set; } + public required string SessionId { get; set; } - /// Friendly session name (when set). - [JsonPropertyName("sessionName")] - public string? SessionName { get; set; } + /// Deadline (ISO 8601) at which a CLI remote session becomes stale without further heartbeats. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("staleAt")] + public string? StaleAt { get; set; } - /// ISO 8601 timestamp captured at registration. - [JsonPropertyName("startedAt")] - public string StartedAt { get; set; } = string.Empty; + /// Session creation time as an ISO 8601 timestamp. + [JsonPropertyName("startTime")] + public required string StartTime { get; set; } - /// Coarse lifecycle status of the foreground session. - [JsonPropertyName("status")] - public AgentRegistryLiveTargetEntryStatus? Status { get; set; } + /// Server-side task state returned by GitHub. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("state")] + public string? State { get; set; } - /// Monotonic per-publisher revision counter incremented on every status update. Lets watchers detect transient flips. - [JsonPropertyName("statusRevision")] - public long? StatusRevision { get; set; } + /// Short summary of the session, when one has been derived. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("summary")] + public string? Summary { get; set; } - /// Connection token (null when the target is unauthenticated). - [JsonInclude] - [JsonPropertyName("token")] - internal string? Token { get; set; } + /// Whether the remote task originated from CCA or CLI `--remote`. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("taskType")] + public RemoteSessionMetadataTaskType? TaskType { get; set; } } -/// Per-spawn log-capture outcome; populated from spawnLiveTarget. +/// Sessions matching the filter, ordered most-recently-modified first. [Experimental(Diagnostics.Experimental)] -public sealed class AgentRegistryLogCapture +public sealed class SessionList { - /// Whether per-spawn log capture is on (false when env-disabled or open failed). - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } + /// Sessions ordered most-recently-modified first. Discriminated by `isRemote`. + [JsonPropertyName("sessions")] + public IList Sessions { get => field ??= []; set; } +} - /// Human-readable open failure message (only set when enabled === false AND the env-disable opt-out was NOT used). - [JsonPropertyName("openError")] - public string? OpenError { get; set; } +/// Optional filter applied to the returned sessions. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionListFilter +{ + /// Match sessions whose context.branch equals this value. + [JsonPropertyName("branch")] + public string? Branch { get; set; } - /// Categorized reason for log-open failure. - [JsonPropertyName("openErrorReason")] - public AgentRegistryLogCaptureOpenErrorReason? OpenErrorReason { get; set; } + /// Match sessions whose context.cwd equals this value. + [JsonPropertyName("cwd")] + public string? Cwd { get; set; } - /// Absolute path to the per-spawn log file (only set when enabled). - [JsonPropertyName("path")] - public string? Path { get; set; } + /// Match sessions whose context.gitRoot equals this value. + [JsonPropertyName("gitRoot")] + public string? GitRoot { get; set; } + + /// Match sessions whose context.repository equals this value. + [JsonPropertyName("repository")] + public string? Repository { get; set; } } -/// Managed-server child was spawned and registered successfully. -/// The spawned variant of . +/// Optional source filter, metadata-load limit, and context filter applied to the returned sessions. [Experimental(Diagnostics.Experimental)] -public partial class AgentRegistrySpawnResultSpawned : AgentRegistrySpawnResult +internal sealed class SessionsListRequest { - /// - [JsonIgnore] - public override string Kind => "spawned"; + /// Optional filter applied to the returned sessions. + [JsonPropertyName("filter")] + public SessionListFilter? Filter { get; set; } - /// Full registry entry for the spawned child. Lets the controller call `handleLiveTargetSelected(entry)` directly without re-reading the registry (avoids a TOCTOU window). - [JsonPropertyName("entry")] - public required AgentRegistryLiveTargetEntry Entry { get; set; } + /// When true, include detached maintenance sessions. Defaults to false for user-facing session lists. + [JsonPropertyName("includeDetached")] + public bool? IncludeDetached { get; set; } - /// If the delegate attempted to send the initial prompt and failed, the categorized error message. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("initialPromptError")] - public string? InitialPromptError { get; set; } + /// 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). + [JsonPropertyName("metadataLimit")] + public long? MetadataLimit { get; set; } - /// Whether the delegate already sent the initial prompt. Always omitted in the current wiring: the controller sends the prompt post-attach via the standard LocalRpcSession.send path. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("initialPromptSent")] - public bool? InitialPromptSent { get; set; } + /// Which session sources to include. Defaults to `local` for backward compatibility. + [JsonPropertyName("source")] + public SessionSource? Source { get; set; } - /// Per-spawn log-capture outcome; populated from spawnLiveTarget. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("logCapture")] - public AgentRegistryLogCapture? LogCapture { get; set; } + /// 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. + [JsonPropertyName("throwOnError")] + public bool? ThrowOnError { get; set; } } -/// `child_process.spawn` itself failed before the child entered the registry. -/// The spawn-error variant of . +/// Persisted local session metadata, including identifiers, timestamps, summary/name, client, context, detached state, and task ID. [Experimental(Diagnostics.Experimental)] -public partial class AgentRegistrySpawnResultSpawnError : AgentRegistrySpawnResult +public sealed class LocalSessionMetadataValue { - /// - [JsonIgnore] - public override string Kind => "spawn-error"; + /// Runtime client name that created/last resumed this session. + [JsonPropertyName("clientName")] + public string? ClientName { get; set; } - /// Underlying errno code (e.g. ENOENT, EACCES) when available. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("code")] - public string? Code { get; set; } + /// Pre-resolved working-directory context for session startup. + [JsonPropertyName("context")] + public SessionContext? Context { get; set; } - /// Human-readable error message. - [JsonPropertyName("message")] - public required string Message { get; set; } -} + /// True for detached maintenance sessions that should be hidden from normal resume lists. + [JsonPropertyName("isDetached")] + public bool? IsDetached { get; set; } -/// Spawn succeeded but the child did not publish a matching managed-server entry within the timeout. -/// The registry-timeout variant of . -[Experimental(Diagnostics.Experimental)] -public partial class AgentRegistrySpawnResultRegistryTimeout : AgentRegistrySpawnResult -{ - /// - [JsonIgnore] - public override string Kind => "registry-timeout"; + /// Always false for local sessions. + [JsonPropertyName("isRemote")] + public bool IsRemote { get; set; } - /// Process ID of the orphaned child (so the caller can offer 'kill the pid' guidance). - [JsonPropertyName("childPid")] - public required long ChildPid { 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; } - /// Per-spawn log-capture outcome; populated from spawnLiveTarget. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("logCapture")] - public AgentRegistryLogCapture? LogCapture { get; set; } -} + /// Last-modified time of the session's persisted state, as ISO 8601. + [JsonPropertyName("modifiedTime")] + public string ModifiedTime { get; set; } = string.Empty; -/// Synchronous pre-validation rejected the spawn request. -/// The validation-error variant of . -[Experimental(Diagnostics.Experimental)] -public partial class AgentRegistrySpawnResultValidationError : AgentRegistrySpawnResult -{ - /// - [JsonIgnore] - public override string Kind => "validation-error"; + /// Optional human-friendly name set via /rename. + [JsonPropertyName("name")] + public string? Name { get; set; } - /// Which parameter field was invalid. Omitted when the rejection is not field-specific. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("field")] - public AgentRegistrySpawnValidationErrorField? Field { get; set; } + /// Stable session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// Human-readable explanation; safe to surface in the UI banner. Never logged to unrestricted telemetry. - [JsonPropertyName("message")] - public required string Message { get; set; } + /// Session creation time as an ISO 8601 timestamp. + [JsonPropertyName("startTime")] + public string StartTime { get; set; } = string.Empty; - /// Categorized reason for the rejection. Low-cardinality enum so telemetry can aggregate by reason without leaking raw paths or agent/model names. - [JsonPropertyName("reason")] - public required AgentRegistrySpawnValidationErrorReason Reason { get; set; } + /// Short summary of the session, when one has been derived. + [JsonPropertyName("summary")] + public string? Summary { get; set; } } -/// Inputs to spawn a managed-server child via the controller's spawn delegate. +/// Persisted local session metadata when the session exists. [Experimental(Diagnostics.Experimental)] -internal sealed class AgentRegistrySpawnRequest +internal sealed class SessionsGetMetadataResult { - /// Custom or built-in agent name (e.g. 'explore'). When omitted, the child uses its own default. - [JsonPropertyName("agentName")] - public string? AgentName { get; set; } + /// Local session metadata, omitted when the session does not exist. + [JsonPropertyName("session")] + public LocalSessionMetadataValue? Session { get; set; } +} - /// Working directory for the spawned child (must be an existing directory). - [JsonPropertyName("cwd")] - public string Cwd { get; set; } = string.Empty; +/// 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; +} - /// Optional first user message. Forwarded to the caller (the CLI's spawn wrapper sends it post-attach via the standard LocalRpcSession.send path). - [JsonPropertyName("initialPrompt")] - public string? InitialPrompt { get; set; } +/// 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; - /// Model identifier to apply to the new session. - [JsonPropertyName("model")] - public string? Model { 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 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; } - /// Friendly session name. Must satisfy validateSessionName: non-empty, no leading/trailing whitespace, <=100 chars, no control chars, no double quotes. - [JsonPropertyName("name")] - public string? Name { 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; } - /// Permission posture for the new session. 'yolo' requires the controller-local session to currently be in allow-all mode. - [JsonPropertyName("permissionMode")] - public AgentRegistrySpawnPermissionMode? PermissionMode { get; 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; } } -/// Identifies the target session. +/// Pagination options for reading an inactive or active local session's persisted event journal. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionSuspendRequest +internal sealed class SessionsReadPersistedEventsRequest { - /// Target session identifier. + /// Opaque cursor returned by a previous persisted-event read. Omit on the first call. + [JsonPropertyName("cursor")] + public string? Cursor { get; set; } + + /// Direction to page through persisted history. Forward starts at the beginning; backward starts with the newest events. Events in each page remain chronological. + [JsonPropertyName("direction")] + public EventsReadDirection? Direction { get; set; } + + /// Maximum number of events to return in this batch (1–1000, default 200). + [JsonPropertyName("max")] + public long? Max { get; set; } + + /// Session ID whose persisted event journal should be read. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Result of sending a user message. +/// Recent local session IDs that contain user-visible history. [Experimental(Diagnostics.Experimental)] -public sealed class SendResult +internal sealed class SessionsListNonEmptySessionIdsResult { - /// Unique identifier assigned to the message. - [JsonPropertyName("messageId")] - public string MessageId { get; set; } = string.Empty; + /// Session IDs ordered newest-first. + [JsonPropertyName("sessionIds")] + public IList SessionIds { get => field ??= []; set; } } -/// Parameters for sending a user message to the session. +/// Limit for non-empty local session IDs. [Experimental(Diagnostics.Experimental)] -internal sealed class SendRequest +internal sealed class SessionsListNonEmptySessionIdsRequest { - /// The UI mode the agent was in when this message was sent. Defaults to the session's current mode. - [JsonPropertyName("agentMode")] - public SendAgentMode? AgentMode { get; set; } - - /// Optional attachments (files, directories, selections, blobs, GitHub references) to include with the message. - [JsonPropertyName("attachments")] - public IList? Attachments { get; set; } - - /// If false, this message will not trigger a Premium Request Unit charge. User messages default to billable. - [JsonPropertyName("billable")] - public bool? Billable { get; set; } - - /// If provided, this is shown in the timeline instead of `prompt`. - [JsonPropertyName("displayPrompt")] - public string? DisplayPrompt { get; set; } - - /// How to deliver the message. `enqueue` (default) appends to the message queue. `immediate` interjects during an in-progress turn. - [JsonPropertyName("mode")] - public SendMode? Mode { get; set; } - - /// If true, adds the message to the front of the queue instead of the end. - [JsonPropertyName("prepend")] - public bool? Prepend { get; set; } - - /// The user message text. - [JsonPropertyName("prompt")] - public string Prompt { get; set; } = string.Empty; - - /// 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. - [JsonPropertyName("requestHeaders")] - public IDictionary? RequestHeaders { get; set; } - - /// 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. - [JsonPropertyName("requiredTool")] - public string? RequiredTool { get; set; } + /// Maximum number of session IDs to return. + [JsonPropertyName("limit")] + public long? Limit { get; set; } +} - /// Target session identifier. +/// ID of the local session bound to the given GitHub task, or omitted when none. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionsFindByTaskIDResult +{ + /// Omitted when no local session is bound to that GitHub task. [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - - /// 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; } - - /// W3C Trace Context traceparent header for distributed tracing of this agent turn. - [JsonPropertyName("traceparent")] - public string? Traceparent { get; set; } - - /// W3C Trace Context tracestate header for distributed tracing. - [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. 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; } + public string? SessionId { get; set; } } -/// Result of sending zero or more user messages. +/// GitHub task ID to look up. [Experimental(Diagnostics.Experimental)] -public sealed class SendMessagesResult +internal sealed class SessionsFindByTaskIDRequest { - /// Unique identifiers assigned to the messages, one per provided message in order. Empty when no messages were provided. - [JsonPropertyName("messageIds")] - public IList MessageIds { get => field ??= []; set; } + /// GitHub task ID to look up. + [JsonPropertyName("taskId")] + public string TaskId { get; set; } = string.Empty; } -/// A single user message to append to the session as part of a `session.sendMessages` turn. +/// Session ID matching the prefix, omitted when no unique match exists. [Experimental(Diagnostics.Experimental)] -public sealed class SendMessageItem +public sealed class SessionsFindByPrefixResult { - /// Optional attachments (files, directories, selections, blobs, GitHub references) to include with this message. - [JsonPropertyName("attachments")] - public IList? Attachments { get; set; } - - /// If false, this message will not trigger a Premium Request Unit charge. User messages default to billable. - [JsonInclude] - [JsonPropertyName("billable")] - internal bool? Billable { get; set; } - - /// If provided, this is shown in the timeline instead of `prompt`. - [JsonPropertyName("displayPrompt")] - public string? DisplayPrompt { get; set; } - - /// The user message text. - [JsonPropertyName("prompt")] - public string Prompt { get; set; } = string.Empty; - - /// 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. - [JsonPropertyName("requiredTool")] - public string? RequiredTool { get; set; } - - /// 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; } + /// Omitted when no unique session matches the prefix (no match or ambiguous). + [JsonPropertyName("sessionId")] + public string? SessionId { get; set; } } -/// 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. +/// UUID prefix to resolve to a unique session ID. [Experimental(Diagnostics.Experimental)] -internal sealed class SendMessagesRequest +internal sealed class SessionsFindByPrefixRequest { - /// The UI mode the agent was in when these messages were sent. Defaults to the session's current mode. - [JsonPropertyName("agentMode")] - public SendAgentMode? AgentMode { get; set; } - - /// 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. - [JsonPropertyName("messages")] - public IList Messages { get => field ??= []; set; } - - /// How to deliver the messages. `enqueue` (default) appends to the message queue. `immediate` interjects during an in-progress turn. - [JsonPropertyName("mode")] - public SendMode? Mode { get; set; } - - /// If true, adds the messages to the front of the queue instead of the end. - [JsonPropertyName("prepend")] - public bool? Prepend { get; set; } - - /// 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. - [JsonPropertyName("requestHeaders")] - public IDictionary? RequestHeaders { get; set; } + /// 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. + [JsonPropertyName("prefix")] + public string Prefix { get; set; } = string.Empty; +} - /// Target session identifier. +/// Most-relevant session ID for the supplied context, or omitted when no sessions exist. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionsGetLastForContextResult +{ + /// Most-relevant session ID for the supplied context, or omitted when no sessions exist. [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - - /// W3C Trace Context traceparent header for distributed tracing of this agent turn. - [JsonPropertyName("traceparent")] - public string? Traceparent { get; set; } - - /// W3C Trace Context tracestate header for distributed tracing. - [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. 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; } + public string? SessionId { get; set; } } -/// Internal request for sending a system notification. +/// Optional working-directory context used to score session relevance. [Experimental(Diagnostics.Experimental)] -internal sealed class SendSystemNotificationRequest +internal sealed class SessionsGetLastForContextRequest { - /// 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; + /// Optional working-directory context used to score session relevance. When omitted the most-recently-modified session wins. + [JsonPropertyName("context")] + public SessionContext? Context { get; set; } +} - /// Internal delivery options, including passive policy. - [JsonPropertyName("options")] - public JsonElement? Options { get; set; } +/// Absolute path to the session's events.jsonl file on disk. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionsGetEventFilePathResult +{ + /// Absolute path to the session's events.jsonl file. + [JsonPropertyName("filePath")] + public string FilePath { get; set; } = string.Empty; +} - /// Target session identifier. +/// Session ID whose event-log file path to compute. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionsGetEventFilePathRequest +{ + /// Session ID whose event-log file path to compute. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Result of aborting the current turn. +/// Map of sessionId -> on-disk size in bytes for each session's workspace directory. [Experimental(Diagnostics.Experimental)] -public sealed class AbortResult +public sealed class SessionSizes { - /// Error message if the abort failed. - [JsonPropertyName("error")] - public string? Error { get; set; } - - /// Whether the abort completed successfully. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// Map of sessionId -> on-disk size in bytes for the session's workspace directory. + [JsonPropertyName("sizes")] + public IDictionary Sizes { get => field ??= new Dictionary(); set; } } -/// Parameters for aborting the current turn. +/// Session IDs from the input set that are currently in use by another process. [Experimental(Diagnostics.Experimental)] -internal sealed class AbortRequest +public sealed class SessionsCheckInUseResult { - /// Finite reason code describing why the current turn was aborted. - [JsonPropertyName("reason")] - public AbortReason? Reason { get; set; } - - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Session IDs from the input set that are currently held by another running process via an alive lock file. + [JsonPropertyName("inUse")] + public IList InUse { get => field ??= []; set; } } -/// Result of interrupting the main agent turn. +/// Session IDs to test for live in-use locks. [Experimental(Diagnostics.Experimental)] -public sealed class InterruptMainTurnResult +internal sealed class SessionsCheckInUseRequest { - /// Whether an in-flight main agent turn was interrupted. False when the main loop was not processing. - [JsonPropertyName("interrupted")] - public bool Interrupted { get; set; } + /// Session IDs to test for live in-use locks. + [JsonPropertyName("sessionIds")] + public IList SessionIds { get => field ??= []; set; } } -/// Parameters for interrupting the main agent turn. +/// The session's persisted remote-steerable flag, or omitted when no value has been persisted. [Experimental(Diagnostics.Experimental)] -internal sealed class InterruptMainTurnRequest +internal sealed class SessionsGetPersistedRemoteSteerableResult { - /// 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; + /// The session's persisted remote-steerable flag if recorded; omitted when no value has been persisted. + [JsonPropertyName("remoteSteerable")] + public bool? RemoteSteerable { get; set; } } -/// Identifies the target session. +/// Session ID to look up the persisted remote-steerable flag for. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionCancelAllBackgroundAgentsRequest +internal sealed class SessionsGetPersistedRemoteSteerableRequest { - /// Target session identifier. + /// Session ID to look up the persisted remote-steerable flag for. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Parameters for shutting down the session. +/// 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(Diagnostics.Experimental)] -internal sealed class ShutdownRequest +public sealed class SessionsCloseResult { - /// Optional human-readable reason. Typically the message of the error that triggered shutdown when type is 'error'. - [JsonPropertyName("reason")] - public string? Reason { get; set; } +} - /// Target session identifier. +/// Session ID to close. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionsCloseRequest +{ + /// Session ID to close. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; +} - /// Why the session is being shut down. Defaults to "routine" when omitted. - [JsonPropertyName("type")] - public ShutdownType? Type { get; set; } +/// Map of sessionId -> bytes freed by removing the session's workspace directory. +[Experimental(Diagnostics.Experimental)] +public sealed class 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). + [JsonPropertyName("freedBytes")] + public IDictionary FreedBytes { get => field ??= new Dictionary(); set; } } -/// Identifier of the session event that was emitted for the log message. +/// Session IDs to close, deactivate, and delete from disk. [Experimental(Diagnostics.Experimental)] -public sealed class LogResult +internal sealed class SessionsBulkDeleteRequest { - /// The unique identifier of the emitted session event. - [JsonPropertyName("eventId")] - public Guid EventId { get; set; } + /// Session IDs to close, deactivate, and delete from disk. + [JsonPropertyName("sessionIds")] + public IList SessionIds { get => field ??= []; set; } } -/// Message text, optional severity level, persistence flag, optional follow-up URL, and optional tip. +/// Session ID to delete from disk. [Experimental(Diagnostics.Experimental)] -internal sealed class LogRequest +internal sealed class SessionsDeleteRequest { - /// When true, the message is transient and not persisted to the session event log on disk. - [JsonPropertyName("ephemeral")] - public bool? Ephemeral { get; set; } + /// Session ID to delete. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// Log severity level. Determines how the message is displayed in the timeline. Defaults to "info". - [JsonPropertyName("level")] - public SessionLogLevel? Level { get; set; } + /// Internal resolved session directory path to delete. + [JsonPropertyName("sessionPath")] + public string? SessionPath { get; set; } +} - /// Human-readable message. - [JsonPropertyName("message")] - public string Message { get; set; } = string.Empty; +/// 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 +{ + /// Session IDs that would be deleted in dry-run mode (always empty otherwise). + [JsonPropertyName("candidates")] + public IList Candidates { get => field ??= []; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Session IDs that were deleted (always empty in dry-run mode). + [JsonPropertyName("deleted")] + public IList Deleted { get => field ??= []; set; } - /// Optional actionable tip displayed alongside the message. Only honored on `level: "info"`. - [JsonPropertyName("tip")] - public string? Tip { get; set; } + /// True when no deletions were actually performed. + [JsonPropertyName("dryRun")] + public bool DryRun { get; set; } - /// Domain category for this log entry (e.g., "mcp", "subscription", "policy", "model"). Maps to `infoType`/`warningType`/`errorType` on the emitted event. Defaults to "notification". - [JsonPropertyName("type")] - public string? Type { get; set; } + /// Total bytes freed (actual when not dry-run, projected when dry-run). + [JsonPropertyName("freedBytes")] + public long FreedBytes { get; set; } - /// Optional URL the user can open in their browser for more details. - [Url] - [StringSyntax(StringSyntaxAttribute.Uri)] - [JsonPropertyName("url")] - public string? Url { get; set; } + /// Session IDs that were skipped (e.g., named sessions). + [JsonPropertyName("skipped")] + public IList Skipped { get => field ??= []; set; } } -/// Authentication status and account metadata for the session. +/// Age threshold and optional flags controlling which old sessions are pruned (or simulated when dryRun is true). [Experimental(Diagnostics.Experimental)] -public sealed class SessionAuthStatus +internal sealed class SessionsPruneOldRequest { - /// Authentication type. - [JsonPropertyName("authType")] - public AuthInfoType? AuthType { get; set; } - - /// Copilot plan tier (e.g., individual_pro, business). - [JsonPropertyName("copilotPlan")] - public string? CopilotPlan { get; set; } + /// When true, only report what would be deleted without performing any deletion. + [JsonPropertyName("dryRun")] + public bool? DryRun { get; set; } - /// Authentication host URL. - [Url] - [StringSyntax(StringSyntaxAttribute.Uri)] - [JsonPropertyName("host")] - public string? Host { get; set; } + /// Session IDs that should never be considered for pruning. + [JsonPropertyName("excludeSessionIds")] + public IList? ExcludeSessionIds { get; set; } - /// Whether the session has resolved authentication. - [JsonPropertyName("isAuthenticated")] - public bool IsAuthenticated { get; set; } + /// When true, named sessions (set via /rename) are also eligible for pruning. + [JsonPropertyName("includeNamed")] + public bool? IncludeNamed { get; set; } - /// Authenticated login/username, if available. - [JsonPropertyName("login")] - public string? Login { get; set; } + /// Delete sessions whose modifiedTime is at least this many days old. + [JsonPropertyName("olderThanDays")] + public long OlderThanDays { get; set; } +} - /// Human-readable authentication status description. - [JsonPropertyName("statusMessage")] - public string? StatusMessage { get; set; } +/// Flush a session's pending events to disk. No-op when no writer exists for the session (e.g., already closed). +[Experimental(Diagnostics.Experimental)] +public sealed class SessionsSaveResult +{ } -/// Identifies the target session. +/// Session ID whose pending events should be flushed to disk. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionGitHubAuthGetStatusRequest +internal sealed class SessionsSaveRequest { - /// Target session identifier. + /// Session ID whose pending events should be flushed to disk. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Indicates whether the credential update succeeded. +/// 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. [Experimental(Diagnostics.Experimental)] -public sealed class SessionSetCredentialsResult +public sealed class SessionsReleaseLockResult { - /// 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). - [JsonPropertyName("copilotUserResolved")] - public bool? CopilotUserResolved { get; set; } - - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } } -/// New auth credentials to install on the session. Omit to leave credentials unchanged. +/// Session ID whose in-use lock should be released. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionSetCredentialsParams +internal sealed class SessionsReleaseLockRequest { - /// 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. - [JsonPropertyName("credentials")] - public AuthInfo? Credentials { get; set; } - - /// Target session identifier. + /// Session ID whose in-use lock should be released. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// A file included in the redacted debug bundle. +/// 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 DebugCollectLogsCollectedEntry +public sealed class SessionEnrichMetadataResult { - /// Relative path of the file in the staged bundle/archive. - [JsonPropertyName("bundlePath")] - public string BundlePath { get; set; } = string.Empty; - - /// Redacted output size in bytes. - [JsonPropertyName("sizeBytes")] - public long SizeBytes { get; set; } - - /// Source category for this entry. - [JsonPropertyName("source")] - public DebugCollectLogsSource Source { get; set; } + /// Enriched records, with summary and context backfilled. Sessions confirmed empty and unnamed may be omitted. + [JsonPropertyName("sessions")] + public IList Sessions { get => field ??= []; set; } } -/// An optional debug bundle entry that could not be included. +/// Session metadata records to enrich with summary and context information. [Experimental(Diagnostics.Experimental)] -public sealed class DebugCollectLogsSkippedEntry +internal sealed class SessionsEnrichMetadataRequest { - /// Relative path requested for this bundle entry. - [JsonPropertyName("bundlePath")] - public string BundlePath { get; set; } = string.Empty; - - /// Server-local source path that could not be read. - [JsonPropertyName("path")] - public string? Path { get; set; } - - /// Reason the entry was skipped. - [JsonPropertyName("reason")] - public string Reason { get; set; } = string.Empty; + /// Session metadata records to enrich. Records that already have summary and context are returned unchanged. + [JsonPropertyName("sessions")] + public IList Sessions { get => field ??= []; set; } } -/// Result of collecting a redacted debug bundle. +/// 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. [Experimental(Diagnostics.Experimental)] -public sealed class DebugCollectLogsResult +public sealed class SessionsReloadPluginHooksResult { - /// Files included in the redacted bundle. - [JsonPropertyName("entries")] - public IList Entries { get => field ??= []; set; } - - /// Destination kind that was written. - [JsonPropertyName("kind")] - public DebugCollectLogsResultKind Kind { get; set; } - - /// 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. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; - - /// Optional files or directories that could not be included. - [JsonPropertyName("skippedEntries")] - public IList? SkippedEntries { get; set; } } -/// A caller-provided server-local file or directory to include in the debug bundle. +/// Active session ID and an optional flag for deferring repo-level hooks until folder trust. [Experimental(Diagnostics.Experimental)] -public sealed class DebugCollectLogsEntry +internal sealed class SessionsReloadPluginHooksRequest { - /// Relative path to use inside the staged bundle/archive. - [JsonPropertyName("bundlePath")] - public string BundlePath { get; set; } = string.Empty; - - /// Kind of source path to include. - [JsonPropertyName("kind")] - public DebugCollectLogsEntryKind Kind { get; set; } + /// When true, skip repo-level hooks. Use before folder trust is confirmed; loadDeferredRepoHooks loads them post-trust. + [JsonPropertyName("deferRepoHooks")] + public bool? DeferRepoHooks { get; set; } - /// Server-local source path to read. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// Active session ID to reload hooks for. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// How text content from this entry should be redacted. Defaults to plain-text. - [JsonPropertyName("redaction")] - public DebugCollectLogsRedaction? Redaction { get; set; } +/// Queued repo-level startup prompts and the total hook command count after loading. +[Experimental(Diagnostics.Experimental)] +public sealed class 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. + [JsonPropertyName("hookCount")] + public long HookCount { get; set; } - /// When true, collection fails if this entry cannot be read. Defaults to false, which records the entry in `skippedEntries`. - [JsonPropertyName("required")] - public bool? Required { get; set; } + /// Repo-level startup prompts queued from repo hook configs. Empty on resume, when no repo configs were pending, or when disableAllHooks is set. + [JsonPropertyName("startupPrompts")] + public IList StartupPrompts { get => field ??= []; set; } } -/// Destination for the redacted debug bundle. -/// Polymorphic base type discriminated by kind. +/// Active session ID whose deferred repo-level hooks should be loaded. [Experimental(Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(DebugCollectLogsDestinationArchive), "archive")] -[JsonDerivedType(typeof(DebugCollectLogsDestinationDirectory), "directory")] -public partial class DebugCollectLogsDestination +internal sealed class SessionsLoadDeferredRepoHooksRequest { - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; + /// Active session ID whose deferred repo-level hooks should be loaded. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } - -/// The archive variant of . +/// 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. [Experimental(Diagnostics.Experimental)] -public partial class DebugCollectLogsDestinationArchive : DebugCollectLogsDestination +public sealed class SessionsSetAdditionalPluginsResult { - /// - [JsonIgnore] - public override string Kind => "archive"; - - /// When true, create the archive atomically without overwriting an existing file by appending ` (N)` before the extension as needed. Defaults to false. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("noOverwrite")] - public bool? NoOverwrite { get; set; } - - /// Absolute or server-relative path for the .tgz archive to create. - [JsonPropertyName("outputPath")] - public required string OutputPath { get; set; } } -/// The directory variant of . +/// Installed plugin record from global state, with marketplace, version, install time, enabled state, cache path, and source. [Experimental(Diagnostics.Experimental)] -public partial class DebugCollectLogsDestinationDirectory : DebugCollectLogsDestination +public sealed class InstalledPlugin { - /// - [JsonIgnore] - public override string Kind => "directory"; + /// Path where the plugin is cached locally. + [JsonPropertyName("cache_path")] + public string? CachePath { get; set; } - /// Directory where redacted files should be staged. The directory is created if needed. - [JsonPropertyName("outputDirectory")] - public required string OutputDirectory { get; set; } -} + /// Whether the plugin is currently enabled. + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } -/// Built-in session diagnostics to include in the bundle. Omitted fields default to true. -[Experimental(Diagnostics.Experimental)] -public sealed class DebugCollectLogsInclude -{ - /// Server-local path to the current process log. When set, it is included as `process.log` and its directory is searched for prior logs from the same session. - [JsonPropertyName("currentProcessLogPath")] - public string? CurrentProcessLogPath { get; set; } + /// Installation timestamp. + [JsonPropertyName("installed_at")] + public string InstalledAt { get; set; } = string.Empty; - /// Include the session event log (`events.jsonl`). Defaults to true. - [JsonPropertyName("events")] - public bool? Events { get; set; } + /// Absolute path of the marketplace directory a live plugin was resolved from. Present only on live, never-persisted records — those synthesized at session start for a directory/local marketplace, whose cache_path points at the real plugin directory on disk rather than a copy under the installed-plugins cache. Its presence is what marks a record as live, and no record carrying it is ever written to the persisted installedPlugins key. + [JsonPropertyName("installed_from")] + public string? InstalledFrom { get; set; } - /// Server-local path to the session's events.jsonl file. Internal callers normally omit this and let the runtime derive it from the session. - [JsonPropertyName("eventsPath")] - public string? EventsPath { get; set; } + /// Marketplace the plugin came from (empty string for direct repo installs). + [JsonPropertyName("marketplace")] + public string Marketplace { get; set; } = string.Empty; - /// Maximum number of previous process logs to include. Defaults to 5. - [JsonPropertyName("previousProcessLogLimit")] - public long? PreviousProcessLogLimit { get; set; } + /// Plugin name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// Server-local process log directory to search when `currentProcessLogPath` is unavailable, useful for collecting logs for inactive sessions. - [JsonPropertyName("processLogDirectory")] - public string? ProcessLogDirectory { get; set; } + /// Source for direct repo installs (when marketplace is empty). + [JsonPropertyName("source")] + public JsonElement? Source { get; set; } - /// Include process logs for the session. Defaults to true. - [JsonPropertyName("processLogs")] - public bool? ProcessLogs { 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; } - /// Include interactive shell logs written under the session's `shell-logs` directory. Defaults to true. - [JsonPropertyName("shellLogs")] - public bool? ShellLogs { get; set; } + /// Version installed (if available). + [JsonPropertyName("version")] + public string? Version { get; set; } } -/// Options for collecting a redacted session debug bundle. +/// Manager-wide additional plugins to register; replaces any previously-configured set. [Experimental(Diagnostics.Experimental)] -internal sealed class DebugCollectLogsRequest +internal sealed class SessionsSetAdditionalPluginsRequest { - /// Caller-provided server-local files or directories to include in addition to the runtime's built-in session diagnostics. This lets host applications add their own diagnostics without changing the API shape. - [JsonPropertyName("additionalEntries")] - public IList? AdditionalEntries { get; set; } - - /// Where the redacted bundle should be written. Use `archive` to produce a .tgz, or `directory` to stage redacted files for caller-managed upload/post-processing. - [JsonPropertyName("destination")] - public DebugCollectLogsDestination Destination { get => field ??= new(); set; } + /// Manager-wide additional plugins to register. Replaces any previously-configured set. Pass an empty array to clear. + [JsonPropertyName("plugins")] + public IList Plugins { get => field ??= []; set; } +} - /// Which built-in session diagnostics to include. Omitted fields default to true. - [JsonPropertyName("include")] - public DebugCollectLogsInclude? Include { get; set; } +/// Dynamic-context board entry count, when available. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionsGetBoardEntryCountResult +{ + /// Board entry count, when available. + [JsonPropertyName("count")] + public long? Count { get; set; } +} - /// Target session identifier. +/// Session ID whose board entry count should be returned. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionsGetBoardEntryCountRequest +{ + /// Session ID whose board entry count should be returned. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Canvas action that the agent or host can invoke. To discover the input schema for a particular action, call the list_canvas_capabilities tool. +/// State of the runtime-managed remote-control singleton. +/// Polymorphic base type discriminated by state. [Experimental(Diagnostics.Experimental)] -public sealed class CanvasAction +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "state", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(RemoteControlStatusOff), "off")] +[JsonDerivedType(typeof(RemoteControlStatusConnecting), "connecting")] +[JsonDerivedType(typeof(RemoteControlStatusActive), "active")] +[JsonDerivedType(typeof(RemoteControlStatusError), "error")] +public partial class RemoteControlStatus { - /// Description of the action. - [JsonPropertyName("description")] - public string? Description { get; set; } + /// The type discriminator. + [JsonPropertyName("state")] + public virtual string State { get; set; } = string.Empty; +} - /// JSON Schema for the action input. - [JsonPropertyName("inputSchema")] - public JsonElement? InputSchema { get; set; } - /// Action name exposed by the canvas provider. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; +/// Remote control is not connected. +/// The off variant of . +[Experimental(Diagnostics.Experimental)] +public partial class RemoteControlStatusOff : RemoteControlStatus +{ + /// + [JsonIgnore] + public override string State => "off"; } -/// Canvas available in the current session. +/// Remote control is in the middle of initial setup. +/// The connecting variant of . [Experimental(Diagnostics.Experimental)] -public sealed class DiscoveredCanvas +public partial class RemoteControlStatusConnecting : RemoteControlStatus { - /// Actions the agent or host may invoke on an open instance. - [JsonPropertyName("actions")] - public IList? Actions { get; set; } - - /// Provider-local canvas identifier. - [JsonPropertyName("canvasId")] - public string CanvasId { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string State => "connecting"; - /// Short, single-sentence description shown to the agent in canvas catalogs. - [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("description")] - public string Description { get; set; } = string.Empty; + /// Session id the connection is attaching to. + [JsonPropertyName("attachedSessionId")] + public required string AttachedSessionId { get; set; } +} - /// Human-readable canvas name. - [JsonPropertyName("displayName")] - public string DisplayName { get; set; } = string.Empty; +/// Remote control is connected to a local session. +/// The active variant of . +[Experimental(Diagnostics.Experimental)] +public partial class RemoteControlStatusActive : RemoteControlStatus +{ + /// + [JsonIgnore] + public override string State => "active"; - /// Owning provider identifier. - [JsonPropertyName("extensionId")] - public string ExtensionId { get; set; } = string.Empty; + /// Session id remote control is pointed at. + [JsonPropertyName("attachedSessionId")] + public required string AttachedSessionId { get; set; } - /// Owning extension display name, when available. - [JsonPropertyName("extensionName")] - public string? ExtensionName { get; set; } + /// 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. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonInclude] + [JsonPropertyName("awaitingFirstMessage")] + internal bool? AwaitingFirstMessage { get; set; } - /// Host-local PNG path for the canvas icon, when supplied. - [JsonPropertyName("icon")] - public string? Icon { get; set; } + /// MC frontend URL for this session, when known. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("frontendUrl")] + public string? FrontendUrl { get; set; } - /// JSON Schema for canvas open input. - [JsonPropertyName("inputSchema")] - public JsonElement? InputSchema { get; set; } + /// Whether the MC session may steer this session. + [JsonPropertyName("isSteerable")] + public required bool IsSteerable { get; set; } } -/// Declared canvases available in this session. +/// The last setup attempt failed. The singleton is otherwise off. +/// The error variant of . [Experimental(Diagnostics.Experimental)] -public sealed class CanvasList +public partial class RemoteControlStatusError : RemoteControlStatus { - /// Declared canvases available in this session. - [JsonPropertyName("canvases")] - public IList Canvases { get => field ??= []; set; } + /// + [JsonIgnore] + public override string State => "error"; + + /// Session id the failing setup attempt targeted, when known. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("attachedSessionId")] + public string? AttachedSessionId { get; set; } + + /// Human-readable error message from the last setup attempt. + [JsonPropertyName("error")] + public required string Error { get; set; } } -/// Identifies the target session. +/// Wrapper for the singleton's current status. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionCanvasListRequest +public sealed class RemoteControlStatusResult { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// State of the runtime-managed remote-control singleton. + [JsonPropertyName("status")] + public RemoteControlStatus Status { get => field ??= new(); set; } } -/// Open canvas instance snapshot. +/// Reattach to an existing MC session without creating a new one. [Experimental(Diagnostics.Experimental)] -public sealed class OpenCanvasInstance +public sealed class RemoteControlConfigExistingMcSession { - /// Provider-local canvas identifier. - [JsonPropertyName("canvasId")] - public string CanvasId { get; set; } = string.Empty; + /// Existing MC session ID to reattach to. + [JsonPropertyName("mcSessionId")] + public string McSessionId { get; set; } = string.Empty; - /// Owning provider identifier. - [JsonPropertyName("extensionId")] - public string ExtensionId { get; set; } = string.Empty; + /// Existing MC task ID for the reattached session. + [JsonPropertyName("mcTaskId")] + public string McTaskId { get; set; } = string.Empty; +} - /// Owning extension display name, when available. - [JsonPropertyName("extensionName")] - public string? ExtensionName { get; set; } +/// Configuration for the runtime-managed remote-control singleton. +[Experimental(Diagnostics.Experimental)] +public sealed class RemoteControlConfig +{ + /// Reattach to an existing MC session without creating a new one. + [JsonPropertyName("existingMcSession")] + public RemoteControlConfigExistingMcSession? ExistingMcSession { get; set; } - /// Host-local PNG path for the canvas icon, when supplied. - [JsonPropertyName("icon")] - public string? Icon { get; set; } - - /// Input supplied when the instance was opened. - [JsonPropertyName("input")] - public JsonElement? Input { get; set; } + /// Whether the user explicitly requested remote (vs. implicit session-sync). Controls warning surfacing for missing-repo cases. + [JsonPropertyName("explicit")] + public bool Explicit { get; set; } - /// Stable caller-supplied canvas instance identifier. - [JsonPropertyName("instanceId")] - public string InstanceId { get; set; } = string.Empty; + /// Whether remote export should be enabled. + [JsonPropertyName("remote")] + public bool Remote { get; set; } - /// Provider-supplied status text. - [JsonPropertyName("status")] - public string? Status { get; set; } + /// When true, suppresses timeline messages on successful setup. + [JsonPropertyName("silent")] + public bool Silent { get; set; } - /// Rendered title. - [JsonPropertyName("title")] - public string? Title { get; set; } + /// Whether the MC session may steer the local session (write mode). + [JsonPropertyName("steerable")] + public bool Steerable { get; set; } - /// URL for web-rendered canvases. - [JsonPropertyName("url")] - public string? Url { get; set; } + /// Existing Mission Control task ID to attach the exported session to. + [JsonPropertyName("taskId")] + public string? TaskId { get; set; } } -/// Live open-canvas snapshot. +/// Parameters for attaching the remote-control singleton to a session. [Experimental(Diagnostics.Experimental)] -public sealed class CanvasListOpenResult +internal sealed class SessionsStartRemoteControlRequest { - /// Currently open canvas instances. - [JsonPropertyName("openCanvases")] - public IList OpenCanvases { get => field ??= []; set; } + /// Configuration for the runtime-managed remote-control singleton. + [JsonPropertyName("config")] + public RemoteControlConfig Config { get => field ??= new(); set; } + + /// Local session id to attach remote control to. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Identifies the target session. +/// Outcome of a transferRemoteControl call. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionCanvasListOpenRequest +public sealed class RemoteControlTransferResult { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// State of the runtime-managed remote-control singleton. + [JsonPropertyName("status")] + public RemoteControlStatus Status { get => field ??= new(); set; } + + /// Whether the rebinding actually happened. + [JsonPropertyName("transferred")] + public bool Transferred { get; set; } } -/// Canvas open parameters. +/// Parameters for atomically rebinding the remote-control singleton. [Experimental(Diagnostics.Experimental)] -internal sealed class CanvasOpenRequest +internal sealed class SessionsTransferRemoteControlRequest { - /// Provider-local canvas identifier. - [JsonPropertyName("canvasId")] - public string CanvasId { get; set; } = string.Empty; + /// When provided, the transfer is rejected unless the singleton currently points at this session id (compare-and-swap semantics to avoid clobbering newer state). + [JsonPropertyName("expectedFromSessionId")] + public string? ExpectedFromSessionId { get; set; } - /// Owning provider identifier. Optional when the canvasId is unique across providers; required to disambiguate when multiple providers register the same canvasId. - [JsonPropertyName("extensionId")] - public string? ExtensionId { get; set; } + /// Local session id to point remote control at. + [JsonPropertyName("toSessionId")] + public string ToSessionId { get; set; } = string.Empty; +} - /// Canvas open input. - [JsonPropertyName("input")] - public JsonElement? Input { get; set; } +/// Patch for the singleton's steering state. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionsSetRemoteControlSteeringRequest +{ + /// Target steering state. Today only `true` is actionable on the underlying exporter; `false` is reserved for future use. + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } +} - /// Caller-supplied stable instance identifier. - [JsonPropertyName("instanceId")] - public string InstanceId { get; set; } = string.Empty; +/// Outcome of a stopRemoteControl call. +[Experimental(Diagnostics.Experimental)] +public sealed class RemoteControlStopResult +{ + /// State of the runtime-managed remote-control singleton. + [JsonPropertyName("status")] + public RemoteControlStatus Status { get => field ??= new(); set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Whether the singleton was actually torn down by this call. + [JsonPropertyName("stopped")] + public bool Stopped { get; set; } } -/// Canvas close parameters. +/// RPC data type for SessionsStopRemoteControl operations. [Experimental(Diagnostics.Experimental)] -internal sealed class CanvasCloseRequest +internal sealed class SessionsStopRemoteControlRequest { - /// Open canvas instance identifier. - [JsonPropertyName("instanceId")] - public string InstanceId { get; set; } = string.Empty; + /// When provided, the stop is rejected unless the singleton currently points at this session id (compare-and-swap semantics). + [JsonPropertyName("expectedSessionId")] + public string? ExpectedSessionId { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// When true, the singleton is unconditionally torn down regardless of `expectedSessionId`. Use during shutdown or explicit `/remote off`. + [JsonPropertyName("force")] + public bool? Force { get; set; } } -/// Canvas action invocation result. +/// Handle for releasing the extension tool registration. [Experimental(Diagnostics.Experimental)] -public sealed class CanvasActionInvokeResult +internal sealed class RegisterExtensionToolsResult { - /// Provider-supplied action result. - [JsonPropertyName("result")] - public JsonElement? Result { get; set; } } -/// Canvas action invocation parameters. +/// Optional registration options. [Experimental(Diagnostics.Experimental)] -internal sealed class CanvasActionInvokeRequest +public sealed class SessionsRegisterExtensionToolsOnSessionOptions { - /// Action name to invoke. - [JsonPropertyName("actionName")] - public string ActionName { get; set; } = string.Empty; +} - /// Action input. - [JsonPropertyName("input")] - public JsonElement? Input { get; set; } +/// Params to attach an extension loader's tools to a session. +[Experimental(Diagnostics.Experimental)] +internal sealed class RegisterExtensionToolsParams +{ + /// Optional registration options. + [JsonPropertyName("options")] + public SessionsRegisterExtensionToolsOnSessionOptions? Options { get; set; } - /// Open canvas instance identifier. - [JsonPropertyName("instanceId")] - public string InstanceId { get; set; } = string.Empty; + /// Session to register extension tools on. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Target session identifier. +/// Params to attach or detach an in-process ExtensionController delegate. +[Experimental(Diagnostics.Experimental)] +internal sealed class ConfigureSessionExtensionsParams +{ + /// Session to attach the extension controller delegate to. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Machine-readable factory run failure. -/// Polymorphic base type discriminated by type. +/// Outcome of an agentRegistry.spawn call. +/// Polymorphic base type discriminated by kind. [Experimental(Diagnostics.Experimental)] [JsonPolymorphic( - TypeDiscriminatorPropertyName = "type", + TypeDiscriminatorPropertyName = "kind", UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(FactoryRunFailureFactoryLimitReached), "factory_limit_reached")] -[JsonDerivedType(typeof(FactoryRunFailureFactoryResumeDeclined), "factory_resume_declined")] -[JsonDerivedType(typeof(FactoryRunFailureFactoryDurableFailure), "factory_durable_failure")] -public partial class FactoryRunFailure +[JsonDerivedType(typeof(AgentRegistrySpawnResultSpawned), "spawned")] +[JsonDerivedType(typeof(AgentRegistrySpawnResultSpawnError), "spawn-error")] +[JsonDerivedType(typeof(AgentRegistrySpawnResultRegistryTimeout), "registry-timeout")] +[JsonDerivedType(typeof(AgentRegistrySpawnResultValidationError), "validation-error")] +public partial class AgentRegistrySpawnResult { /// The type discriminator. - [JsonPropertyName("type")] - public virtual string Type { get; set; } = string.Empty; + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; } -/// The factory_limit_reached variant of . +/// Full registry entry for the spawned child. Lets the controller call `handleLiveTargetSelected(entry)` directly without re-reading the registry (avoids a TOCTOU window). [Experimental(Diagnostics.Experimental)] -public partial class FactoryRunFailureFactoryLimitReached : FactoryRunFailure +public sealed class AgentRegistryLiveTargetEntry { - /// - [JsonIgnore] - public override string Type => "factory_limit_reached"; + /// Kind of attention required when status === "attention". Meaningful only when status === "attention". + [JsonPropertyName("attentionKind")] + public AgentRegistryLiveTargetEntryAttentionKind? AttentionKind { get; set; } - /// Resource ceiling that stopped the run. - [JsonPropertyName("kind")] - public required FactoryRunFailureKind Kind { get; set; } + /// Git branch of the session (when known). + [JsonPropertyName("branch")] + public string? Branch { get; set; } - /// Factory run identifier. - [JsonPropertyName("runId")] - public required string RunId { get; set; } + /// Copilot CLI version that wrote the entry. + [JsonPropertyName("copilotVersion")] + public string CopilotVersion { get; set; } = string.Empty; - /// Approved effective ceiling that was reached. - [JsonPropertyName("value")] - public required double Value { get; set; } -} + /// Working directory of the session (when known). + [JsonPropertyName("cwd")] + public string? Cwd { get; set; } -/// The factory_resume_declined variant of . -[Experimental(Diagnostics.Experimental)] -public partial class FactoryRunFailureFactoryResumeDeclined : FactoryRunFailure -{ - /// - [JsonIgnore] - public override string Type => "factory_resume_declined"; + /// Bind host for the entry's JSON-RPC server. + [JsonPropertyName("host")] + public string Host { get; set; } = string.Empty; - /// Human-readable reason the resume did not proceed. - [JsonPropertyName("reason")] - public required string Reason { get; set; } + /// Process kind tag for the registry entry. + [JsonPropertyName("kind")] + public AgentRegistryLiveTargetEntryKind Kind { get; set; } - /// Factory run identifier whose changed limits were declined. - [JsonPropertyName("runId")] - public required string RunId { get; set; } -} + /// Wall-clock milliseconds since the watcher last observed this entry (heartbeat freshness). + [JsonPropertyName("lastSeenMs")] + public long LastSeenMs { get; set; } -/// The factory_durable_failure variant of . -[Experimental(Diagnostics.Experimental)] -public partial class FactoryRunFailureFactoryDurableFailure : FactoryRunFailure -{ - /// - [JsonIgnore] - public override string Type => "factory_durable_failure"; + /// How the most recent turn ended (clean vs aborted). Lets the renderer distinguish done from done_cancelled. + [JsonPropertyName("lastTerminalEvent")] + public AgentRegistryLiveTargetEntryLastTerminalEvent? LastTerminalEvent { get; set; } - /// Stable failure code. - [JsonPropertyName("code")] - public required string Code { get; set; } + /// Model identifier currently selected for the session. + [JsonPropertyName("model")] + public string? Model { get; set; } - /// Execution-critical durable operation that failed. - [JsonPropertyName("operation")] - public required FactoryDurableOperation Operation { get; set; } + /// Operating-system pid of the process owning this entry. + [JsonPropertyName("pid")] + public long Pid { get; set; } - /// Factory run identifier. - [JsonPropertyName("runId")] - public required string RunId { get; set; } -} + /// TCP port the entry's JSON-RPC server is listening on. + [JsonPropertyName("port")] + public long Port { 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; } + /// Registry entry schema version (1 = ui-server, 2 = managed-server). + [JsonPropertyName("schemaVersion")] + public long SchemaVersion { get; set; } - /// Machine-readable failure details for an errored run. - [JsonPropertyName("failure")] - public FactoryRunFailure? Failure { get; set; } + /// Session ID of the foreground session for this entry. + [JsonPropertyName("sessionId")] + public string? SessionId { get; set; } - /// Reason for a halted or cancelled run. - [JsonPropertyName("reason")] - public string? Reason { get; set; } + /// Friendly session name (when set). + [JsonPropertyName("sessionName")] + public string? SessionName { get; set; } - /// Completed factory result. - [JsonPropertyName("result")] - public JsonElement? Result { get; set; } + /// ISO 8601 timestamp captured at registration. + [JsonPropertyName("startedAt")] + public string StartedAt { get; set; } = string.Empty; - /// Factory run identifier. - [JsonPropertyName("runId")] - public string RunId { get; set; } = string.Empty; + /// Coarse lifecycle status of the foreground session. + [JsonPropertyName("status")] + public AgentRegistryLiveTargetEntryStatus? Status { get; set; } - /// Partial journal and progress snapshot for a halted, cancelled, or errored run. - [JsonPropertyName("snapshot")] - public JsonElement? Snapshot { get; set; } + /// Monotonic per-publisher revision counter incremented on every status update. Lets watchers detect transient flips. + [JsonPropertyName("statusRevision")] + public long? StatusRevision { get; set; } - /// Current or terminal factory run status. - [JsonPropertyName("status")] - public FactoryRunStatus Status { get; set; } + /// Connection token (null when the target is unauthenticated). + [JsonInclude] + [JsonPropertyName("token")] + internal string? Token { get; set; } } -/// Wire-only per-invocation factory resource ceiling overrides. +/// Per-spawn log-capture outcome; populated from spawnLiveTarget. [Experimental(Diagnostics.Experimental)] -public sealed class FactoryRunLimits +public sealed class AgentRegistryLogCapture { - /// 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; } + /// Whether per-spawn log capture is on (false when env-disabled or open failed). + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } - /// Maximum number of factory subagents that may run concurrently. - [JsonPropertyName("maxConcurrentSubagents")] - public long? MaxConcurrentSubagents { get; set; } + /// Human-readable open failure message (only set when enabled === false AND the env-disable opt-out was NOT used). + [JsonPropertyName("openError")] + public string? OpenError { get; set; } - /// Maximum total number of factory subagents that may be admitted. - [JsonPropertyName("maxTotalSubagents")] - public long? MaxTotalSubagents { get; set; } + /// Categorized reason for log-open failure. + [JsonPropertyName("openErrorReason")] + public AgentRegistryLogCaptureOpenErrorReason? OpenErrorReason { 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; } + /// Absolute path to the per-spawn log file (only set when enabled). + [JsonPropertyName("path")] + public string? Path { get; set; } } -/// Options controlling factory invocation. +/// Managed-server child was spawned and registered successfully. +/// The spawned variant of . [Experimental(Diagnostics.Experimental)] -public sealed class RunOptions +public partial class AgentRegistrySpawnResultSpawned : AgentRegistrySpawnResult { - /// Per-invocation resource ceiling overrides. - [JsonPropertyName("limits")] - public FactoryRunLimits? Limits { get; set; } - - /// Run identifier whose journal and progress should seed this resumed run. - [JsonPropertyName("resumeFromRunId")] - public string? ResumeFromRunId { get; set; } -} + /// + [JsonIgnore] + public override string Kind => "spawned"; -/// Parameters for invoking a registered factory. -[Experimental(Diagnostics.Experimental)] -internal sealed class FactoryRunRequest -{ - /// Factory input value. - [JsonPropertyName("args")] - public JsonElement Args { get; set; } + /// Full registry entry for the spawned child. Lets the controller call `handleLiveTargetSelected(entry)` directly without re-reading the registry (avoids a TOCTOU window). + [JsonPropertyName("entry")] + public required AgentRegistryLiveTargetEntry Entry { get; set; } - /// Registered factory name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// If the delegate attempted to send the initial prompt and failed, the categorized error message. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("initialPromptError")] + public string? InitialPromptError { get; set; } - /// Factory invocation options. - [JsonPropertyName("options")] - public RunOptions? Options { get; set; } + /// Whether the delegate already sent the initial prompt. Always omitted in the current wiring: the controller sends the prompt post-attach via the standard LocalRpcSession.send path. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("initialPromptSent")] + public bool? InitialPromptSent { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Per-spawn log-capture outcome; populated from spawnLiveTarget. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("logCapture")] + public AgentRegistryLogCapture? LogCapture { get; set; } } -/// Resolved persisted factory identity and resumed run envelope. +/// `child_process.spawn` itself failed before the child entered the registry. +/// The spawn-error variant of . [Experimental(Diagnostics.Experimental)] -public sealed class FactoryResumeResult +public partial class AgentRegistrySpawnResultSpawnError : AgentRegistrySpawnResult { - /// Persisted factory name resolved for the resumed run. - [JsonPropertyName("factoryName")] - public string FactoryName { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "spawn-error"; - /// Terminal resumed run envelope. - [JsonPropertyName("run")] - public FactoryRunResult Run { get => field ??= new(); set; } + /// Underlying errno code (e.g. ENOENT, EACCES) when available. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("code")] + public string? Code { get; set; } + + /// Human-readable error message. + [JsonPropertyName("message")] + public required string Message { get; set; } } -/// Parameters for resuming a factory run from its persisted identity. +/// Spawn succeeded but the child did not publish a matching managed-server entry within the timeout. +/// The registry-timeout variant of . [Experimental(Diagnostics.Experimental)] -internal sealed class FactoryResumeRequest +public partial class AgentRegistrySpawnResultRegistryTimeout : AgentRegistrySpawnResult { - /// Optional per-invocation resource ceiling overrides. - [JsonPropertyName("limits")] - public FactoryRunLimits? Limits { get; set; } + /// + [JsonIgnore] + public override string Kind => "registry-timeout"; - /// Factory run identifier. - [JsonPropertyName("runId")] - public string RunId { get; set; } = string.Empty; + /// Process ID of the orphaned child (so the caller can offer 'kill the pid' guidance). + [JsonPropertyName("childPid")] + public required long ChildPid { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Per-spawn log-capture outcome; populated from spawnLiveTarget. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("logCapture")] + public AgentRegistryLogCapture? LogCapture { get; set; } } -/// Parameters for retrieving a factory run. +/// Synchronous pre-validation rejected the spawn request. +/// The validation-error variant of . [Experimental(Diagnostics.Experimental)] -internal sealed class FactoryGetRunRequest +public partial class AgentRegistrySpawnResultValidationError : AgentRegistrySpawnResult { - /// Factory run identifier. - [JsonPropertyName("runId")] - public string RunId { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "validation-error"; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Which parameter field was invalid. Omitted when the rejection is not field-specific. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("field")] + public AgentRegistrySpawnValidationErrorField? Field { get; set; } + + /// Human-readable explanation; safe to surface in the UI banner. Never logged to unrestricted telemetry. + [JsonPropertyName("message")] + public required string Message { get; set; } + + /// Categorized reason for the rejection. Low-cardinality enum so telemetry can aggregate by reason without leaking raw paths or agent/model names. + [JsonPropertyName("reason")] + public required AgentRegistrySpawnValidationErrorReason Reason { get; set; } } -/// Declared or approved factory resource ceilings. +/// Inputs to spawn a managed-server child via the controller's spawn delegate. [Experimental(Diagnostics.Experimental)] -public sealed class FactoryDeclaredLimits +internal sealed class AgentRegistrySpawnRequest { - /// Gets or sets the maxAiCredits value. - [JsonPropertyName("maxAiCredits")] - public double? MaxAiCredits { get; set; } - - /// Gets or sets the maxConcurrentSubagents value. - [JsonPropertyName("maxConcurrentSubagents")] - public long? MaxConcurrentSubagents { get; set; } + /// Custom or built-in agent name (e.g. 'explore'). When omitted, the child uses its own default. + [JsonPropertyName("agentName")] + public string? AgentName { get; set; } - /// Gets or sets the maxTotalSubagents value. - [JsonPropertyName("maxTotalSubagents")] - public long? MaxTotalSubagents { get; set; } + /// Working directory for the spawned child (must be an existing directory). + [JsonPropertyName("cwd")] + public string Cwd { get; set; } = string.Empty; - /// Gets or sets the timeoutSeconds value. - [JsonPropertyName("timeoutSeconds")] - public double? TimeoutSeconds { get; set; } -} + /// Optional first user message. Forwarded to the caller (the CLI's spawn wrapper sends it post-attach via the standard LocalRpcSession.send path). + [JsonPropertyName("initialPrompt")] + public string? InitialPrompt { get; set; } -/// Durable factory resource consumption. -[Experimental(Diagnostics.Experimental)] -public sealed class FactoryRunConsumed -{ - /// Gets or sets the activeMs value. - [JsonPropertyName("activeMs")] - public long ActiveMs { get; set; } + /// Model identifier to apply to the new session. + [JsonPropertyName("model")] + public string? Model { get; set; } - /// Gets or sets the nanoAiu value. - [JsonPropertyName("nanoAiu")] - public long NanoAiu { get; set; } + /// Friendly session name. Must satisfy validateSessionName: non-empty, no leading/trailing whitespace, <=100 chars, no control chars, no double quotes. + [JsonPropertyName("name")] + public string? Name { get; set; } - /// Gets or sets the subagents value. - [JsonPropertyName("subagents")] - public long Subagents { get; set; } + /// Permission posture for the new session. 'yolo' requires the controller-local session to currently be in allow-all mode. + [JsonPropertyName("permissionMode")] + public AgentRegistrySpawnPermissionMode? PermissionMode { get; set; } } -/// Current factory phase identity. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -public sealed class FactoryCurrentPhase +internal sealed class SessionSuspendRequest { - /// 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; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Prompt-safe terminal factory outcome. +/// Result of sending a user message. [Experimental(Diagnostics.Experimental)] -public sealed class FactoryRunTerminal +public sealed class SendResult { - /// Gets or sets the error value. - [JsonPropertyName("error")] - public string? Error { get; set; } - - /// Gets or sets the failure value. - [JsonPropertyName("failure")] - public FactoryRunFailure? Failure { 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; } + /// Unique identifier assigned to the message. + [JsonPropertyName("messageId")] + public string MessageId { get; set; } = string.Empty; } -/// Durable factory run summary with read-time live overlays. +/// Parameters for sending a user message to the session. [Experimental(Diagnostics.Experimental)] -public sealed class FactoryRunSummary +internal sealed class SendRequest { - /// Gets or sets the activeSegmentStartedAt value. - [JsonPropertyName("activeSegmentStartedAt")] - public long? ActiveSegmentStartedAt { get; set; } - - /// Gets or sets the approved value. - [JsonPropertyName("approved")] - public FactoryDeclaredLimits? Approved { get; set; } - - /// Gets or sets the completedAt value. - [JsonPropertyName("completedAt")] - public long? CompletedAt { get; set; } - - /// Gets or sets the consumed value. - [JsonPropertyName("consumed")] - public FactoryRunConsumed Consumed { get => field ??= new(); set; } - - /// Gets or sets the createdAt value. - [JsonPropertyName("createdAt")] - public long CreatedAt { get; set; } - - /// Gets or sets the currentPhase value. - [JsonPropertyName("currentPhase")] - public FactoryCurrentPhase? CurrentPhase { get; set; } - - /// Gets or sets the declaredLimits value. - [JsonPropertyName("declaredLimits")] - public FactoryDeclaredLimits DeclaredLimits { get => field ??= new(); set; } + /// The UI mode the agent was in when this message was sent. Defaults to the session's current mode. + [JsonPropertyName("agentMode")] + public SendAgentMode? AgentMode { get; set; } - /// Gets or sets the declaredPhaseCount value. - [JsonPropertyName("declaredPhaseCount")] - public long DeclaredPhaseCount { get; set; } + /// Optional attachments (files, directories, selections, blobs, GitHub references) to include with the message. + [JsonPropertyName("attachments")] + public IList? Attachments { get; set; } - /// Gets or sets the description value. - [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; + /// If false, this message will not trigger a Premium Request Unit charge. User messages default to billable. + [JsonPropertyName("billable")] + public bool? Billable { get; set; } - /// Gets or sets the factoryName value. - [JsonPropertyName("factoryName")] - public string FactoryName { get; set; } = string.Empty; + /// If provided, this is shown in the timeline instead of `prompt`. + [JsonPropertyName("displayPrompt")] + public string? DisplayPrompt { get; set; } - /// Gets or sets the liveAgentCount value. - [JsonPropertyName("liveAgentCount")] - public long LiveAgentCount { get; set; } + /// How to deliver the message. `enqueue` (default) appends to the message queue. `immediate` interjects during an in-progress turn. + [JsonPropertyName("mode")] + public SendMode? Mode { get; set; } - /// Gets or sets the observedAt value. - [JsonPropertyName("observedAt")] - public long ObservedAt { get; set; } + /// If true, adds the message to the front of the queue instead of the end. + [JsonPropertyName("prepend")] + public bool? Prepend { get; set; } - /// Gets or sets the revision value. - [JsonPropertyName("revision")] - public long Revision { get; set; } + /// The user message text. + [JsonPropertyName("prompt")] + public string Prompt { get; set; } = string.Empty; - /// Gets or sets the runId value. - [JsonPropertyName("runId")] - public string RunId { get; set; } = string.Empty; + /// 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. + [JsonPropertyName("requestHeaders")] + public IDictionary? RequestHeaders { get; set; } - /// Gets or sets the startedAt value. - [JsonPropertyName("startedAt")] - public long? StartedAt { get; set; } + /// 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. + [JsonPropertyName("requiredTool")] + public string? RequiredTool { get; set; } - /// Gets or sets the status value. - [JsonPropertyName("status")] - public FactoryRunStatus Status { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// Gets or sets the terminal value. - [JsonPropertyName("terminal")] - public FactoryRunTerminal? Terminal { get; set; } + /// 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; } - /// Gets or sets the totalSpawnedAgentCount value. - [JsonPropertyName("totalSpawnedAgentCount")] - public long TotalSpawnedAgentCount { get; set; } + /// W3C Trace Context traceparent header for distributed tracing of this agent turn. + [JsonPropertyName("traceparent")] + public string? Traceparent { get; set; } - /// Gets or sets the updatedAt value. - [JsonPropertyName("updatedAt")] - public long UpdatedAt { get; set; } -} + /// W3C Trace Context tracestate header for distributed tracing. + [JsonPropertyName("tracestate")] + public string? Tracestate { get; set; } -/// Factory runs in durable creation order. -[Experimental(Diagnostics.Experimental)] -public sealed class FactoryListRunsResult -{ - /// Gets or sets the runs value. - [JsonPropertyName("runs")] - public IList Runs { get => field ??= []; 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. 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; } } -/// Empty parameters for listing factory runs. +/// Result of sending zero or more user messages. [Experimental(Diagnostics.Experimental)] -internal sealed class FactoryListRunsRequest +public sealed class SendMessagesResult { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Unique identifiers assigned to the messages, one per provided message in order. Empty when no messages were provided. + [JsonPropertyName("messageIds")] + public IList MessageIds { get => field ??= []; set; } } -/// Prompt-safe durable identity and live status for a direct factory agent. +/// A single user message to append to the session as part of a `session.sendMessages` turn. [Experimental(Diagnostics.Experimental)] -public sealed class FactoryAgentSummary +public sealed class SendMessageItem { - /// Gets or sets the activeMs value. - [JsonPropertyName("activeMs")] - public long ActiveMs { get; set; } - - /// Gets or sets the activity value. - [JsonPropertyName("activity")] - public string? Activity { get; set; } - - /// Gets or sets the agentId value. - [JsonPropertyName("agentId")] - public string AgentId { get; set; } = string.Empty; - - /// 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; } + /// Optional attachments (files, directories, selections, blobs, GitHub references) to include with this message. + [JsonPropertyName("attachments")] + public IList? Attachments { get; set; } - /// Gets or sets the resolvedModel value. - [JsonPropertyName("resolvedModel")] - public string? ResolvedModel { get; set; } + /// If false, this message will not trigger a Premium Request Unit charge. User messages default to billable. + [JsonInclude] + [JsonPropertyName("billable")] + internal bool? Billable { get; set; } - /// Gets or sets the runId value. - [JsonPropertyName("runId")] - public string RunId { get; set; } = string.Empty; + /// If provided, this is shown in the timeline instead of `prompt`. + [JsonPropertyName("displayPrompt")] + public string? DisplayPrompt { get; set; } - /// Gets or sets the startedAt value. - [JsonPropertyName("startedAt")] - public long? StartedAt { get; set; } + /// The user message text. + [JsonPropertyName("prompt")] + public string Prompt { get; set; } = string.Empty; - /// Gets or sets the status value. - [JsonPropertyName("status")] - public string Status { get; set; } = string.Empty; + /// 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. + [JsonPropertyName("requiredTool")] + public string? RequiredTool { get; set; } - /// Gets or sets the toolCallId value. - [JsonPropertyName("toolCallId")] - public string ToolCallId { get; set; } = string.Empty; + /// 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; } } -/// Durable lifecycle and timing for one factory phase. +/// 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(Diagnostics.Experimental)] -public sealed class FactoryPhaseObservation +internal sealed class SendMessagesRequest { - /// Gets or sets the accumulatedActiveMs value. - [JsonPropertyName("accumulatedActiveMs")] - public long AccumulatedActiveMs { get; set; } + /// The UI mode the agent was in when these messages were sent. Defaults to the session's current mode. + [JsonPropertyName("agentMode")] + public SendAgentMode? AgentMode { get; set; } - /// Gets or sets the completedAt value. - [JsonPropertyName("completedAt")] - public long? CompletedAt { get; set; } + /// 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. + [JsonPropertyName("messages")] + public IList Messages { get => field ??= []; set; } - /// Gets or sets the currentActiveMs value. - [JsonPropertyName("currentActiveMs")] - public long CurrentActiveMs { get; set; } + /// How to deliver the messages. `enqueue` (default) appends to the message queue. `immediate` interjects during an in-progress turn. + [JsonPropertyName("mode")] + public SendMode? Mode { get; set; } - /// Gets or sets the detail value. - [JsonPropertyName("detail")] - public string? Detail { get; set; } + /// If true, adds the messages to the front of the queue instead of the end. + [JsonPropertyName("prepend")] + public bool? Prepend { get; set; } - /// Gets or sets the entryCount value. - [JsonPropertyName("entryCount")] - public long EntryCount { get; set; } + /// 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. + [JsonPropertyName("requestHeaders")] + public IDictionary? RequestHeaders { get; set; } - /// 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; - /// Gets or sets the lastEnteredRunAttempt value. - [JsonPropertyName("lastEnteredRunAttempt")] - public long LastEnteredRunAttempt { get; set; } + /// W3C Trace Context traceparent header for distributed tracing of this agent turn. + [JsonPropertyName("traceparent")] + public string? Traceparent { get; set; } - /// Gets or sets the liveAgentCount value. - [JsonPropertyName("liveAgentCount")] - public long LiveAgentCount { get; set; } + /// W3C Trace Context tracestate header for distributed tracing. + [JsonPropertyName("tracestate")] + public string? Tracestate { get; set; } - /// Gets or sets the ordinal value. - [JsonPropertyName("ordinal")] - public long? Ordinal { 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. 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; } +} - /// Gets or sets the startedAt value. - [JsonPropertyName("startedAt")] - public long? StartedAt { 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; } - /// Gets or sets the status value. - [JsonPropertyName("status")] - public FactoryPhaseStatus Status { get; set; } + /// Notification text to deliver to the model. + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; - /// Gets or sets the title value. - [JsonPropertyName("title")] - public string Title { get; set; } = string.Empty; + /// Internal delivery options, including passive policy. + [JsonPropertyName("options")] + public JsonElement? Options { get; set; } - /// Gets or sets the totalAgentCount value. - [JsonPropertyName("totalAgentCount")] - public long TotalAgentCount { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// One durable factory progress record. +/// Result of aborting the current turn. [Experimental(Diagnostics.Experimental)] -public sealed class FactoryProgressLine +public sealed class AbortResult { - /// Resume attempt that emitted this record. - [JsonPropertyName("attempt")] - public long Attempt { get; set; } - - /// Progress record kind. - [JsonPropertyName("kind")] - public FactoryLogLineKind Kind { 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; } + /// Error message if the abort failed. + [JsonPropertyName("error")] + public string? Error { get; set; } - /// Prompt-safe progress text. - [JsonPropertyName("text")] - public string Text { get; set; } = string.Empty; + /// Whether the abort completed successfully. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// A bidirectional page of factory progress. +/// Parameters for aborting the current turn. [Experimental(Diagnostics.Experimental)] -public sealed class FactoryProgressPage +internal sealed class AbortRequest { - /// Gets or sets the hasMoreNewer value. - [JsonPropertyName("hasMoreNewer")] - public bool HasMoreNewer { 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; } + /// Finite reason code describing why the current turn was aborted. + [JsonPropertyName("reason")] + public AbortReason? Reason { get; set; } - /// Run revision reflected by this page. - [JsonPropertyName("revision")] - public long Revision { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Full factory run observability detail. +/// Result of interrupting the main agent turn. [Experimental(Diagnostics.Experimental)] -public sealed class FactoryRunDetail +public sealed class InterruptMainTurnResult { - /// Gets or sets the activeSegmentStartedAt value. - [JsonPropertyName("activeSegmentStartedAt")] - public long? ActiveSegmentStartedAt { get; set; } - - /// Gets or sets the agents value. - [JsonPropertyName("agents")] - public IList Agents { get => field ??= []; set; } - - /// Gets or sets the approved value. - [JsonPropertyName("approved")] - public FactoryDeclaredLimits? Approved { get; set; } - - /// Gets or sets the completedAt value. - [JsonPropertyName("completedAt")] - public long? CompletedAt { get; set; } - - /// Gets or sets the consumed value. - [JsonPropertyName("consumed")] - public FactoryRunConsumed Consumed { get => field ??= new(); set; } - - /// Gets or sets the createdAt value. - [JsonPropertyName("createdAt")] - public long CreatedAt { get; set; } - - /// Gets or sets the currentPhase value. - [JsonPropertyName("currentPhase")] - public FactoryCurrentPhase? CurrentPhase { get; set; } - - /// Gets or sets the declaredLimits value. - [JsonPropertyName("declaredLimits")] - public FactoryDeclaredLimits DeclaredLimits { get => field ??= new(); set; } - - /// Gets or sets the declaredPhaseCount value. - [JsonPropertyName("declaredPhaseCount")] - public long DeclaredPhaseCount { get; set; } - - /// Gets or sets the description value. - [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; - - /// Gets or sets the factoryName value. - [JsonPropertyName("factoryName")] - public string FactoryName { get; set; } = string.Empty; - - /// Gets or sets the liveAgentCount value. - [JsonPropertyName("liveAgentCount")] - public long LiveAgentCount { get; set; } - - /// Gets or sets the observedAt value. - [JsonPropertyName("observedAt")] - public long ObservedAt { get; set; } - - /// Gets or sets the phases value. - [JsonPropertyName("phases")] - public IList Phases { get => field ??= []; set; } - - /// Gets or sets the progress value. - [JsonPropertyName("progress")] - public FactoryProgressPage Progress { get => field ??= new(); 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; - - /// 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; } + /// 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 paging factory progress. +/// Parameters for interrupting the main agent turn. [Experimental(Diagnostics.Experimental)] -internal sealed class FactoryGetRunProgressRequest +internal sealed class InterruptMainTurnRequest { - /// 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; + /// 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; } -/// Parameters for cancelling a factory run. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class FactoryCancelRequest +internal sealed class SessionCancelAllBackgroundAgentsRequest { - /// Factory run identifier. - [JsonPropertyName("runId")] - public string RunId { get; set; } = string.Empty; - /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Acknowledgement that a factory request was accepted. +/// Parameters for shutting down the session. [Experimental(Diagnostics.Experimental)] -public sealed class FactoryAckResult +internal sealed class ShutdownRequest { + /// Optional human-readable reason. Typically the message of the error that triggered shutdown when type is 'error'. + [JsonPropertyName("reason")] + public string? Reason { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + /// Why the session is being shut down. Defaults to "routine" when omitted. + [JsonPropertyName("type")] + public ShutdownType? Type { get; set; } } -/// One ordered factory progress line. +/// Identifier of the session event that was emitted for the log message. [Experimental(Diagnostics.Experimental)] -public sealed class FactoryLogLine +public sealed class LogResult { - /// 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; + /// The unique identifier of the emitted session event. + [JsonPropertyName("eventId")] + public Guid EventId { get; set; } } -/// Parameters for recording factory progress. +/// Message text, optional severity level, persistence flag, optional follow-up URL, and optional tip. [Experimental(Diagnostics.Experimental)] -internal sealed class FactoryLogRequest +internal sealed class LogRequest { - /// Opaque token identifying the current factory execution attempt. - [JsonPropertyName("executionToken")] - public string ExecutionToken { get; set; } = string.Empty; + /// When true, the message is transient and not persisted to the session event log on disk. + [JsonPropertyName("ephemeral")] + public bool? Ephemeral { get; set; } - /// Ordered progress lines to append. - [JsonPropertyName("lines")] - public IList Lines { get => field ??= []; set; } + /// Log severity level. Determines how the message is displayed in the timeline. Defaults to "info". + [JsonPropertyName("level")] + public SessionLogLevel? Level { get; set; } - /// Factory run identifier. - [JsonPropertyName("runId")] - public string RunId { get; set; } = string.Empty; + /// Human-readable message. + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; -} -/// Result of one factory-scoped subagent call. -[Experimental(Diagnostics.Experimental)] -public sealed class FactoryAgentResult -{ - /// Agent result, omitted when the agent produced no result. - [JsonPropertyName("result")] - public JsonElement? Result { get; set; } + /// Optional actionable tip displayed alongside the message. Only honored on `level: "info"`. + [JsonPropertyName("tip")] + public string? Tip { get; set; } + + /// Domain category for this log entry (e.g., "mcp", "subscription", "policy", "model"). Maps to `infoType`/`warningType`/`errorType` on the emitted event. Defaults to "notification". + [JsonPropertyName("type")] + public string? Type { get; set; } + + /// Optional URL the user can open in their browser for more details. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] + [JsonPropertyName("url")] + public string? Url { get; set; } } -/// Options for one factory-scoped subagent call. +/// Managed sandbox enforcement state for a session. [Experimental(Diagnostics.Experimental)] -public sealed class FactoryAgentOptions +public sealed class SandboxEnforcementStatus { - /// Optional label distinguishing otherwise identical memoized agent calls. - [JsonPropertyName("label")] - public string? Label { get; set; } + /// Whether an enforcement failure has permanently blocked the session. + [JsonPropertyName("blocked")] + public bool Blocked { get; set; } - /// Optional model identifier for the subagent. - [JsonPropertyName("model")] - public string? Model { get; set; } + /// The first sandbox enforcement failure that blocked the session. + [JsonPropertyName("reason")] + public string? Reason { get; set; } - /// Optional JSON Schema for structured agent output. - [JsonPropertyName("schema")] - public JsonElement? Schema { get; set; } + /// Whether the effective managed policy requires an available sandbox backend. + [JsonPropertyName("required")] + public bool Required { get; set; } } -/// Parameters for one factory-scoped subagent call. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class FactoryAgentRequest +internal sealed class SessionSandboxGetEnforcementStatusRequest { - /// 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; } -/// Result of reading a factory journal entry. +/// Authentication status and account metadata for the session. [Experimental(Diagnostics.Experimental)] -public sealed class FactoryJournalGetResult +public sealed class SessionAuthStatus { - /// Whether the journal contained the requested key. - [JsonPropertyName("hit")] - public bool Hit { get; set; } + /// Authentication type. + [JsonPropertyName("authType")] + public AuthInfoType? AuthType { get; set; } - /// Cached JSON result. The hit field distinguishes a cached JSON null from a miss. - [JsonPropertyName("resultJson")] - public JsonElement? ResultJson { get; set; } -} + /// Copilot plan tier (e.g., individual_pro, business). + [JsonPropertyName("copilotPlan")] + public string? CopilotPlan { get; set; } -/// Parameters for reading a factory journal entry. -[Experimental(Diagnostics.Experimental)] -internal sealed class FactoryJournalGetRequest -{ - /// Opaque token identifying the current factory execution attempt. - [JsonPropertyName("executionToken")] - public string ExecutionToken { get; set; } = string.Empty; + /// Authentication host URL. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] + [JsonPropertyName("host")] + public string? Host { get; set; } - /// Namespaced journal key. - [JsonPropertyName("key")] - public string Key { get; set; } = string.Empty; + /// Whether the session has resolved authentication. + [JsonPropertyName("isAuthenticated")] + public bool IsAuthenticated { get; set; } - /// Factory run identifier. - [JsonPropertyName("runId")] - public string RunId { get; set; } = string.Empty; + /// Authenticated login/username, if available. + [JsonPropertyName("login")] + public string? Login { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Human-readable authentication status description. + [JsonPropertyName("statusMessage")] + public string? StatusMessage { get; set; } } -/// Parameters for storing a factory journal entry. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class FactoryJournalPutRequest +internal sealed class SessionGitHubAuthGetStatusRequest { - /// 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; - - /// JSON result to memoize. - [JsonPropertyName("resultJson")] - public JsonElement ResultJson { 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 currently selected model, reasoning effort, and context tier for the session. The context tier reflects `Session.getContextTier()`, restored from the session journal on resume. +/// Indicates whether the credential update succeeded. [Experimental(Diagnostics.Experimental)] -public sealed class CurrentModel +public sealed class SessionSetCredentialsResult { - /// 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; } + /// 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). + [JsonPropertyName("copilotUserResolved")] + public bool? CopilotUserResolved { 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; } + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// Identifies the target session. +/// Authentication credentials accepted by session.gitHubAuth.setCredentials. Session-owned token-provider identities cannot be installed through this method. +/// Polymorphic base type discriminated by type. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionModelGetCurrentRequest +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "type", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(SettableAuthInfoHmac), "hmac")] +[JsonDerivedType(typeof(SettableAuthInfoEnv), "env")] +[JsonDerivedType(typeof(SettableAuthInfoToken), "token")] +[JsonDerivedType(typeof(SettableAuthInfoCopilotApiToken), "copilot-api-token")] +[JsonDerivedType(typeof(SettableAuthInfoUser), "user")] +[JsonDerivedType(typeof(SettableAuthInfoGhCli), "gh-cli")] +[JsonDerivedType(typeof(SettableAuthInfoApiKey), "api-key")] +public partial class SettableAuthInfo { - /// 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; } -/// The model identifier active on the session after the switch. + +/// Authentication-info input variant for GitHub-internal HMAC auth, carrying the public GitHub host and HMAC secret. +/// The hmac variant of . [Experimental(Diagnostics.Experimental)] -public sealed class ModelSwitchToResult +public partial class SettableAuthInfoHmac : SettableAuthInfo { - /// 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; } + /// + [JsonIgnore] + public override string Type => "hmac"; - /// Currently active model identifier after the switch. - [JsonPropertyName("modelId")] - public string? ModelId { get; set; } + /// 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. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("copilotUser")] + public CopilotUserResponse? CopilotUser { get; set; } + + /// HMAC secret used to sign requests. + [JsonPropertyName("hmac")] + public required string Hmac { get; set; } + + /// Authentication host. HMAC auth always targets the public GitHub host. + [JsonPropertyName("host")] + public required string Host { get; set; } } -/// Vision-specific limits. +/// Authentication-info input variant for a token sourced from an environment variable, with host, optional login, token, and env var name. +/// The env variant of . [Experimental(Diagnostics.Experimental)] -public sealed class ModelCapabilitiesOverrideLimitsVision +public partial class SettableAuthInfoEnv : SettableAuthInfo { - /// Maximum image size in bytes. - [JsonPropertyName("max_prompt_image_size")] - public long? MaxPromptImageSize { get; set; } + /// + [JsonIgnore] + public override string Type => "env"; - /// Maximum number of images per prompt. - [JsonPropertyName("max_prompt_images")] - public long? MaxPromptImages { get; set; } + /// 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. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("copilotUser")] + public CopilotUserResponse? CopilotUser { get; set; } - /// MIME types the model accepts. - [JsonPropertyName("supported_media_types")] - public IList? SupportedMediaTypes { get; set; } + /// Name of the environment variable the token was sourced from. + [JsonPropertyName("envVar")] + public required string EnvVar { get; set; } + + /// Authentication host (e.g. https://github.com or a GHES host). + [JsonPropertyName("host")] + public required string Host { get; set; } + + /// User login associated with the token. Undefined for server-to-server tokens (those starting with `ghs_`). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("login")] + public string? Login { get; set; } + + /// The token value itself. Treat as a secret. + [JsonPropertyName("token")] + public required string Token { get; set; } } -/// Token limits for prompts, outputs, and context window. +/// Token authentication accepted by session.gitHubAuth.setCredentials. +/// The token variant of . [Experimental(Diagnostics.Experimental)] -public sealed class ModelCapabilitiesOverrideLimits +public partial class SettableAuthInfoToken : SettableAuthInfo { - /// Maximum total context window size in tokens. - [JsonPropertyName("max_context_window_tokens")] - public long? MaxContextWindowTokens { get; set; } + /// + [JsonIgnore] + public override string Type => "token"; - /// Maximum number of output/completion tokens. - [JsonPropertyName("max_output_tokens")] - public long? MaxOutputTokens { get; set; } + /// 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. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("copilotUser")] + public CopilotUserResponse? CopilotUser { get; set; } - /// Maximum number of prompt/input tokens. - [JsonPropertyName("max_prompt_tokens")] - public long? MaxPromptTokens { get; set; } + /// Authentication host. + [JsonPropertyName("host")] + public required string Host { get; set; } - /// Vision-specific limits. - [JsonPropertyName("vision")] - public ModelCapabilitiesOverrideLimitsVision? Vision { get; set; } + /// The token value itself. Treat as a secret. + [JsonPropertyName("token")] + public required string Token { get; set; } } -/// Feature flags indicating what the model supports. +/// Authentication-info variant for direct Copilot API token auth sourced from environment variables, with public GitHub host. +/// The copilot-api-token variant of . [Experimental(Diagnostics.Experimental)] -public sealed class ModelCapabilitiesOverrideSupports +public partial class SettableAuthInfoCopilotApiToken : SettableAuthInfo { - /// 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; } + /// + [JsonIgnore] + public override string Type => "copilot-api-token"; - /// Whether this model supports reasoning effort configuration. - [JsonPropertyName("reasoningEffort")] - public bool? ReasoningEffort { get; set; } + /// 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. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("copilotUser")] + public CopilotUserResponse? CopilotUser { get; set; } - /// Whether this model supports vision/image input. - [JsonPropertyName("vision")] - public bool? Vision { get; set; } + /// Authentication host (always the public GitHub host). + [JsonPropertyName("host")] + public required string Host { get; set; } } -/// Optional capability overrides (vision, tool_calls, reasoning, etc.). +/// Authentication-info variant for OAuth user auth, with host and login; the token remains in the runtime secret store. +/// The user variant of . [Experimental(Diagnostics.Experimental)] -public sealed class ModelCapabilitiesOverride +public partial class SettableAuthInfoUser : SettableAuthInfo { - /// Token limits for prompts, outputs, and context window. - [JsonPropertyName("limits")] - public ModelCapabilitiesOverrideLimits? Limits { get; set; } + /// + [JsonIgnore] + public override string Type => "user"; - /// Feature flags indicating what the model supports. - [JsonPropertyName("supports")] - public ModelCapabilitiesOverrideSupports? Supports { get; set; } + /// 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. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("copilotUser")] + public CopilotUserResponse? CopilotUser { get; set; } + + /// Authentication host. + [JsonPropertyName("host")] + public required string Host { get; set; } + + /// OAuth user login. + [JsonPropertyName("login")] + public required string Login { get; set; } } -/// Target model identifier and optional reasoning effort, summary, capability overrides, and context tier. +/// Authentication-info input variant for GitHub CLI credentials, carrying host, login, and the `gh auth token` value. +/// The gh-cli variant of . [Experimental(Diagnostics.Experimental)] -internal sealed class ModelSwitchToRequest +public partial class SettableAuthInfoGhCli : SettableAuthInfo { - /// 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; } - - /// 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; } + /// + [JsonIgnore] + public override string Type => "gh-cli"; - /// Override individual model capabilities resolved by the runtime. - [JsonPropertyName("modelCapabilities")] - public ModelCapabilitiesOverride? ModelCapabilities { 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; - - /// 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; } + /// 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. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("copilotUser")] + public CopilotUserResponse? CopilotUser { get; set; } - /// Reasoning summary mode to request for supported model clients. - [JsonPropertyName("reasoningSummary")] - public ReasoningSummary? ReasoningSummary { get; set; } + /// Authentication host. + [JsonPropertyName("host")] + public required string Host { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// User login as reported by `gh auth status`. + [JsonPropertyName("login")] + public required string Login { get; set; } - /// Output verbosity level to request for supported models. - [JsonPropertyName("verbosity")] - public Verbosity? Verbosity { get; set; } + /// The token returned by `gh auth token`. Treat as a secret. + [JsonPropertyName("token")] + public required string Token { 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. +/// Authentication-info input variant for API-key authentication to a non-GitHub LLM provider, carrying the secret `apiKey` and host. +/// The api-key variant of . [Experimental(Diagnostics.Experimental)] -public sealed class ModelSetReasoningEffortResult +public partial class SettableAuthInfoApiKey : SettableAuthInfo { - /// Reasoning effort level recorded on the session after the update. - [JsonPropertyName("reasoningEffort")] - public string ReasoningEffort { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Type => "api-key"; + + /// The API key. Treat as a secret. + [JsonPropertyName("apiKey")] + public required string ApiKey { get; set; } + + /// 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. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("copilotUser")] + public CopilotUserResponse? CopilotUser { get; set; } + + /// Authentication host. + [JsonPropertyName("host")] + public required string Host { get; set; } } -/// Reasoning effort level to apply to the currently selected model. +/// New auth credentials to install on the session. Omit to leave credentials unchanged. [Experimental(Diagnostics.Experimental)] -internal sealed class ModelSetReasoningEffortRequest +internal sealed class SessionSetCredentialsParams { - /// 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; + /// 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. + [JsonPropertyName("credentials")] + public SettableAuthInfo? Credentials { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Cost-category metadata for a CAPI model. +/// Credential-free authentication identity safe to expose to hosts and user interfaces. [Experimental(Diagnostics.Experimental)] -public sealed class SessionModelPriceCategory +public sealed class AuthIdentity { - /// Gets or sets the id value. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// Snapshot of the authenticated user's Copilot subscription info, if known. + [JsonPropertyName("copilotUser")] + public CopilotUserResponse? CopilotUser { get; set; } - /// Gets or sets the priceCategory value. - [JsonPropertyName("priceCategory")] - public ModelPickerPriceCategory PriceCategory { get; set; } -} + /// Name of the environment variable that supplied the credential, when applicable. + [JsonPropertyName("envVar")] + public string? EnvVar { 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; } + /// Authentication host. + [JsonPropertyName("host")] + public string Host { get; set; } = string.Empty; - /// 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; } + /// Authenticated login, when available. + [JsonPropertyName("login")] + public string? Login { get; set; } - /// Per-quota snapshots returned alongside the model list, keyed by quota type. - [JsonPropertyName("quotaSnapshots")] - public IDictionary? QuotaSnapshots { get; set; } -} + /// Opaque SDK GitHub credential registration backing this identity. Routing metadata only; never a credential. + [JsonPropertyName("registrationId")] + public string? RegistrationId { get; set; } -/// RPC data type for SessionModelList operations. -[Experimental(Diagnostics.Experimental)] -public sealed class SessionModelListRequest -{ - /// If true, bypasses the per-session model list cache and re-fetches from CAPI. - [JsonPropertyName("skipCache")] - public bool? SkipCache { get; set; } + /// Authentication type. + [JsonPropertyName("type")] + public AuthInfoType Type { get; set; } } -/// RPC data type for SessionModelListRequestWithSession operations. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionModelListRequestWithSession +internal sealed class SessionGitHubAuthGetCurrentAuthInfoRequest { /// 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; } } /// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionModeGetRequest +internal sealed class SessionGitHubAuthGetAllAuthAvailableRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Agent interaction mode to apply to the session. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class ModeSetRequest +internal sealed class SessionGitHubAuthRefreshCopilotUserRequest { - /// The session mode the agent is operating in. - [JsonPropertyName("mode")] - public SessionMode Mode { get; set; } - /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// The session's friendly name, or null when not yet set. +/// Internal GitHub login parameters. [Experimental(Diagnostics.Experimental)] -public sealed class NameGetResult +internal sealed class SessionAuthLoginRequest { - /// The session name (user-set or auto-generated), or null if not yet set. - [JsonPropertyName("name")] - public string? Name { get; set; } -} + /// GitHub host URL. + [JsonPropertyName("host")] + public string Host { get; set; } = string.Empty; + + /// GitHub login. + [JsonPropertyName("login")] + public string Login { get; set; } = string.Empty; + + /// Whether to persist the token after login. + [JsonPropertyName("persist")] + public bool? Persist { get; set; } -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionNameGetRequest -{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; + + /// GitHub authentication token. + [JsonPropertyName("token")] + public string Token { get; set; } = string.Empty; } -/// New friendly name to apply to the session. +/// Parameters for switching the session's active authentication. [Experimental(Diagnostics.Experimental)] -internal sealed class NameSetRequest +internal sealed class SessionAuthSwitchRequest { - /// 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; + /// Authentication information to activate. + [JsonPropertyName("authInfo")] + public AuthInfo AuthInfo { get => field ??= new(); set; } /// 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. -[Experimental(Diagnostics.Experimental)] -public sealed class 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. - [JsonPropertyName("applied")] - public bool Applied { get; set; } + /// Optional token paired with the authentication information. + [JsonPropertyName("token")] + public string? Token { get; set; } } -/// Auto-generated session summary to apply as the session's name when no user-set name exists. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class NameSetAutoRequest +internal sealed class SessionGitHubAuthLogoutRequest { /// 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; } -/// Existence, contents, and resolved path of the session plan file. +/// Parameters identifying a GitHub authentication to log out. [Experimental(Diagnostics.Experimental)] -public sealed class PlanReadResult +internal sealed class SessionAuthLogoutUserRequest { - /// The content of the plan file, or null if it does not exist. - [JsonPropertyName("content")] - public string? Content { get; set; } - - /// Whether the plan file exists in the workspace. - [JsonPropertyName("exists")] - public bool Exists { get; set; } - - /// Absolute file path of the plan file, or null if workspace is not enabled. - [JsonPropertyName("path")] - public string? Path { get; set; } -} + /// Authentication information to log out. + [JsonPropertyName("authInfo")] + public AuthInfo AuthInfo { get => field ??= new(); set; } -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionPlanReadRequest -{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Replacement contents to write to the session plan file. +/// Validation error from an authentication attempt. [Experimental(Diagnostics.Experimental)] -internal sealed class PlanUpdateRequest +public sealed class AuthValidationError { - /// The new content for the plan file. - [JsonPropertyName("content")] - public string Content { get; set; } = string.Empty; + /// Optional message returned by GitHub. + [JsonPropertyName("githubMessage")] + public string? GitHubMessage { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Authentication validation error message. + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; } /// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionPlanDeleteRequest +internal sealed class SessionGitHubAuthLastAuthErrorsRequest { /// 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. +/// A file included in the redacted debug bundle. [Experimental(Diagnostics.Experimental)] -public sealed class PlanSqlTodosRow +public sealed class DebugCollectLogsCollectedEntry { - /// Todo description. - [JsonPropertyName("description")] - public string? Description { get; set; } - - /// Todo identifier. - [JsonPropertyName("id")] - public string? Id { get; set; } + /// Relative path of the file in the staged bundle/archive. + [JsonPropertyName("bundlePath")] + public string BundlePath { get; set; } = string.Empty; - /// Todo status. - [JsonPropertyName("status")] - public string? Status { get; set; } + /// Redacted output size in bytes. + [JsonPropertyName("sizeBytes")] + public long SizeBytes { get; set; } - /// Todo title. - [JsonPropertyName("title")] - public string? Title { get; set; } + /// Source category for this entry. + [JsonPropertyName("source")] + public DebugCollectLogsSource Source { get; set; } } -/// Todo rows read from the session SQL database. Empty when no session database is available. +/// An optional debug bundle entry that could not be included. [Experimental(Diagnostics.Experimental)] -public sealed class PlanReadSqlTodosResult +public sealed class DebugCollectLogsSkippedEntry { - /// Rows from the session SQL todos table, ordered by creation time and id. - [JsonPropertyName("rows")] - public IList Rows { get => field ??= []; set; } -} + /// Relative path requested for this bundle entry. + [JsonPropertyName("bundlePath")] + public string BundlePath { get; set; } = string.Empty; -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionPlanReadSqlTodosRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Server-local source path that could not be read. + [JsonPropertyName("path")] + public string? Path { get; set; } + + /// Reason the entry was skipped. + [JsonPropertyName("reason")] + public string Reason { get; set; } = string.Empty; } -/// A single dependency edge read from the session SQL `todo_deps` table, indicating that one todo must complete before another. +/// Result of collecting a redacted debug bundle. [Experimental(Diagnostics.Experimental)] -public sealed class PlanSqlTodoDependency +public sealed class DebugCollectLogsResult { - /// ID of the todo it depends on. - [JsonPropertyName("dependsOn")] - public string DependsOn { get; set; } = string.Empty; + /// Files included in the redacted bundle. + [JsonPropertyName("entries")] + public IList Entries { get => field ??= []; set; } - /// ID of the todo that has the dependency. - [JsonPropertyName("todoId")] - public string TodoId { get; set; } = string.Empty; -} + /// Destination kind that was written. + [JsonPropertyName("kind")] + public DebugCollectLogsResultKind Kind { get; set; } -/// 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; } + /// 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. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; - /// 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; } + /// Optional files or directories that could not be included. + [JsonPropertyName("skippedEntries")] + public IList? SkippedEntries { get; set; } } -/// Identifies the target session. +/// A caller-provided server-local file or directory to include in the debug bundle. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionPlanReadSqlTodosWithDependenciesRequest +public sealed class DebugCollectLogsEntry { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Relative path to use inside the staged bundle/archive. + [JsonPropertyName("bundlePath")] + public string BundlePath { get; set; } = string.Empty; -/// RPC data type for WorkspacesGetWorkspaceResultWorkspace operations. -public sealed class WorkspacesGetWorkspaceResultWorkspace -{ - /// Gets or sets the branch value. - [JsonPropertyName("branch")] - public string? Branch { get; set; } + /// Kind of source path to include. + [JsonPropertyName("kind")] + public DebugCollectLogsEntryKind Kind { get; set; } - /// Gets or sets the chronicle_sync_dismissed value. - [JsonPropertyName("chronicle_sync_dismissed")] - public bool? ChronicleSyncDismissed { get; set; } + /// Server-local source path to read. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; - /// Gets or sets the client_name value. - [JsonPropertyName("client_name")] - public string? ClientName { get; set; } + /// How text content from this entry should be redacted. Defaults to plain-text. + [JsonPropertyName("redaction")] + public DebugCollectLogsRedaction? Redaction { get; set; } - /// Gets or sets the created_at value. - [JsonPropertyName("created_at")] - public DateTimeOffset? CreatedAt { get; set; } + /// When true, collection fails if this entry cannot be read. Defaults to false, which records the entry in `skippedEntries`. + [JsonPropertyName("required")] + public bool? Required { get; set; } +} - /// Gets or sets the cwd value. - [JsonPropertyName("cwd")] - public string? Cwd { get; set; } +/// Destination for the redacted debug bundle. +/// Polymorphic base type discriminated by kind. +[Experimental(Diagnostics.Experimental)] +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(DebugCollectLogsDestinationArchive), "archive")] +[JsonDerivedType(typeof(DebugCollectLogsDestinationDirectory), "directory")] +public partial class DebugCollectLogsDestination +{ + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} - /// Gets or sets the git_root value. - [JsonPropertyName("git_root")] - public string? GitRoot { get; set; } - /// Allowed values for the `WorkspacesWorkspaceDetailsHostType` enumeration. - [JsonPropertyName("host_type")] - public WorkspacesWorkspaceDetailsHostType? HostType { get; set; } +/// The archive variant of . +[Experimental(Diagnostics.Experimental)] +public partial class DebugCollectLogsDestinationArchive : DebugCollectLogsDestination +{ + /// + [JsonIgnore] + public override string Kind => "archive"; - /// 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; + /// When true, create the archive atomically without overwriting an existing file by appending ` (N)` before the extension as needed. Defaults to false. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("noOverwrite")] + public bool? NoOverwrite { get; set; } - /// Gets or sets the mc_last_event_id value. - [JsonPropertyName("mc_last_event_id")] - public string? McLastEventId { get; set; } + /// Absolute or server-relative path for the .tgz archive to create. + [JsonPropertyName("outputPath")] + public required string OutputPath { get; set; } +} - /// Gets or sets the mc_session_id value. - [JsonPropertyName("mc_session_id")] - public string? McSessionId { get; set; } +/// The directory variant of . +[Experimental(Diagnostics.Experimental)] +public partial class DebugCollectLogsDestinationDirectory : DebugCollectLogsDestination +{ + /// + [JsonIgnore] + public override string Kind => "directory"; - /// Gets or sets the mc_task_id value. - [JsonPropertyName("mc_task_id")] - public string? McTaskId { get; set; } + /// Directory where redacted files should be staged. The directory is created if needed. + [JsonPropertyName("outputDirectory")] + public required string OutputDirectory { get; set; } +} - /// Gets or sets the name value. - [JsonPropertyName("name")] - public string? Name { get; set; } +/// Built-in session diagnostics to include in the bundle. Omitted fields default to true. +[Experimental(Diagnostics.Experimental)] +public sealed class DebugCollectLogsInclude +{ + /// Server-local path to the current process log. When set, it is included as `process.log` and its directory is searched for prior logs from the same session. + [JsonPropertyName("currentProcessLogPath")] + public string? CurrentProcessLogPath { get; set; } - /// Gets or sets the remote_steerable value. - [JsonPropertyName("remote_steerable")] - public bool? RemoteSteerable { get; set; } + /// Include the session event log (`events.jsonl`). Defaults to true. + [JsonPropertyName("events")] + public bool? Events { get; set; } - /// Gets or sets the repository value. - [JsonPropertyName("repository")] - public string? Repository { get; set; } + /// Server-local path to the session's events.jsonl file. Internal callers normally omit this and let the runtime derive it from the session. + [JsonPropertyName("eventsPath")] + public string? EventsPath { get; set; } - /// Gets or sets the summary_count value. - [JsonPropertyName("summary_count")] - public long? SummaryCount { get; set; } + /// Maximum number of previous process logs to include. Defaults to 5. + [JsonPropertyName("previousProcessLogLimit")] + public long? PreviousProcessLogLimit { get; set; } - /// Gets or sets the updated_at value. - [JsonPropertyName("updated_at")] - public DateTimeOffset? UpdatedAt { get; set; } + /// Server-local process log directory to search when `currentProcessLogPath` is unavailable, useful for collecting logs for inactive sessions. + [JsonPropertyName("processLogDirectory")] + public string? ProcessLogDirectory { get; set; } - /// Gets or sets the user_named value. - [JsonPropertyName("user_named")] - public bool? UserNamed { get; set; } + /// Include process logs for the session. Defaults to true. + [JsonPropertyName("processLogs")] + public bool? ProcessLogs { get; set; } + + /// Include interactive shell logs written under the session's `shell-logs` directory. Defaults to true. + [JsonPropertyName("shellLogs")] + public bool? ShellLogs { get; set; } } -/// Current workspace metadata for the session, including its absolute filesystem path when available. +/// Options for collecting a redacted session debug bundle. [Experimental(Diagnostics.Experimental)] -public sealed class WorkspacesGetWorkspaceResult +internal sealed class DebugCollectLogsRequest { - /// 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; } + /// Caller-provided server-local files or directories to include in addition to the runtime's built-in session diagnostics. This lets host applications add their own diagnostics without changing the API shape. + [JsonPropertyName("additionalEntries")] + public IList? AdditionalEntries { get; set; } - /// Current workspace metadata, or null if not available. - [JsonPropertyName("workspace")] - public WorkspacesGetWorkspaceResultWorkspace? Workspace { get; set; } -} + /// Where the redacted bundle should be written. Use `archive` to produce a .tgz, or `directory` to stage redacted files for caller-managed upload/post-processing. + [JsonPropertyName("destination")] + public DebugCollectLogsDestination Destination { get => field ??= new(); set; } + + /// Which built-in session diagnostics to include. Omitted fields default to true. + [JsonPropertyName("include")] + public DebugCollectLogsInclude? Include { get; set; } -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionWorkspacesGetWorkspaceRequest -{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Workspace metadata fields to update. +/// Canvas action that the agent or host can invoke. To discover the input schema for a particular action, call the list_canvas_capabilities tool. [Experimental(Diagnostics.Experimental)] -internal sealed class WorkspacesUpdateMetadataRequest +public sealed class CanvasAction { - /// Opaque workspace context supplied by the session host. - [JsonPropertyName("context")] - public JsonElement? Context { get; set; } + /// Description of the action. + [JsonPropertyName("description")] + public string? Description { get; set; } - /// Optional workspace display name override. - [JsonPropertyName("name")] - public string? Name { get; set; } + /// JSON Schema for the action input. + [JsonPropertyName("inputSchema")] + public JsonElement? InputSchema { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Action name exposed by the canvas provider. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; } -/// Optional session context used when creating a local workspace. +/// Canvas available in the current session. [Experimental(Diagnostics.Experimental)] -internal sealed class WorkspacesEnsureRequest +public sealed class DiscoveredCanvas { - /// Opaque workspace context supplied by the session host. - [JsonPropertyName("context")] - public JsonElement? Context { get; set; } + /// Actions the agent or host may invoke on an open instance. + [JsonPropertyName("actions")] + public IList? Actions { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Provider-local canvas identifier. + [JsonPropertyName("canvasId")] + public string CanvasId { get; set; } = string.Empty; + + /// Short, single-sentence description shown to the agent in canvas catalogs. + [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("description")] + public string Description { get; set; } = string.Empty; + + /// Human-readable canvas name. + [JsonPropertyName("displayName")] + public string DisplayName { get; set; } = string.Empty; + + /// Owning provider identifier. + [JsonPropertyName("extensionId")] + public string ExtensionId { get; set; } = string.Empty; + + /// Owning extension display name, when available. + [JsonPropertyName("extensionName")] + public string? ExtensionName { get; set; } + + /// Host-local PNG path for the canvas icon, when supplied. + [JsonPropertyName("icon")] + public string? Icon { get; set; } + + /// JSON Schema for canvas open input. + [JsonPropertyName("inputSchema")] + public JsonElement? InputSchema { get; set; } } -/// Relative paths of files stored in the session workspace files directory. +/// Declared canvases available in this session. [Experimental(Diagnostics.Experimental)] -public sealed class WorkspacesListFilesResult +public sealed class CanvasList { - /// Relative file paths in the workspace files directory. - [JsonPropertyName("files")] - public IList Files { get => field ??= []; set; } + /// Declared canvases available in this session. + [JsonPropertyName("canvases")] + public IList Canvases { get => field ??= []; set; } } /// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionWorkspacesListFilesRequest +internal sealed class SessionCanvasListRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Contents of the requested workspace file as a UTF-8 string. +/// Open canvas instance snapshot. [Experimental(Diagnostics.Experimental)] -public sealed class WorkspacesReadFileResult +public sealed class OpenCanvasInstance { - /// File content as a UTF-8 string. - [JsonPropertyName("content")] - public string Content { get; set; } = string.Empty; -} - -/// Relative path of the workspace file to read. -[Experimental(Diagnostics.Experimental)] -internal sealed class WorkspacesReadFileRequest -{ - /// Relative path within the workspace files directory. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// 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; -/// Relative path and UTF-8 content for the workspace file to create or overwrite. -[Experimental(Diagnostics.Experimental)] -internal sealed class WorkspacesCreateFileRequest -{ - /// File content to write as a UTF-8 string. - [JsonPropertyName("content")] - public string Content { get; set; } = string.Empty; + /// Owning extension display name, when available. + [JsonPropertyName("extensionName")] + public string? ExtensionName { get; set; } - /// Relative path within the workspace files directory. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// Host-local PNG path for the canvas icon, when supplied. + [JsonPropertyName("icon")] + public string? Icon { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Input supplied when the instance was opened. + [JsonPropertyName("input")] + public JsonElement? Input { get; set; } -/// Workspace checkpoint metadata with assigned number, human-readable title, and checkpoint filename. -[Experimental(Diagnostics.Experimental)] -public sealed class WorkspacesCheckpoints -{ - /// Filename of the checkpoint within the workspace checkpoints directory. - [JsonPropertyName("filename")] - public string Filename { get; set; } = string.Empty; + /// Stable caller-supplied canvas instance identifier. + [JsonPropertyName("instanceId")] + public string InstanceId { get; set; } = string.Empty; - /// Checkpoint number assigned by the workspace manager. - [JsonPropertyName("number")] - public long Number { get; set; } + /// Provider-supplied status text. + [JsonPropertyName("status")] + public string? Status { get; set; } - /// Human-readable checkpoint title. + /// Rendered title. [JsonPropertyName("title")] - public string Title { get; set; } = string.Empty; + public string? Title { get; set; } + + /// URL for web-rendered canvases. + [JsonPropertyName("url")] + public string? Url { get; set; } } -/// Workspace checkpoints in chronological order; empty when the workspace is not enabled. +/// Live open-canvas snapshot. [Experimental(Diagnostics.Experimental)] -public sealed class WorkspacesListCheckpointsResult +public sealed class CanvasListOpenResult { - /// Workspace checkpoints in chronological order. Empty when workspace is not enabled. - [JsonPropertyName("checkpoints")] - public IList Checkpoints { get => field ??= []; set; } + /// Currently open canvas instances. + [JsonPropertyName("openCanvases")] + public IList OpenCanvases { get => field ??= []; set; } } /// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionWorkspacesListCheckpointsRequest +internal sealed class SessionCanvasListOpenRequest { /// 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. -[Experimental(Diagnostics.Experimental)] -public sealed class WorkspacesReadCheckpointResult -{ - /// Checkpoint content as a UTF-8 string, or null when the checkpoint or workspace is missing. - [JsonPropertyName("content")] - public string? Content { get; set; } -} - -/// Checkpoint number to read. +/// Canvas open parameters. [Experimental(Diagnostics.Experimental)] -internal sealed class WorkspacesReadCheckpointRequest -{ - /// Checkpoint number to read. - [JsonPropertyName("number")] - public long Number { get; set; } - - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} - -/// RPC data type for WorkspacesAddSummaryResultSummary operations. -public sealed class WorkspacesAddSummaryResultSummary -{ -} - -/// RPC data type for WorkspacesAddSummaryResultWorkspace operations. -public sealed class WorkspacesAddSummaryResultWorkspace +internal sealed class CanvasOpenRequest { -} + /// Provider-local canvas identifier. + [JsonPropertyName("canvasId")] + public string CanvasId { get; set; } = string.Empty; -/// Persisted summary metadata and refreshed workspace metadata. -[Experimental(Diagnostics.Experimental)] -public sealed class WorkspacesAddSummaryResult -{ - /// Gets or sets the summary value. - [JsonPropertyName("summary")] - public WorkspacesAddSummaryResultSummary? Summary { get; set; } + /// Owning provider identifier. Optional when the canvasId is unique across providers; required to disambiguate when multiple providers register the same canvasId. + [JsonPropertyName("extensionId")] + public string? ExtensionId { get; set; } - /// Gets or sets the workspace value. - [JsonPropertyName("workspace")] - public WorkspacesAddSummaryResultWorkspace? Workspace { get; set; } -} + /// Canvas open input. + [JsonPropertyName("input")] + public JsonElement? Input { get; set; } -/// Compaction summary checkpoint to persist. -[Experimental(Diagnostics.Experimental)] -internal sealed class WorkspacesAddSummaryRequest -{ - /// Markdown summary content to persist. - [JsonPropertyName("content")] - public string Content { get; set; } = string.Empty; + /// Caller-supplied stable instance identifier. + [JsonPropertyName("instanceId")] + public string InstanceId { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; - - /// Summary title shown in checkpoint listings. - [JsonPropertyName("title")] - public string Title { get; set; } = string.Empty; } -/// Rollback point for local workspace summaries. +/// Canvas close parameters. [Experimental(Diagnostics.Experimental)] -internal sealed class WorkspacesTruncateSummariesRequest +internal sealed class CanvasCloseRequest { - /// Number of newest summaries to keep. - [JsonPropertyName("keepCount")] - public long KeepCount { get; set; } + /// Open canvas instance identifier. + [JsonPropertyName("instanceId")] + public string InstanceId { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Autopilot objective file content, or null when missing. +/// Canvas action invocation result. [Experimental(Diagnostics.Experimental)] -public sealed class WorkspacesReadAutopilotObjectiveResult +public sealed class CanvasActionInvokeResult { - /// Autopilot objective file content, or null when missing. - [JsonPropertyName("content")] - public string? Content { get; set; } + /// Provider-supplied action result. + [JsonPropertyName("result")] + public JsonElement? Result { get; set; } } -/// Identifies the target session. +/// Canvas action invocation parameters. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionWorkspacesReadAutopilotObjectiveRequest +internal sealed class CanvasActionInvokeRequest { + /// Action name to invoke. + [JsonPropertyName("actionName")] + public string ActionName { get; set; } = string.Empty; + + /// Action input. + [JsonPropertyName("input")] + public JsonElement? Input { get; set; } + + /// Open canvas instance identifier. + [JsonPropertyName("instanceId")] + public string InstanceId { get; set; } = string.Empty; + /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Result of writing the autopilot objective file. +/// Internal canvas provider registration parameters. [Experimental(Diagnostics.Experimental)] -public sealed class WorkspacesWriteAutopilotObjectiveResult +internal sealed class CanvasProviderRegisterRequest { - /// Filesystem operation performed. - [JsonPropertyName("operation")] - public string Operation { get; set; } = string.Empty; -} + /// Canvas contributions supplied by the provider. + [JsonPropertyName("canvases")] + public IList Canvases { get => field ??= []; set; } -/// Autopilot objective file content to persist. -[Experimental(Diagnostics.Experimental)] -internal sealed class WorkspacesWriteAutopilotObjectiveRequest -{ - /// Autopilot objective file content. - [JsonPropertyName("content")] - public string Content { get; set; } = string.Empty; + /// Connection identifier for callback routing. + [JsonPropertyName("connectionId")] + public string ConnectionId { get; set; } = string.Empty; + + /// Provider metadata supplied by the host. + [JsonPropertyName("info")] + public JsonElement Info { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Result of deleting the autopilot objective file. +/// Internal canvas provider unregistration parameters. [Experimental(Diagnostics.Experimental)] -public sealed class WorkspacesDeleteAutopilotObjectiveResult +internal sealed class CanvasProviderUnregisterRequest { - /// True when a file was deleted. - [JsonPropertyName("deleted")] - public bool Deleted { get; set; } -} + /// Connection identifier to unregister. + [JsonPropertyName("connectionId")] + public string ConnectionId { get; set; } = string.Empty; -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionWorkspacesDeleteAutopilotObjectiveRequest -{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Whether the autopilot objective file exists. +/// Machine-readable factory run failure. +/// Polymorphic base type discriminated by type. [Experimental(Diagnostics.Experimental)] -public sealed class WorkspacesAutopilotObjectiveExistsResult +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "type", + 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")] +[JsonDerivedType(typeof(FactoryRunFailureFactoryProviderDisconnected), "factory_provider_disconnected")] +public partial class FactoryRunFailure { - /// True when the objective file exists. - [JsonPropertyName("exists")] - public bool Exists { get; set; } + /// The type discriminator. + [JsonPropertyName("type")] + public virtual string Type { get; set; } = string.Empty; } -/// Identifies the target session. + +/// The factory_limit_reached variant of . [Experimental(Diagnostics.Experimental)] -internal sealed class SessionWorkspacesAutopilotObjectiveExistsRequest +public partial class FactoryRunFailureFactoryLimitReached : FactoryRunFailure { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// + [JsonIgnore] + public override string Type => "factory_limit_reached"; -/// RPC data type for WorkspacesSaveLargePasteResultSaved operations. -public sealed class WorkspacesSaveLargePasteResultSaved -{ - /// Filename within the workspace files directory. - [JsonPropertyName("filename")] - public string Filename { get; set; } = string.Empty; + /// Resource ceiling that stopped the run. + [JsonPropertyName("kind")] + public required FactoryRunFailureKind Kind { get; set; } - /// Absolute filesystem path to the saved paste file. - [JsonPropertyName("filePath")] - public string FilePath { get; set; } = string.Empty; + /// Factory run identifier. + [JsonPropertyName("runId")] + public required string RunId { get; set; } - /// Size of the saved file in bytes. - [JsonPropertyName("sizeBytes")] - public long SizeBytes { get; set; } + /// Approved effective ceiling that was reached. + [JsonPropertyName("value")] + public required double Value { get; set; } } -/// Descriptor for the saved paste file, or null when the workspace is unavailable. +/// The factory_resume_declined variant of . [Experimental(Diagnostics.Experimental)] -public sealed class WorkspacesSaveLargePasteResult +public partial class FactoryRunFailureFactoryResumeDeclined : FactoryRunFailure { - /// 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; } + /// + [JsonIgnore] + public override string Type => "factory_resume_declined"; + + /// Human-readable reason the resume did not proceed. + [JsonPropertyName("reason")] + public required string Reason { get; set; } + + /// Factory run identifier whose changed limits were declined. + [JsonPropertyName("runId")] + public required string RunId { get; set; } } -/// Pasted content to save as a UTF-8 file in the session workspace. +/// The factory_durable_failure variant of . [Experimental(Diagnostics.Experimental)] -internal sealed class WorkspacesSaveLargePasteRequest +public partial class FactoryRunFailureFactoryDurableFailure : FactoryRunFailure { - /// Pasted content to save as a UTF-8 file. - [JsonPropertyName("content")] - public string Content { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Type => "factory_durable_failure"; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Stable failure code. + [JsonPropertyName("code")] + public required string Code { get; set; } + + /// Execution-critical durable operation that failed. + [JsonPropertyName("operation")] + public required FactoryDurableOperation Operation { get; set; } + + /// Factory run identifier. + [JsonPropertyName("runId")] + public required string RunId { get; set; } } -/// A single changed file and its unified diff. +/// The run stopped because its usage accounting could not be completed. +/// The factory_accounting_incomplete variant of . [Experimental(Diagnostics.Experimental)] -public sealed class WorkspaceDiffFileChange +public partial class FactoryRunFailureFactoryAccountingIncomplete : FactoryRunFailure { - /// Type of change represented by this file diff. - [JsonPropertyName("changeType")] - public WorkspaceDiffFileChangeType ChangeType { get; set; } + /// + [JsonIgnore] + public override string Type => "factory_accounting_incomplete"; - /// Unified diff content for the file. Empty when the diff was truncated. - [JsonPropertyName("diff")] - public string Diff { get; set; } = string.Empty; + /// Confirmed usage in nano-AIU, representing the floor of what the run spent. + [JsonPropertyName("drainedNanoAiu")] + public required long DrainedNanoAiu { get; set; } - /// Whether the diff content was omitted because it exceeded the per-file size limit. - [JsonPropertyName("isTruncated")] - public bool? IsTruncated { get; set; } + /// Factory run identifier. + [JsonPropertyName("runId")] + public required string RunId { get; set; } +} - /// Original file path for renamed files. - [JsonPropertyName("oldPath")] - public string? OldPath { get; set; } +/// The extension that owns the factory disconnected while the run was executing, so the host halted it. The run's journaled subagent results are preserved so a resume can reuse them. +/// The factory_provider_disconnected variant of . +[Experimental(Diagnostics.Experimental)] +public partial class FactoryRunFailureFactoryProviderDisconnected : FactoryRunFailure +{ + /// + [JsonIgnore] + public override string Type => "factory_provider_disconnected"; - /// 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; + /// Factory run identifier. + [JsonPropertyName("runId")] + public required string RunId { get; set; } } -/// Workspace diff result for the requested mode. +/// Complete current or terminal factory run envelope. [Experimental(Diagnostics.Experimental)] -public sealed class WorkspaceDiffResult +public sealed class FactoryRunResult { - /// Default branch used for a branch diff, when branch mode was requested. - [JsonPropertyName("baseBranch")] - public string? BaseBranch { get; set; } + /// One-based execution attempt represented by this envelope. Absent before the first attempt starts or when returned by an older runtime. + [JsonPropertyName("attempt")] + public long? Attempt { get; set; } - /// Changed files and their unified diffs. - [JsonPropertyName("changes")] - public IList Changes { get => field ??= []; set; } + /// Error message for an errored run. + [JsonPropertyName("error")] + public string? Error { get; 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; } + /// Machine-readable failure details for a halted or errored run. + [JsonPropertyName("failure")] + public FactoryRunFailure? Failure { get; set; } - /// Effective mode used for the returned changes. - [JsonPropertyName("mode")] - public WorkspaceDiffMode Mode { get; set; } + /// Reason for a halted or cancelled run. + [JsonPropertyName("reason")] + public string? Reason { get; set; } - /// Diff mode requested by the client. - [JsonPropertyName("requestedMode")] - public WorkspaceDiffMode RequestedMode { get; set; } + /// Completed factory result. + [JsonPropertyName("result")] + public JsonElement? Result { 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; } + /// 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. + [JsonPropertyName("status")] + public FactoryRunStatus Status { get; set; } } -/// Parameters for computing a workspace diff. +/// Wire-only per-invocation factory resource ceiling overrides. [Experimental(Diagnostics.Experimental)] -internal sealed class WorkspacesDiffRequest +public sealed class FactoryRunLimits { - /// When true, ignore whitespace-only changes (git `--ignore-all-space`). Defaults to false. - [JsonPropertyName("ignoreWhitespace")] - public bool? IgnoreWhitespace { get; set; } + /// 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; } - /// Diff mode requested by the client. - [JsonPropertyName("mode")] - public WorkspaceDiffMode Mode { get; set; } + /// Maximum number of factory subagents that may run concurrently. + [JsonPropertyName("maxConcurrentSubagents")] + public long? MaxConcurrentSubagents { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Maximum total number of factory subagents that may be admitted. + [JsonPropertyName("maxTotalSubagents")] + public long? MaxTotalSubagents { 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; } } -/// 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`). +/// Options controlling factory invocation. [Experimental(Diagnostics.Experimental)] -public sealed class CompletionsGetTriggerCharactersResult +public sealed class RunOptions { - /// Trigger characters advertised by the host (e.g. `["@", "#"]`). Empty disables host-driven completions for the session. - [JsonPropertyName("triggerCharacters")] - public IList TriggerCharacters { get => field ??= []; set; } + /// Per-invocation resource ceiling overrides. + [JsonPropertyName("limits")] + public FactoryRunLimits? Limits { get; set; } + + /// Whether to emit factory phase names to the session transcript. + [JsonPropertyName("logPhaseNames")] + public bool? LogPhaseNames { get; set; } + + /// Whether to notify the originating session when the factory completes. + [JsonPropertyName("notifyOnComplete")] + public bool? NotifyOnComplete { get; set; } + + /// Run identifier whose journal and progress should seed this resumed run. + [JsonPropertyName("resumeFromRunId")] + public string? ResumeFromRunId { get; set; } } -/// Identifies the target session. +/// Parameters for invoking a registered factory. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionCompletionsGetTriggerCharactersRequest +internal sealed class FactoryRunRequest { + /// Factory input value. + [JsonPropertyName("args")] + public JsonElement Args { get; set; } + + /// Registered factory name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Factory invocation options. + [JsonPropertyName("options")] + public RunOptions? Options { get; set; } + /// 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. +/// Resolved persisted factory identity and resumed run envelope. [Experimental(Diagnostics.Experimental)] -public sealed class SessionCompletionItem +public sealed class FactoryResumeResult { - /// 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; } + /// Persisted factory name resolved for the resumed run. + [JsonPropertyName("factoryName")] + public string FactoryName { get; set; } = string.Empty; - /// Start of the replacement range in `text`, in UTF-16 code units. - [JsonPropertyName("rangeStart")] - public long? RangeStart { get; set; } + /// Terminal resumed run envelope. + [JsonPropertyName("run")] + public FactoryRunResult Run { get => field ??= new(); set; } } -/// Host-driven completion items for the current composer input. Empty when the host returns no items or does not support completions. +/// Parameters for resuming a factory run from its persisted identity. [Experimental(Diagnostics.Experimental)] -public sealed class CompletionsRequestResult +internal sealed class FactoryResumeRequest { - /// Completion items in host-ranked order. - [JsonPropertyName("items")] - public IList Items { get => field ??= []; set; } -} + /// Optional per-invocation resource ceiling overrides. + [JsonPropertyName("limits")] + public FactoryRunLimits? Limits { 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; } + /// Whether to emit factory phase names to the session transcript. + [JsonPropertyName("logPhaseNames")] + public bool? LogPhaseNames { get; set; } + + /// Whether to notify the originating session when the factory completes. + [JsonPropertyName("notifyOnComplete")] + public bool? NotifyOnComplete { 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 full composed composer input. - [JsonPropertyName("text")] - public string Text { get; set; } = string.Empty; } -/// Instruction sources loaded for the session, in merge order. +/// Options for an internal tool-originated factory invocation. [Experimental(Diagnostics.Experimental)] -public sealed class InstructionsGetSourcesResult +internal sealed class FactoryToolRunOptions { - /// Instruction sources for the session. - [JsonPropertyName("sources")] - public IList Sources { get => field ??= []; set; } + /// Per-invocation resource ceiling overrides. + [JsonPropertyName("limits")] + public FactoryRunLimits? Limits { get; set; } + + /// Run identifier whose journal and progress should seed this resumed run. + [JsonPropertyName("resumeFromRunId")] + public string? ResumeFromRunId { get; set; } } -/// Identifies the target session. +/// Internal parameters for invoking a registered factory from a tool. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionInstructionsGetSourcesRequest +internal sealed class FactoryToolRunRequest { + /// Factory input value. + [JsonPropertyName("args")] + public JsonElement Args { get; set; } + + /// Registered factory name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Tool-originated factory invocation options. + [JsonPropertyName("options")] + public FactoryToolRunOptions? Options { get; set; } + /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; + + /// Opaque identifier of the originating tool call. + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } } -/// Indicates whether fleet mode was successfully activated. +/// Internal parameters for resuming a factory run from a tool. [Experimental(Diagnostics.Experimental)] -public sealed class FleetStartResult +internal sealed class FactoryToolResumeRequest { - /// Whether fleet mode was successfully activated. - [JsonPropertyName("started")] - public bool Started { get; set; } -} + /// Optional per-invocation resource ceiling overrides. + [JsonPropertyName("limits")] + public FactoryRunLimits? Limits { 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; } + /// Factory run identifier. + [JsonPropertyName("runId")] + public string RunId { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; + + /// Opaque identifier of the originating tool call. + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } } -/// Agents available to the session. +/// Parameters for retrieving a factory run. [Experimental(Diagnostics.Experimental)] -public sealed class AgentList +internal sealed class FactoryGetRunRequest { - /// Available agents. - [JsonPropertyName("agents")] - public IList Agents { 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; } -/// RPC data type for SessionAgentList operations. +/// Declared or approved factory resource ceilings. [Experimental(Diagnostics.Experimental)] -public sealed class SessionAgentListRequest +public sealed class FactoryDeclaredLimits { - /// 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; } + /// Maximum AI credits consumed by subagents and descendants. + [JsonPropertyName("maxAiCredits")] + public double? MaxAiCredits { 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; } + /// Maximum concurrently active subagents. + [JsonPropertyName("maxConcurrentSubagents")] + public long? MaxConcurrentSubagents { get; set; } + + /// Maximum total subagents spawned by the run. + [JsonPropertyName("maxTotalSubagents")] + public long? MaxTotalSubagents { get; set; } + + /// Maximum accumulated active execution time in seconds. + [JsonPropertyName("timeoutSeconds")] + public double? TimeoutSeconds { get; set; } } -/// RPC data type for SessionAgentListRequestWithSession operations. +/// Durable factory resource consumption. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionAgentListRequestWithSession +public sealed class FactoryRunConsumed { - /// 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; } + /// Accumulated active execution time in milliseconds. + [JsonPropertyName("activeMs")] + public long ActiveMs { 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; } + /// AI usage consumed by the run in nano-AIU. + [JsonPropertyName("nanoAiu")] + public long NanoAiu { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Total subagents spawned by the run. + [JsonPropertyName("subagents")] + public long Subagents { get; set; } } -/// An in-memory authored prompt override for an available agent. +/// Current factory phase identity. [Experimental(Diagnostics.Experimental)] -internal sealed class AgentSetPromptRequest +public sealed class FactoryCurrentPhase { - /// Stable effective agent id. Plugin namespace separators are normalized. + /// Current phase identifier. [JsonPropertyName("id")] public string Id { 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; + /// Zero-based declared phase ordinal, or null for an undeclared phase. + [JsonPropertyName("ordinal")] + public long? Ordinal { get; set; } } -/// The currently selected custom agent, or null when using the default agent. +/// Prompt-safe terminal factory outcome. [Experimental(Diagnostics.Experimental)] -public sealed class AgentGetCurrentResult +public sealed class FactoryRunTerminal { - /// Currently selected custom agent, or null if using the default agent. - [JsonPropertyName("agent")] - public AgentInfo? Agent { get; set; } -} + /// Human-readable terminal error. + [JsonPropertyName("error")] + public string? Error { get; set; } -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionAgentGetCurrentRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Machine-readable terminal failure. + [JsonPropertyName("failure")] + public FactoryRunFailure? Failure { get; set; } -/// The newly selected custom agent. -[Experimental(Diagnostics.Experimental)] -public sealed class AgentSelectResult -{ - /// The newly selected custom agent. - [JsonPropertyName("agent")] - public AgentInfo Agent { get => field ??= new(); set; } + /// Human-readable terminal reason. + [JsonPropertyName("reason")] + public string? Reason { get; set; } + + /// Prompt-safe preview of the completed result. + [JsonPropertyName("resultPreview")] + public string? ResultPreview { get; set; } } -/// Name of the custom agent to select for subsequent turns. +/// Durable factory run summary with read-time live overlays. [Experimental(Diagnostics.Experimental)] -internal sealed class AgentSelectRequest +public sealed class FactoryRunSummary { - /// Name of the custom agent to select. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Epoch milliseconds when the current active segment started, or null while inactive. + [JsonPropertyName("activeSegmentStartedAt")] + public long? ActiveSegmentStartedAt { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Approved effective resource ceilings, or null until approved. + [JsonPropertyName("approved")] + public FactoryDeclaredLimits? Approved { get; set; } -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionAgentDeselectRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Epoch milliseconds when the run completed, or null while nonterminal. + [JsonPropertyName("completedAt")] + public long? CompletedAt { get; set; } + + /// Durable resource consumption. + [JsonPropertyName("consumed")] + public FactoryRunConsumed Consumed { get => field ??= new(); set; } + + /// Epoch milliseconds when the run was created. + [JsonPropertyName("createdAt")] + public long CreatedAt { get; set; } + + /// Current phase identity, or null before any phase is entered. + [JsonPropertyName("currentPhase")] + public FactoryCurrentPhase? CurrentPhase { get; set; } + + /// Resource ceilings declared by the factory. + [JsonPropertyName("declaredLimits")] + public FactoryDeclaredLimits DeclaredLimits { get => field ??= new(); set; } + + /// Number of phases declared by the factory. + [JsonPropertyName("declaredPhaseCount")] + public long DeclaredPhaseCount { get; set; } + + /// Human-readable factory description. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; + + /// Registered factory name. + [JsonPropertyName("factoryName")] + public string FactoryName { get; set; } = string.Empty; + + /// Number of direct factory agents currently live. + [JsonPropertyName("liveAgentCount")] + public long LiveAgentCount { get; set; } + + /// Epoch milliseconds when this live-overlay snapshot was observed. + [JsonPropertyName("observedAt")] + public long ObservedAt { get; set; } + + /// Monotonic durable run revision. + [JsonPropertyName("revision")] + public long Revision { get; set; } + + /// Factory run identifier. + [JsonPropertyName("runId")] + public string RunId { get; set; } = string.Empty; + + /// Epoch milliseconds when execution first started, or null before start. + [JsonPropertyName("startedAt")] + public long? StartedAt { get; set; } + + /// Current factory run status. + [JsonPropertyName("status")] + public FactoryRunStatus Status { get; set; } + + /// Terminal run outcome, or null while nonterminal. + [JsonPropertyName("terminal")] + public FactoryRunTerminal? Terminal { get; set; } + + /// Total direct factory agents spawned across all attempts. + [JsonPropertyName("totalSpawnedAgentCount")] + public long TotalSpawnedAgentCount { get; set; } + + /// Epoch milliseconds when the durable run was last updated. + [JsonPropertyName("updatedAt")] + public long UpdatedAt { get; set; } } -/// Custom agents available to the session after reloading definitions from disk. +/// A page of factory runs in durable creation order. [Experimental(Diagnostics.Experimental)] -public sealed class AgentReloadResult +public sealed class FactoryListRunsResult { - /// Reloaded custom agents. - [JsonPropertyName("agents")] - public IList Agents { get => field ??= []; set; } + /// Whether terminal runs newer than this page exist. + [JsonPropertyName("hasMoreNewer")] + public bool? HasMoreNewer { get; set; } + + /// Newest terminal-run cursor in this page, or null when the terminal window is empty. + [JsonPropertyName("newestSeq")] + public long? NewestSeq { 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; } + + /// Factory run summaries in durable creation order. + [JsonPropertyName("runs")] + public IList Runs { get => field ??= []; set; } } -/// Identifies the target session. +/// Parameters for paging factory runs. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionAgentReloadRequest +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; } -/// Identifier assigned to the newly started background agent task. +/// Prompt-safe durable identity and live status for a direct factory agent. [Experimental(Diagnostics.Experimental)] -public sealed class TasksStartAgentResult +public sealed class FactoryAgentSummary { - /// Generated agent ID for the background task. + /// Accumulated active agent time in milliseconds. + [JsonPropertyName("activeMs")] + public long ActiveMs { get; set; } + + /// Prompt-safe live activity text. + [JsonPropertyName("activity")] + public string? Activity { get; set; } + + /// Stable direct-agent identifier. [JsonPropertyName("agentId")] public string AgentId { get; set; } = string.Empty; -} -/// Agent type, prompt, name, and optional description and model override for the new task. -[Experimental(Diagnostics.Experimental)] -internal sealed class TasksStartAgentRequest -{ - /// Type of agent to start (e.g., 'explore', 'task', 'general-purpose'). + /// Registered agent type. [JsonPropertyName("agentType")] public string AgentType { get; set; } = string.Empty; - /// Short description of the task. - [JsonPropertyName("description")] - public string? Description { get; set; } + /// Epoch milliseconds when the agent completed. + [JsonPropertyName("completedAt")] + public long? CompletedAt { get; set; } - /// Optional model override. - [JsonPropertyName("model")] - public string? Model { get; set; } + /// Friendly, non-unique name intended for display. + [JsonPropertyName("displayName")] + public string? DisplayName { get; set; } - /// Short name for the agent, used to generate a human-readable ID. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Friendly, non-unique name intended for display. + [JsonPropertyName("label")] + public string Label { 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; -} + /// Phase identifier active when the agent was launched, or null. + [JsonPropertyName("phaseId")] + public string? PhaseId { get; set; } -/// 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 -{ - /// The type discriminator. - [JsonPropertyName("type")] - public virtual string Type { get; set; } = string.Empty; -} + /// Model requested when the agent was launched. + [JsonPropertyName("requestedModel")] + public string? RequestedModel { get; set; } + /// Concrete model resolved for the agent. + [JsonPropertyName("resolvedModel")] + public string? ResolvedModel { get; set; } -/// 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 partial class TaskInfoAgent : TaskInfo -{ - /// - [JsonIgnore] - public override string Type => "agent"; + /// Owning factory run identifier. + [JsonPropertyName("runId")] + public string RunId { get; set; } = string.Empty; - /// ISO 8601 timestamp when the current active period began. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("activeStartedAt")] - public DateTimeOffset? ActiveStartedAt { get; set; } + /// Epoch milliseconds when the agent started. + [JsonPropertyName("startedAt")] + public long? StartedAt { get; set; } - /// Accumulated active execution time in milliseconds. - [JsonConverter(typeof(MillisecondsTimeSpanConverter))] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("activeTimeMs")] - public TimeSpan? ActiveTime { get; set; } + /// Current durable or live agent status. + [JsonPropertyName("status")] + public string Status { get; set; } = string.Empty; - /// Type of agent running this task. - [JsonPropertyName("agentType")] - public required string AgentType { get; set; } + /// Tool-call identifier that launched the agent. + [JsonPropertyName("toolCallId")] + public string ToolCallId { get; set; } = string.Empty; +} - /// 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; } +/// Durable lifecycle and timing for one factory phase. +[Experimental(Diagnostics.Experimental)] +public sealed class FactoryPhaseObservation +{ + /// Completed active time accumulated by this phase in milliseconds. + [JsonPropertyName("accumulatedActiveMs")] + public long AccumulatedActiveMs { get; set; } - /// ISO 8601 timestamp when the task finished. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + /// Epoch milliseconds when this phase completed; for a skipped phase, the synthetic skip timestamp (equal to `startedAt`). [JsonPropertyName("completedAt")] - public DateTimeOffset? CompletedAt { get; set; } + public long? CompletedAt { get; set; } - /// Short description of the task. - [JsonPropertyName("description")] - public required string Description { get; set; } + /// Current live active time for this phase in milliseconds. + [JsonPropertyName("currentActiveMs")] + public long CurrentActiveMs { get; set; } - /// Error message when the task failed. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("error")] - public string? Error { get; set; } + /// Optional human-readable phase detail. + [JsonPropertyName("detail")] + public string? Detail { get; set; } - /// Whether task execution is synchronously awaited or managed in the background. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("executionMode")] - public TaskExecutionMode? ExecutionMode { get; set; } + /// Number of times execution entered this phase. + [JsonPropertyName("entryCount")] + public long EntryCount { get; set; } - /// Unique task identifier. + /// Phase 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; } + public string Id { get; set; } = string.Empty; - /// Most recent prompt delivered to the agent. Updated whenever the agent receives a follow-up message. - [JsonPropertyName("prompt")] - public required string Prompt { get; set; } + /// Most recent run attempt that entered this phase, or `0` if the phase has never been entered. + [JsonPropertyName("lastEnteredRunAttempt")] + public long LastEnteredRunAttempt { get; set; } - /// Runtime model resolved for the task when available. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("resolvedModel")] - public string? ResolvedModel { get; set; } + /// Direct agents in this phase that are currently live. + [JsonPropertyName("liveAgentCount")] + public long LiveAgentCount { get; set; } - /// Result text from the task when available. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("result")] - public string? Result { get; set; } + /// Zero-based declared phase ordinal, or null for an undeclared phase. + [JsonPropertyName("ordinal")] + public long? Ordinal { get; set; } - /// ISO 8601 timestamp when the task was started. + /// Epoch milliseconds when this phase first started; for a skipped phase, the synthetic skip timestamp (equal to `completedAt`). [JsonPropertyName("startedAt")] - public required DateTimeOffset StartedAt { get; set; } + public long? StartedAt { get; set; } - /// Current lifecycle status of the task. + /// Derived lifecycle state of the phase. [JsonPropertyName("status")] - public required TaskStatus Status { get; set; } + public FactoryPhaseStatus Status { get; set; } - /// Tool call ID associated with this agent task. - [JsonPropertyName("toolCallId")] - public required string ToolCallId { get; set; } + /// Human-readable phase title. + [JsonPropertyName("title")] + public string Title { get; set; } = string.Empty; + + /// Total direct agents associated with this phase. + [JsonPropertyName("totalAgentCount")] + public long TotalAgentCount { get; set; } } -/// Tracked shell task metadata, including ID, command, status, timing, attachment/execution mode, log path, and PID. -/// The shell variant of . +/// One durable factory progress record. [Experimental(Diagnostics.Experimental)] -public partial class TaskInfoShell : TaskInfo +public sealed class FactoryProgressLine { - /// - [JsonIgnore] - public override string Type => "shell"; + /// Resume attempt that emitted this record. + [JsonPropertyName("attempt")] + public long Attempt { get; set; } - /// Whether the shell runs inside a managed PTY session or as an independent background process. - [JsonPropertyName("attachmentMode")] - public required TaskShellInfoAttachmentMode AttachmentMode { get; set; } + /// Progress record kind. + [JsonPropertyName("kind")] + public FactoryLogLineKind Kind { get; set; } - /// Whether this shell task can be promoted to background mode. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("canPromoteToBackground")] - public bool? CanPromoteToBackground { get; set; } + /// Phase active when the record was emitted, or null before any phase. + [JsonPropertyName("phaseId")] + public string? PhaseId { get; set; } - /// Command being executed. - [JsonPropertyName("command")] - public required string Command { get; set; } + /// Epoch milliseconds when the record was persisted. + [JsonPropertyName("recordedAt")] + public long RecordedAt { get; set; } - /// ISO 8601 timestamp when the task finished. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("completedAt")] - public DateTimeOffset? CompletedAt { get; set; } + /// Global monotonic sequence number within the run. + [JsonPropertyName("seq")] + public long Seq { get; set; } - /// Short description of the task. - [JsonPropertyName("description")] - public required string Description { get; set; } + /// Prompt-safe progress text. + [JsonPropertyName("text")] + public string Text { 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; } +/// A bidirectional page of factory progress. +[Experimental(Diagnostics.Experimental)] +public sealed class FactoryProgressPage +{ + /// Whether progress records newer than this page exist. + [JsonPropertyName("hasMoreNewer")] + public bool HasMoreNewer { get; set; } - /// Unique task identifier. - [JsonPropertyName("id")] - public required string Id { get; set; } + /// Whether progress records older than this page exist. + [JsonPropertyName("hasMoreOlder")] + public bool HasMoreOlder { get; set; } - /// Path to the detached shell log, when available. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("logPath")] - public string? LogPath { get; set; } + /// Newest sequence number in this page, or null when empty. + [JsonPropertyName("newestSeq")] + public long? NewestSeq { get; set; } - /// Process ID when available. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("pid")] - public long? Pid { get; set; } + /// Oldest sequence number in this page, or null when empty. + [JsonPropertyName("oldestSeq")] + public long? OldestSeq { get; set; } - /// ISO 8601 timestamp when the task was started. - [JsonPropertyName("startedAt")] - public required DateTimeOffset StartedAt { get; set; } + /// Progress records in sequence order. + [JsonPropertyName("records")] + public IList Records { get => field ??= []; set; } - /// Current lifecycle status of the task. - [JsonPropertyName("status")] - public required TaskStatus Status { get; set; } + /// Run revision reflected by this page. + [JsonPropertyName("revision")] + public long Revision { get; set; } } -/// Background tasks currently tracked by the session. +/// Full factory run observability detail. [Experimental(Diagnostics.Experimental)] -public sealed class TaskList +public sealed class FactoryRunDetail { - /// Currently tracked tasks. - [JsonPropertyName("tasks")] - public IList Tasks { get => field ??= []; set; } -} + /// Epoch milliseconds when the current active segment started, or null while inactive. + [JsonPropertyName("activeSegmentStartedAt")] + public long? ActiveSegmentStartedAt { get; set; } -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionTasksListRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Durable identities and live statuses for direct factory agents. + [JsonPropertyName("agents")] + public IList Agents { get => field ??= []; set; } -/// 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)] -public sealed class TasksRefreshResult -{ -} + /// Approved effective resource ceilings, or null until approved. + [JsonPropertyName("approved")] + public FactoryDeclaredLimits? Approved { get; set; } -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionTasksRefreshRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Epoch milliseconds when the run completed, or null while nonterminal. + [JsonPropertyName("completedAt")] + public long? CompletedAt { get; set; } -/// 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)] -public sealed class TasksWaitForPendingResult -{ -} + /// Durable resource consumption. + [JsonPropertyName("consumed")] + public FactoryRunConsumed Consumed { get => field ??= new(); set; } -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionTasksWaitForPendingRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Epoch milliseconds when the run was created. + [JsonPropertyName("createdAt")] + public long CreatedAt { get; set; } -/// Polymorphic base type discriminated by type. -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "type", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(TasksGetProgressResultProgressAgent), "agent")] -[JsonDerivedType(typeof(TasksGetProgressResultProgressShell), "shell")] -public partial class TasksGetProgressResultProgress -{ - /// The type discriminator. - [JsonPropertyName("type")] - public virtual string Type { get; set; } = string.Empty; -} + /// Current phase identity, or null before any phase is entered. + [JsonPropertyName("currentPhase")] + public FactoryCurrentPhase? CurrentPhase { get; set; } + /// Resource ceilings declared by the factory. + [JsonPropertyName("declaredLimits")] + public FactoryDeclaredLimits DeclaredLimits { get => field ??= new(); set; } -/// Timestamped display line for task progress output or recent agent activity. -[Experimental(Diagnostics.Experimental)] -public sealed class TaskProgressLine -{ - /// Display message, e.g., "▸ bash", "✓ edit src/foo.ts". - [JsonPropertyName("message")] - public string Message { get; set; } = string.Empty; + /// Number of phases declared by the factory. + [JsonPropertyName("declaredPhaseCount")] + public long DeclaredPhaseCount { get; set; } - /// ISO 8601 timestamp when this event occurred. - [JsonPropertyName("timestamp")] - public DateTimeOffset Timestamp { get; set; } -} + /// Human-readable factory description. + [JsonPropertyName("description")] + public string Description { 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 -{ - /// - [JsonIgnore] - public override string Type => "agent"; + /// Registered factory name. + [JsonPropertyName("factoryName")] + public string FactoryName { get; set; } = string.Empty; - /// The most recent intent reported by the agent. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("latestIntent")] - public string? LatestIntent { get; set; } + /// Number of direct factory agents currently live. + [JsonPropertyName("liveAgentCount")] + public long LiveAgentCount { get; set; } - /// Recent tool execution events converted to display lines. - [JsonPropertyName("recentActivity")] - public required IList RecentActivity { get; set; } -} + /// Epoch milliseconds when this live-overlay snapshot was observed. + [JsonPropertyName("observedAt")] + public long ObservedAt { 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 -{ - /// - [JsonIgnore] - public override string Type => "shell"; + /// Lifecycle and timing observations for each factory phase. + [JsonPropertyName("phases")] + public IList Phases { get => field ??= []; set; } - /// Process ID when available. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("pid")] - public long? Pid { get; set; } + /// Bidirectional page of durable factory progress. + [JsonPropertyName("progress")] + public FactoryProgressPage Progress { get => field ??= new(); set; } - /// Recent stdout/stderr lines from the running shell command. - [JsonPropertyName("recentOutput")] - public required string RecentOutput { get; set; } -} + /// Monotonic durable run revision. + [JsonPropertyName("revision")] + public long Revision { get; set; } -/// Progress information for the task, or null when no task with that ID is tracked. -[Experimental(Diagnostics.Experimental)] -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; } -} + /// Factory run identifier. + [JsonPropertyName("runId")] + public string RunId { get; set; } = string.Empty; -/// Identifier of the background task to fetch progress for. -[Experimental(Diagnostics.Experimental)] -internal sealed class TasksGetProgressRequest -{ - /// Task identifier (agent ID or shell ID). - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// Epoch milliseconds when execution first started, or null before start. + [JsonPropertyName("startedAt")] + public long? StartedAt { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Current factory run status. + [JsonPropertyName("status")] + public FactoryRunStatus Status { get; set; } -/// The first sync-waiting task that can currently be promoted to background mode. -[Experimental(Diagnostics.Experimental)] -public sealed class 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. - [JsonPropertyName("task")] - public TaskInfo? Task { get; set; } -} + /// Terminal run outcome, or null while nonterminal. + [JsonPropertyName("terminal")] + public FactoryRunTerminal? Terminal { get; set; } -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionTasksGetCurrentPromotableRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Total direct factory agents spawned across all attempts. + [JsonPropertyName("totalSpawnedAgentCount")] + public long TotalSpawnedAgentCount { get; set; } -/// Indicates whether the task was successfully promoted to background mode. -[Experimental(Diagnostics.Experimental)] -public sealed class TasksPromoteToBackgroundResult -{ - /// Whether the task was successfully promoted to background mode. - [JsonPropertyName("promoted")] - public bool Promoted { get; set; } + /// Epoch milliseconds when the durable run was last updated. + [JsonPropertyName("updatedAt")] + public long UpdatedAt { get; set; } } -/// Identifier of the task to promote to background mode. +/// Parameters for paging factory progress. [Experimental(Diagnostics.Experimental)] -internal sealed class TasksPromoteToBackgroundRequest +internal sealed class FactoryGetRunProgressRequest { - /// Task identifier. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// 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; } -/// The promoted task as it now exists in background mode, omitted if no promotable task was waiting. +/// Parameters for cancelling a factory run. [Experimental(Diagnostics.Experimental)] -public sealed class TasksPromoteCurrentToBackgroundResult +internal sealed class FactoryCancelRequest { - /// 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; } -} + /// Factory run identifier. + [JsonPropertyName("runId")] + public string RunId { get; set; } = string.Empty; -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionTasksPromoteCurrentToBackgroundRequest -{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Indicates whether the background task was successfully cancelled. +/// Acknowledgement that a factory request was accepted. [Experimental(Diagnostics.Experimental)] -public sealed class TasksCancelResult +public sealed class FactoryAckResult { - /// Whether the task was successfully cancelled. - [JsonPropertyName("cancelled")] - public bool Cancelled { get; set; } } -/// Identifier of the background task to cancel. +/// One ordered factory progress line. [Experimental(Diagnostics.Experimental)] -internal sealed class TasksCancelRequest +public sealed class FactoryLogLine { - /// Task identifier. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// Progress line kind. + [JsonPropertyName("kind")] + public FactoryLogLineKind Kind { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Monotonic sequence number within the factory run. + [JsonPropertyName("seq")] + public long Seq { get; set; } -/// Indicates whether the task was removed. False when the task does not exist or is still running/idle. -[Experimental(Diagnostics.Experimental)] -public sealed class TasksRemoveResult -{ - /// 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; } + /// Progress text. + [JsonPropertyName("text")] + public string Text { get; set; } = string.Empty; } -/// Identifier of the completed or cancelled task to remove from tracking. +/// Parameters for recording factory progress. [Experimental(Diagnostics.Experimental)] -internal sealed class TasksRemoveRequest +internal sealed class FactoryLogRequest { - /// Task identifier. - [JsonPropertyName("id")] - public string Id { 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 message was delivered, with an error message when delivery failed. +/// Result of one factory-scoped subagent call. [Experimental(Diagnostics.Experimental)] -public sealed class TasksSendMessageResult +public sealed class FactoryAgentResult { - /// Error message if delivery failed. - [JsonPropertyName("error")] - public string? Error { get; set; } - - /// Whether the message was successfully delivered or steered. - [JsonPropertyName("sent")] - public bool Sent { get; set; } + /// Agent result, omitted when the agent produced no result. + [JsonPropertyName("result")] + public JsonElement? Result { get; set; } } -/// Identifier of the target agent task, message content, and optional sender agent ID. +/// Options for one factory-scoped subagent call. [Experimental(Diagnostics.Experimental)] -internal sealed class TasksSendMessageRequest +public sealed class FactoryAgentOptions { - /// Agent ID of the sender, if sent on behalf of another agent. - [JsonPropertyName("fromAgentId")] - public string? FromAgentId { get; set; } + /// Optional custom agent name for the subagent. This field is accepted but not yet honored. + [JsonPropertyName("agent")] + public string? Agent { get; set; } - /// Agent task identifier. - [JsonPropertyName("id")] - public string Id { 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; } - /// Message content to send to the agent. - [JsonPropertyName("message")] - public string Message { get; set; } = string.Empty; + /// Optional label distinguishing otherwise identical memoized agent calls. + [JsonPropertyName("label")] + public string? Label { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Optional model identifier for the subagent. + [JsonPropertyName("model")] + public string? Model { 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; } } -/// Skill metadata available to a session, with name, description, source, enabled/invocable state, path, plugin, and argument hint. +/// Parameters for one factory-scoped subagent call. [Experimental(Diagnostics.Experimental)] -public sealed class Skill +internal sealed class FactoryAgentRequest { - /// 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; } + /// Opaque token identifying the current factory execution attempt. + [JsonPropertyName("executionToken")] + public string ExecutionToken { get; set; } = string.Empty; - /// Source location type (e.g., project, personal-copilot, plugin, builtin). - [JsonPropertyName("source")] - public SkillSource Source { get; set; } + /// Factory run identifier that owns the subagent. + [JsonPropertyName("factoryRunId")] + public string FactoryRunId { get; set; } = string.Empty; - /// Whether the skill can be invoked by the user as a slash command. - [JsonPropertyName("userInvocable")] - public bool UserInvocable { get; set; } -} + /// Subagent execution options. + [JsonPropertyName("opts")] + public FactoryAgentOptions Opts { get => field ??= new(); set; } -/// Skills available to the session, with their enabled state. -[Experimental(Diagnostics.Experimental)] -public sealed class SkillList -{ - /// Available skills. - [JsonPropertyName("skills")] - public IList Skills { get => field ??= []; set; } -} + /// Prompt to send to the subagent. + [JsonPropertyName("prompt")] + public string Prompt { get; set; } = string.Empty; -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionSkillsListRequest -{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Skill invocation record with name, path, content, allowed tools, and turn number. +/// Result of reading a factory journal entry. [Experimental(Diagnostics.Experimental)] -public sealed class SkillsInvokedSkill +public sealed class FactoryJournalGetResult { - /// 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; + /// Whether the journal contained the requested key. + [JsonPropertyName("hit")] + public bool Hit { get; set; } - /// Path to the SKILL.md file. - [JsonPropertyName("path")] - public string Path { 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; } } -/// Skills invoked during this session, ordered by invocation time (most recent last). +/// Parameters for reading a factory journal entry. [Experimental(Diagnostics.Experimental)] -public sealed class SkillsGetInvokedResult +internal sealed class FactoryJournalGetRequest { - /// Skills invoked during this session, ordered by invocation time (most recent last). - [JsonPropertyName("skills")] - public IList Skills { get => field ??= []; set; } -} + /// Opaque token identifying the current factory execution attempt. + [JsonPropertyName("executionToken")] + public string ExecutionToken { get; set; } = string.Empty; -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionSkillsGetInvokedRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Namespaced journal key. + [JsonPropertyName("key")] + public string Key { get; set; } = string.Empty; -/// Name of the skill to enable for the session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SkillsEnableRequest -{ - /// Name of the skill to enable. - [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; } -/// Name of the skill to disable for the session. +/// Parameters for storing a factory journal entry. [Experimental(Diagnostics.Experimental)] -internal sealed class SkillsDisableRequest +internal sealed class FactoryJournalPutRequest { - /// Name of the skill to disable. - [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; + + /// Namespaced journal key. + [JsonPropertyName("key")] + public string Key { get; set; } = string.Empty; + + /// JSON result to memoize. + [JsonPropertyName("resultJson")] + public JsonElement ResultJson { 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; } -/// Diagnostics from reloading skill definitions, with warnings and errors as separate lists. +/// 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 SkillsLoadDiagnostics +public sealed class CurrentModel { - /// Errors emitted while loading skills (e.g. skills that failed to load entirely). - [JsonPropertyName("errors")] - public IList Errors { get => field ??= []; set; } + /// Context tier for models that support multiple context-window sizes. + [JsonPropertyName("contextTier")] + public ContextTier? ContextTier { get; set; } - /// Warnings emitted while loading skills (e.g. skills that loaded but had issues). - [JsonPropertyName("warnings")] - public IList Warnings { get => field ??= []; set; } -} + /// Currently active model identifier. + [JsonPropertyName("modelId")] + public string? ModelId { get; set; } -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionSkillsReloadRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// 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 SessionSkillsEnsureLoadedRequest +internal sealed class SessionModelGetCurrentRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Recorded MCP server connection failure. +/// RPC data type for ModelSwitchConfirmation operations. [Experimental(Diagnostics.Experimental)] -public sealed class McpServerFailureInfo +public sealed class ModelSwitchConfirmation { - /// Failure message produced when the MCP server connection failed. - [JsonPropertyName("message")] - public string Message { get; set; } = string.Empty; + /// Current conversation token count before switching models. + [JsonPropertyName("currentTokens")] + public double CurrentTokens { get; set; } - /// epoch-ms timestamp at which the failure was recorded. - [JsonPropertyName("timestamp")] - public long Timestamp { get; set; } -} + /// Target model token limit used by the compaction preflight. + [JsonPropertyName("targetLimit")] + public double TargetLimit { get; set; } -/// Recorded MCP server pending-auth state. -[Experimental(Diagnostics.Experimental)] -public sealed class McpServerNeedsAuthInfo -{ - /// epoch-ms timestamp at which the server signalled it needs authentication. - [JsonPropertyName("timestamp")] - public long Timestamp { get; set; } + /// Display name of the model that requires compaction confirmation. + [JsonPropertyName("targetModelDisplayName")] + public string TargetModelDisplayName { get; set; } = string.Empty; } -/// Host-level state, omitted when no MCP host is initialized. +/// The model identifier active on the session after the switch. [Experimental(Diagnostics.Experimental)] -public sealed class McpHostState +public sealed class ModelSwitchToResult { - /// Names of currently-connected MCP clients. - [JsonPropertyName("clients")] - public IList Clients { get => field ??= []; set; } + /// Compaction confirmation projection when status is confirmation_required. + [JsonPropertyName("confirmation")] + public ModelSwitchConfirmation? Confirmation { get; set; } - /// Configured servers that are explicitly disabled. - [JsonPropertyName("disabledServers")] - public IList DisabledServers { get => field ??= []; set; } + /// 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; } - /// Map of server name to recorded connection failure. - [JsonPropertyName("failedServers")] - public IDictionary FailedServers { get => field ??= new Dictionary(); set; } + /// Deprecation warnings associated with the selected model or options. + [JsonPropertyName("deprecationWarnings")] + public IList? DeprecationWarnings { get; set; } - /// Configured servers filtered out by MCP server policy. - [JsonPropertyName("filteredServers")] - public IList FilteredServers { get => field ??= []; set; } + /// User-facing outcome message for the model switch. + [JsonPropertyName("message")] + public string? Message { get; set; } - /// Whether third-party MCP servers are policy-enabled for this session. - [JsonPropertyName("mcp3pEnabled")] - public bool Mcp3pEnabled { get; set; } + /// Currently active model identifier after the switch. + [JsonPropertyName("modelId")] + public string? ModelId { get; set; } - /// Map of server name to recorded pending-auth state. - [JsonPropertyName("needsAuthServers")] - public IDictionary NeedsAuthServers { get => field ??= new Dictionary(); set; } + /// Persistence failure encountered after applying the model switch. + [JsonPropertyName("persistenceError")] + public string? PersistenceError { get; set; } - /// Names of servers with in-flight connection attempts. - [JsonPropertyName("pendingConnections")] - public IList PendingConnections { get => field ??= []; set; } + /// Lifecycle result for the requested switch. + [JsonPropertyName("status")] + public string? Status { get; set; } + + /// User-facing warning produced while applying the model switch. + [JsonPropertyName("warning")] + public string? Warning { get; set; } } -/// MCP server status entry, including config source/plugin source and any connection error. +/// Vision-specific limits. [Experimental(Diagnostics.Experimental)] -public sealed class McpServer +public sealed class ModelCapabilitiesOverrideLimitsVision { - /// 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("name")] - public string Name { 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; } + /// Maximum image size in bytes. + [JsonPropertyName("max_prompt_image_size")] + public long? MaxPromptImageSize { get; set; } - /// Plugin version that provided this server, when source is plugin. - [JsonPropertyName("sourcePluginVersion")] - public string? SourcePluginVersion { get; set; } + /// Maximum number of images per prompt. + [JsonPropertyName("max_prompt_images")] + public long? MaxPromptImages { get; set; } - /// Connection status: connected, failed, needs-auth, pending, disabled, stopped, or not_configured. - [JsonPropertyName("status")] - public McpServerStatus Status { get; set; } + /// MIME types the model accepts. + [JsonPropertyName("supported_media_types")] + public IList? SupportedMediaTypes { get; set; } } -/// MCP servers configured for the session, with their connection status and host-level state. +/// Token limits for prompts, outputs, and context window. [Experimental(Diagnostics.Experimental)] -public sealed class McpServerList +public sealed class ModelCapabilitiesOverrideLimits { - /// Host-level state, omitted when no MCP host is initialized. - [JsonPropertyName("host")] - public McpHostState? Host { get; set; } + /// Maximum total context window size in tokens. + [JsonPropertyName("max_context_window_tokens")] + public long? MaxContextWindowTokens { get; set; } - /// Configured MCP servers. - [JsonPropertyName("servers")] - public IList Servers { get => field ??= []; set; } + /// Maximum number of output/completion tokens. + [JsonPropertyName("max_output_tokens")] + public long? MaxOutputTokens { get; set; } + + /// Maximum number of prompt/input tokens. + [JsonPropertyName("max_prompt_tokens")] + public long? MaxPromptTokens { get; set; } + + /// Vision-specific limits. + [JsonPropertyName("vision")] + public ModelCapabilitiesOverrideLimitsVision? Vision { get; set; } } -/// Identifies the target session. +/// Feature flags indicating what the model supports. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionMcpListRequest +public sealed class ModelCapabilitiesOverrideSupports { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// 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; } + + /// Whether this model supports reasoning effort configuration. + [JsonPropertyName("reasoningEffort")] + public bool? ReasoningEffort { get; set; } + + /// Whether this model supports vision/image input. + [JsonPropertyName("vision")] + public bool? Vision { get; set; } } -/// Normalized MCP Apps discovery metadata from a tool's `_meta.ui` block. +/// Optional capability overrides (vision, tool_calls, reasoning, etc.). [Experimental(Diagnostics.Experimental)] -public sealed class McpToolUi +public sealed class ModelCapabilitiesOverride { - /// 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; } + /// Token limits for prompts, outputs, and context window. + [JsonPropertyName("limits")] + public ModelCapabilitiesOverrideLimits? Limits { get; set; } - /// Tool visibility advertised by the server. When absent, MCP Apps defaults apply. - [JsonPropertyName("visibility")] - public IList? Visibility { get; set; } + /// Feature flags indicating what the model supports. + [JsonPropertyName("supports")] + public ModelCapabilitiesOverrideSupports? Supports { get; set; } } -/// MCP tool metadata with tool name, optional description, and normalized MCP Apps discovery metadata. +/// Environment variables consulted while resolving model-picker settings. +public sealed class ModelPickerSettingsContextEnvironment +{ +} + +/// Filesystem and environment context used to resolve model-picker settings. [Experimental(Diagnostics.Experimental)] -public sealed class McpTools +public sealed class ModelPickerSettingsContext { - /// Tool description, when provided. - [JsonPropertyName("description")] - public string? Description { get; set; } + /// Optional Copilot configuration directory containing persisted settings. + [JsonPropertyName("configDir")] + public string? ConfigDir { get; set; } - /// Tool name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Environment variables consulted while resolving model-picker settings. + [JsonPropertyName("environment")] + public ModelPickerSettingsContextEnvironment Environment { get => field ??= new(); 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; } + /// User home directory used when resolving persisted settings. + [JsonPropertyName("homeDirectory")] + public string HomeDirectory { get; set; } = string.Empty; } -/// Tools exposed by the connected MCP server. Throws when the server is not connected. +/// RPC data type for ModelPickerPersistence operations. [Experimental(Diagnostics.Experimental)] -public sealed class McpListToolsResult +public sealed class ModelPickerPersistenceRequest { - /// Tools exposed by the server. - [JsonPropertyName("tools")] - public IList Tools { get => field ??= []; set; } + /// Whether context tier was explicitly selected and should be persisted. + [JsonPropertyName("contextTierExplicit")] + public bool? ContextTierExplicit { get; set; } + + /// Whether reasoning effort was explicitly selected and should be persisted. + [JsonPropertyName("reasoningEffortExplicit")] + public bool? ReasoningEffortExplicit { get; set; } + + /// Filesystem and environment context used to resolve settings persistence. + [JsonPropertyName("settingsContext")] + public ModelPickerSettingsContext SettingsContext { get => field ??= new(); set; } } -/// Server name whose tool list should be returned. +/// Target model identifier and optional reasoning effort, summary, capability overrides, and context tier. [Experimental(Diagnostics.Experimental)] -internal sealed class McpListToolsRequest +internal sealed class ModelSwitchToRequest { - /// 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; + /// Explicit response to a model-switch compaction preflight. Omit to request a confirmation projection when compaction is necessary. + [JsonPropertyName("compactionDecision")] + public string? CompactionDecision { get; set; } + + /// 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; } + + /// 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; } + + /// Override individual model capabilities resolved by the runtime. + [JsonPropertyName("modelCapabilities")] + public ModelCapabilitiesOverride? ModelCapabilities { get; set; } + + /// Settings scope used when persisting the selected model. + [JsonPropertyName("modelChangeScope")] + public string? ModelChangeScope { 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; + + /// Optional settings context and explicit-override flags used to persist a picker selection. + [JsonPropertyName("pickerPersistence")] + public ModelPickerPersistenceRequest? PickerPersistence { 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; } + + /// Reasoning summary mode to request for supported model clients. + [JsonPropertyName("reasoningSummary")] + public ReasoningSummary? ReasoningSummary { get; set; } + + /// Optional repository settings scope to persist after the switch commits. + [JsonPropertyName("repoScope")] + public string? RepoScope { get; set; } + + /// Require the target to be currently available and enabled before applying the switch. + [JsonPropertyName("requireAvailable")] + public bool? RequireAvailable { get; set; } + + /// When true, evaluate context-window compaction policy before applying the switch. + [JsonPropertyName("runCompactionPreflight")] + public bool? RunCompactionPreflight { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; + + /// Origin to record on the effective `session.model_change` event for trusted in-process calls. Transport SDK calls are always recorded as `sdk`, regardless of this value. + [JsonPropertyName("source")] + public ModelChangeSource? Source { get; set; } + + /// Output verbosity level to request for supported models. + [JsonPropertyName("verbosity")] + public Verbosity? Verbosity { get; set; } } -/// Name of the MCP server to enable for the session. +/// Managed, repository, and CLI model overrides to overlay onto the session at startup. [Experimental(Diagnostics.Experimental)] -internal sealed class McpEnableRequest +internal sealed class ModelApplyStartupOverlayRequest { - /// 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; + /// Model explicitly selected by the CLI, when provided. + [JsonPropertyName("cliModel")] + public string? CliModel { get; set; } + + /// Whether the overlay is being applied while resuming a deferred session. + [JsonPropertyName("deferredResume")] + public bool? DeferredResume { get; set; } + + /// Model required by device-managed policy, when configured. + [JsonPropertyName("deviceManagedModel")] + public string? DeviceManagedModel { get; set; } + + /// Startup default model from the enterprise policy helper, when configured. Weakest of the managed sources: it applies only when neither device nor server policy names a model, and an explicit user selection still wins. + [JsonPropertyName("policyHelperModel")] + public string? PolicyHelperModel { get; set; } + + /// Context tier selected by repository settings, when configured. + [JsonPropertyName("repoContextTier")] + public string? RepoContextTier { get; set; } + + /// Model selected by repository settings, when configured. + [JsonPropertyName("repoModel")] + public string? RepoModel { get; set; } + + /// Reasoning effort selected by repository settings, when configured. + [JsonPropertyName("repoReasoningEffort")] + public string? RepoReasoningEffort { get; set; } + + /// Model required by server-managed policy, when configured. + [JsonPropertyName("serverManagedModel")] + public string? ServerManagedModel { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Name of the MCP server to disable for the session. +/// 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)] -internal sealed class McpDisableRequest +public sealed class ModelSetReasoningEffortResult { - /// 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; + /// 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 SessionMcpReloadRequest +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; } -/// MCP server allowed by policy, with server name and optional PII-free explanatory note. +/// Cost-category metadata for a CAPI model. [Experimental(Diagnostics.Experimental)] -public sealed class McpAllowedServer +public sealed class SessionModelPriceCategory { - /// Allowed server name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// CAPI model identifier. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; - /// PII-free note explaining why the server was allowed. - [JsonPropertyName("redactedNote")] - public string? RedactedNote { get; set; } + /// Cost category assigned to the model. + [JsonPropertyName("priceCategory")] + public ModelPickerPriceCategory PriceCategory { get; set; } } -/// MCP server filtered by policy, with name, reason, and optional redacted reason. +/// The list of models available to this session. [Experimental(Diagnostics.Experimental)] -public sealed class McpFilteredServer +public sealed class SessionModelList { - /// 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; } - - /// Filtered server name. - [JsonPropertyName("name")] - public string Name { 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; } - /// Human-readable filter reason. - [JsonPropertyName("reason")] - public string Reason { get; set; } = string.Empty; + /// 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; } - /// PII-free filter reason. - [JsonPropertyName("redactedReason")] - public string? RedactedReason { get; set; } + /// Per-quota snapshots returned alongside the model list, keyed by quota type. + [JsonPropertyName("quotaSnapshots")] + public IDictionary? QuotaSnapshots { get; set; } } -/// MCP server startup filtering result. +/// RPC data type for SessionModelList operations. [Experimental(Diagnostics.Experimental)] -internal sealed class McpStartServersResult +public sealed class SessionModelListRequest { - /// 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; } + /// If true, bypasses the per-session model list cache and re-fetches from CAPI. + [JsonPropertyName("skipCache")] + public bool? SkipCache { get; set; } } -/// Opaque MCP reload configuration. +/// RPC data type for SessionModelListRequestWithSession operations. [Experimental(Diagnostics.Experimental)] -internal sealed class McpReloadWithConfigRequest +internal sealed class SessionModelListRequestWithSession { - /// 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; + + /// If true, bypasses the per-session model list cache and re-fetches from CAPI. + [JsonPropertyName("skipCache")] + public bool? SkipCache { 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. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -public sealed class McpExecuteSamplingResult +internal sealed class SessionModeGetRequest { + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Outcome of an MCP sampling execution: success result, failure error, or cancellation. +/// Outcome of a session mode change, including any model switch it triggered and follow-up the host must perform. [Experimental(Diagnostics.Experimental)] -public sealed class McpSamplingExecutionResult +public sealed class ModeSetResult { - /// 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; } + /// Whether the host should arm an interactive continuation after the mode change. + [JsonPropertyName("armInteractiveContinuation")] + public bool? ArmInteractiveContinuation { get; set; } - /// Error description, present when action='failure'. - [JsonPropertyName("error")] - public string? Error { get; set; } + /// Compaction confirmation required before the mode change can complete. + [JsonPropertyName("confirmation")] + public ModelSwitchConfirmation? Confirmation { 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; } -} + /// Whether the host must defer implementing the requested mode change. + [JsonPropertyName("deferImplementation")] + public bool? DeferImplementation { 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. -[Experimental(Diagnostics.Experimental)] -public sealed class McpExecuteSamplingRequest -{ + /// Deprecation warnings associated with the model selected by the mode change. + [JsonPropertyName("deprecationWarnings")] + public IList? DeprecationWarnings { get; set; } + + /// User-facing outcome message for the model switch triggered by the mode change. + [JsonPropertyName("message")] + public string? Message { get; set; } + + /// Whether applying the mode changed the active model. + [JsonPropertyName("modelChanged")] + public bool ModelChanged { get; set; } + + /// Lifecycle status of the requested mode change. + [JsonPropertyName("status")] + public string Status { get; set; } = string.Empty; + + /// User-facing warning produced while applying the mode change. + [JsonPropertyName("warning")] + public string? Warning { get; set; } } -/// Identifiers and raw MCP CreateMessageRequest params used to run a sampling inference. +/// Agent interaction mode to apply to the session. [Experimental(Diagnostics.Experimental)] -internal sealed class McpExecuteSamplingParams +internal sealed class ModeSetRequest { - /// 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; } + /// Explicit response to a model-switch compaction preflight. + [JsonPropertyName("compactionDecision")] + public string? CompactionDecision { 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; } + /// Session whose plan-mode base state should be inherited. + [JsonPropertyName("inheritPlanBaseFromSessionId")] + public string? InheritPlanBaseFromSessionId { get; 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; + /// The session mode the agent is operating in. + [JsonPropertyName("mode")] + public SessionMode Mode { get; set; } - /// Name of the MCP server that initiated the sampling request. - [JsonPropertyName("serverName")] - public string ServerName { get; set; } = string.Empty; + /// Whether the selected plan model should be persisted. + [JsonPropertyName("persistPlanSelection")] + public bool? PersistPlanSelection { get; set; } + + /// Settings context used when persisting the selected plan model. + [JsonPropertyName("pickerSettingsContext")] + public ModelPickerSettingsContext? PickerSettingsContext { get; set; } + + /// Context tier to use with the dedicated plan model. + [JsonPropertyName("planContextTier")] + public string? PlanContextTier { get; set; } + + /// Action to perform when leaving plan mode. + [JsonPropertyName("planExitAction")] + public string? PlanExitAction { get; set; } + + /// Dedicated model to use in plan mode, when configured. + [JsonPropertyName("planModel")] + public string? PlanModel { get; set; } + + /// Whether a dedicated plan model is configured. + [JsonPropertyName("planModelConfigured")] + public bool? PlanModelConfigured { get; set; } + + /// Reasoning effort to use with the dedicated plan model. + [JsonPropertyName("planReasoningEffort")] + public string? PlanReasoningEffort { get; set; } + + /// Whether leaving plan mode should restore the session's previous model. + [JsonPropertyName("restorePlanModel")] + public bool? RestorePlanModel { get; set; } /// 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 session's friendly name, or null when not yet set. [Experimental(Diagnostics.Experimental)] -public sealed class McpCancelSamplingExecutionResult +public sealed class NameGetResult { - /// 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 session name (user-set or auto-generated), or null if not yet set. + [JsonPropertyName("name")] + public string? Name { 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 SessionNameGetRequest { - /// 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. -[Experimental(Diagnostics.Experimental)] -public sealed class McpSetEnvValueModeResult -{ - /// Mode recorded on the session after the update. - [JsonPropertyName("mode")] - public McpSetEnvValueModeDetails Mode { get; set; } -} - -/// Mode controlling how MCP server env values are resolved (`direct` or `indirect`). +/// New friendly name to apply to the session. [Experimental(Diagnostics.Experimental)] -internal sealed class McpSetEnvValueModeParams +internal sealed class NameSetRequest { - /// 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; } + /// 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; } -/// Indicates whether the auto-managed `github` MCP server was removed (false when nothing to remove). +/// Indicates whether the auto-generated summary was applied as the session's name. [Experimental(Diagnostics.Experimental)] -public sealed class McpRemoveGitHubResult +public sealed class NameSetAutoResult { - /// 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; } + /// 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; } } -/// Identifies the target session. +/// Auto-generated session summary to apply as the session's name when no user-set name exists. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionMcpRemoveGitHubRequest +internal sealed class NameSetAutoRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; -} -/// Result of configuring GitHub MCP. -[Experimental(Diagnostics.Experimental)] -internal sealed class McpConfigureGitHubResult -{ - /// Whether GitHub MCP configuration changed. - [JsonPropertyName("changed")] - public bool Changed { 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; } -/// Opaque auth info used to configure GitHub MCP. +/// Existence, contents, and resolved path of the session plan file. [Experimental(Diagnostics.Experimental)] -internal sealed class McpConfigureGitHubRequest +public sealed class PlanReadResult { - /// 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; } + /// The content of the plan file, or null if it does not exist. + [JsonPropertyName("content")] + public string? Content { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Whether the plan file exists in the workspace. + [JsonPropertyName("exists")] + public bool Exists { get; set; } + + /// Absolute file path of the plan file, or null if workspace is not enabled. + [JsonPropertyName("path")] + public string? Path { get; set; } } -/// 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. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class McpStartServerRequest +internal sealed class SessionPlanReadRequest { - /// 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 to start. - [JsonPropertyName("serverName")] - public string ServerName { 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. +/// Replacement contents to write to the session plan file. [Experimental(Diagnostics.Experimental)] -internal sealed class McpRestartServerRequest +internal sealed class PlanUpdateRequest { - /// 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; + /// The new content for the plan file. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Server name for an individual MCP server stop. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class McpStopServerRequest +internal sealed class SessionPlanDeleteRequest { - /// Name of the MCP server to stop. - [JsonPropertyName("serverName")] - public string ServerName { get; set; } = string.Empty; - /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Registration parameters for an external MCP client. +/// 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)] -internal sealed class McpRegisterExternalClientRequest +public sealed class PlanSqlTodosRow { - /// In-process MCP Client instance. Marked internal: cannot be serialized across the JSON-RPC boundary. - [JsonInclude] - [JsonPropertyName("client")] - internal JsonElement Client { get; set; } + /// Todo creation time, as stored by the session SQL schema's `datetime('now')` default: `YYYY-MM-DD HH:MM:SS` in UTC. Lets clients attribute todos to the work item that created them (e.g. scoping a goal's progress to the todos it produced) rather than to the whole session. + [JsonPropertyName("createdAt")] + public string? CreatedAt { 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; } + /// Todo description. + [JsonPropertyName("description")] + public string? Description { get; set; } - /// Logical server name for the external client. - [JsonPropertyName("serverName")] - public string ServerName { get; set; } = string.Empty; + /// Todo identifier. + [JsonPropertyName("id")] + public string? Id { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Todo status. + [JsonPropertyName("status")] + public string? Status { 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; } + /// Todo title. + [JsonPropertyName("title")] + public string? Title { get; set; } } -/// Server name identifying the external client to remove. +/// Todo rows read from the session SQL database. Empty when no session database is available. [Experimental(Diagnostics.Experimental)] -internal sealed class McpUnregisterExternalClientRequest +public sealed class PlanReadSqlTodosResult { - /// Server name of the external client to unregister. - [JsonPropertyName("serverName")] - public string ServerName { get; set; } = string.Empty; + /// Rows from the session SQL todos table, ordered by creation time with insertion order used to break ties when available and id used for WITHOUT ROWID tables. + [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; } -/// Whether the named MCP server is running. +/// 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 McpIsServerRunningResult +public sealed class PlanSqlTodoDependency { - /// True if the server has an active client and transport. - [JsonPropertyName("running")] - public bool Running { get; 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; } -/// Server name to check running status for. +/// Todo rows + dependency edges read from the session SQL database. [Experimental(Diagnostics.Experimental)] -internal sealed class McpIsServerRunningRequest +public sealed class PlanReadSqlTodosWithDependenciesResult { - /// Name of the MCP server to check. - [JsonPropertyName("serverName")] - public string ServerName { get; set; } = string.Empty; + /// 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; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Rows from the session SQL todos table, ordered by creation time with insertion order used to break ties when available and id used for WITHOUT ROWID tables. Empty when no database, no todos table, or the SELECT failed. + [JsonPropertyName("rows")] + public IList Rows { get => field ??= []; set; } } -/// Indicates whether the pending MCP OAuth response was accepted. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -public sealed class McpOauthHandlePendingResult +internal sealed class SessionPlanReadSqlTodosWithDependenciesRequest { - /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// 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. -[Experimental(Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(McpOauthPendingRequestResponseToken), "token")] -[JsonDerivedType(typeof(McpOauthPendingRequestResponseCancelled), "cancelled")] -public partial class McpOauthPendingRequestResponse +/// RPC data type for WorkspacesGetWorkspaceResultWorkspace operations. +public sealed class WorkspacesGetWorkspaceResultWorkspace { - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; -} + /// Current Git branch. + [JsonPropertyName("branch")] + public string? Branch { get; set; } + /// Whether the per-session Chronicle upgrade prompt was dismissed for the workspace. + [JsonPropertyName("chronicle_sync_dismissed")] + public bool? ChronicleSyncDismissed { get; set; } -/// The token variant of . -[Experimental(Diagnostics.Experimental)] -public partial class McpOauthPendingRequestResponseToken : McpOauthPendingRequestResponse -{ - /// - [JsonIgnore] - public override string Kind => "token"; + /// Name of the client that created the workspace. + [JsonPropertyName("client_name")] + public string? ClientName { get; set; } - /// Access token acquired by the SDK host. - [JsonPropertyName("accessToken")] - public required string AccessToken { get; set; } + /// Timestamp when the workspace was created. + [JsonPropertyName("created_at")] + public DateTimeOffset? CreatedAt { get; set; } - /// Token lifetime in seconds, if known. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("expiresIn")] - public long? ExpiresIn { get; set; } + /// Current working directory associated with the workspace. + [JsonPropertyName("cwd")] + public string? Cwd { get; set; } - /// OAuth token type. Defaults to Bearer when omitted. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("tokenType")] - public string? TokenType { get; set; } + /// Git repository root associated with the workspace. + [JsonPropertyName("git_root")] + public string? GitRoot { get; set; } + + /// Allowed values for the `WorkspacesWorkspaceDetailsHostType` enumeration. + [JsonPropertyName("host_type")] + public WorkspacesWorkspaceDetailsHostType? HostType { get; set; } + + /// Stable workspace identifier. + [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; + + /// Most recent Mission Control event identifier observed for the workspace. + [JsonPropertyName("mc_last_event_id")] + public string? McLastEventId { get; set; } + + /// Mission Control session identifier associated with the workspace. + [JsonPropertyName("mc_session_id")] + public string? McSessionId { get; set; } + + /// Mission Control task identifier associated with the workspace. + [JsonPropertyName("mc_task_id")] + public string? McTaskId { get; set; } + + /// Workspace display name. + [JsonPropertyName("name")] + public string? Name { get; set; } + + /// Whether the workspace session can be steered remotely. + [JsonPropertyName("remote_steerable")] + public bool? RemoteSteerable { get; set; } + + /// Repository identifier associated with the workspace. + [JsonPropertyName("repository")] + public string? Repository { get; set; } + + /// Number of persisted summaries in the workspace. + [JsonPropertyName("summary_count")] + public long? SummaryCount { get; set; } + + /// Timestamp when the workspace was last updated. + [JsonPropertyName("updated_at")] + public DateTimeOffset? UpdatedAt { get; set; } + + /// Whether the workspace name was explicitly chosen by the user. + [JsonPropertyName("user_named")] + public bool? UserNamed { get; set; } } -/// The cancelled variant of . +/// Current workspace metadata for the session, including its absolute filesystem path when available. [Experimental(Diagnostics.Experimental)] -public partial class McpOauthPendingRequestResponseCancelled : McpOauthPendingRequestResponse +public sealed class WorkspacesGetWorkspaceResult { - /// - [JsonIgnore] - public override string Kind => "cancelled"; + /// 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; } } -/// 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 SessionWorkspacesGetWorkspaceRequest { - /// 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; } -/// Identifies the MCP server whose persisted OAuth credentials were updated. +/// Workspace metadata fields to update. [Experimental(Diagnostics.Experimental)] -internal sealed class McpOauthAuthenticationStateChangedRequest +internal sealed class WorkspacesUpdateMetadataRequest { - /// 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; } + /// Opaque workspace context supplied by the session host. + [JsonPropertyName("context")] + public JsonElement? Context { 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; } + /// Optional workspace display name override. + [JsonPropertyName("name")] + public string? Name { get; 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. -[Experimental(Diagnostics.Experimental)] -public sealed class 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. - [Url] - [StringSyntax(StringSyntaxAttribute.Uri)] - [JsonPropertyName("authorizationUrl")] - public string? AuthorizationUrl { get; set; } -} - -/// Remote MCP server name and optional overrides controlling reauthentication, OAuth client display name, callback success-page copy, and static OAuth client selection. +/// Optional session context used when creating a local workspace. [Experimental(Diagnostics.Experimental)] -internal sealed class McpOauthLoginRequest +internal sealed class WorkspacesEnsureRequest { - /// 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; } - - /// 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; } - - /// 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; } - - /// 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; } - - /// 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; } - - /// 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; } - - /// 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; } - - /// 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; + /// Opaque workspace context supplied by the session host. + [JsonPropertyName("context")] + public JsonElement? Context { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Indicates whether the pending MCP OAuth response was accepted. +/// Relative paths of files stored in the session workspace files directory. [Experimental(Diagnostics.Experimental)] -public sealed class McpOauthRespondResult +public sealed class WorkspacesListFilesResult { - /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// Relative file paths in the workspace files directory. + [JsonPropertyName("files")] + public IList Files { get => field ??= []; set; } } -/// Pending MCP OAuth request id to respond to. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class McpOauthRespondRequest +internal sealed class SessionWorkspacesListFilesRequest { - /// 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 pending MCP headers refresh response was accepted. +/// Contents of the requested workspace file as a UTF-8 string. [Experimental(Diagnostics.Experimental)] -public sealed class McpHeadersHandlePendingHeadersRefreshRequestResult +public sealed class WorkspacesReadFileResult { - /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// File content as a UTF-8 string. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; } -/// Host response: supply dynamic headers or decline this refresh. -/// Polymorphic base type discriminated by kind. +/// Relative path of the workspace file to read. [Experimental(Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(McpHeadersHandlePendingHeadersRefreshRequestHeaders), "headers")] -[JsonDerivedType(typeof(McpHeadersHandlePendingHeadersRefreshRequestNone), "none")] -public partial class McpHeadersHandlePendingHeadersRefreshRequest +internal sealed class WorkspacesReadFileRequest { - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { 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; +} -/// The headers variant of . +/// Relative path and UTF-8 content for the workspace file to create or overwrite. [Experimental(Diagnostics.Experimental)] -public partial class McpHeadersHandlePendingHeadersRefreshRequestHeaders : McpHeadersHandlePendingHeadersRefreshRequest +internal sealed class WorkspacesCreateFileRequest { - /// - [JsonIgnore] - public override string Kind => "headers"; + /// File content to write as a UTF-8 string. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; - /// 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; } + /// 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; } -/// The none variant of . +/// Workspace checkpoint metadata with assigned number, human-readable title, and checkpoint filename. [Experimental(Diagnostics.Experimental)] -public partial class McpHeadersHandlePendingHeadersRefreshRequestNone : McpHeadersHandlePendingHeadersRefreshRequest +public sealed class WorkspacesCheckpoints { - /// - [JsonIgnore] - public override string Kind => "none"; + /// 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; } -/// MCP headers refresh request id and the host response. +/// Workspace checkpoints in chronological order; empty when the workspace is not enabled. [Experimental(Diagnostics.Experimental)] -internal sealed class McpHeadersHandlePendingHeadersRefreshRequestRequest +public sealed class WorkspacesListCheckpointsResult { - /// 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; } - - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Workspace checkpoints in chronological order. Empty when workspace is not enabled. + [JsonPropertyName("checkpoints")] + public IList Checkpoints { get => field ??= []; set; } } -/// MCP Apps resource content with URI, optional MIME type, text or base64 blob, and resource metadata. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -public sealed class McpAppsResourceContent +internal sealed class SessionWorkspacesListCheckpointsRequest { - /// 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; } - - /// 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; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Resource contents returned by the MCP server. +/// Checkpoint content as a UTF-8 string, or null when the checkpoint or workspace is missing. [Experimental(Diagnostics.Experimental)] -public sealed class McpAppsReadResourceResult +public sealed class WorkspacesReadCheckpointResult { - /// Resource contents returned by the server. - [JsonPropertyName("contents")] - public IList Contents { get => field ??= []; set; } + /// Checkpoint content as a UTF-8 string, or null when the checkpoint or workspace is missing. + [JsonPropertyName("content")] + public string? Content { get; set; } } -/// MCP server and resource URI to fetch. +/// Checkpoint number to read. [Experimental(Diagnostics.Experimental)] -internal sealed class McpAppsReadResourceRequest +internal sealed class WorkspacesReadCheckpointRequest { - /// 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; + /// Checkpoint number to read. + [JsonPropertyName("number")] + public long Number { get; set; } /// 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; +/// Metadata for the persisted summary. +public sealed class WorkspacesAddSummaryResultSummary +{ } -/// App-callable tools from the named MCP server. -[Experimental(Diagnostics.Experimental)] -public sealed class McpAppsListToolsResult +/// Refreshed metadata for the containing workspace. +public sealed class WorkspacesAddSummaryResultWorkspace { - /// App-callable tools from the server. - [JsonPropertyName("tools")] - public IList> Tools { get => field ??= []; set; } } -/// MCP server to list app-callable tools for. +/// Persisted summary metadata and refreshed workspace metadata. [Experimental(Diagnostics.Experimental)] -internal sealed class McpAppsListToolsRequest +public sealed class WorkspacesAddSummaryResult { - /// **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; + /// Metadata for the persisted summary. + [JsonPropertyName("summary")] + public WorkspacesAddSummaryResultSummary? Summary { 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; + /// Refreshed metadata for the containing workspace. + [JsonPropertyName("workspace")] + public WorkspacesAddSummaryResultWorkspace? Workspace { get; set; } +} + +/// Compaction summary checkpoint to persist. +[Experimental(Diagnostics.Experimental)] +internal sealed class WorkspacesAddSummaryRequest +{ + /// Markdown summary content to persist. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; + + /// Summary title shown in checkpoint listings. + [JsonPropertyName("title")] + public string Title { get; set; } = string.Empty; } -/// MCP server, tool name, and arguments to invoke from an MCP App view. +/// Rollback point for local workspace summaries. [Experimental(Diagnostics.Experimental)] -internal sealed class McpAppsCallToolRequest +internal sealed class WorkspacesTruncateSummariesRequest { - /// 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. - [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; + /// Number of newest summaries to keep. + [JsonPropertyName("keepCount")] + public long KeepCount { get; set; } /// 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. +/// Autopilot objective file content, or null when missing. [Experimental(Diagnostics.Experimental)] -public sealed class McpAppsSetHostContextDetails +public sealed class WorkspacesReadAutopilotObjectiveResult { - /// 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; } + /// Autopilot objective file content, or null when missing. + [JsonPropertyName("content")] + public string? Content { get; set; } } -/// Host context to advertise to MCP App guests. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class McpAppsSetHostContextRequest +internal sealed class SessionWorkspacesReadAutopilotObjectiveRequest { - /// 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. +/// Result of writing the autopilot objective file. [Experimental(Diagnostics.Experimental)] -public sealed class McpAppsHostContextDetails +public sealed class WorkspacesWriteAutopilotObjectiveResult { - /// Display modes the host supports. - [JsonPropertyName("availableDisplayModes")] - public IList? AvailableDisplayModes { get; set; } - - /// Current display mode (SEP-1865). - [JsonPropertyName("displayMode")] - public McpAppsHostContextDetailsDisplayMode? 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 McpAppsHostContextDetailsPlatform? Platform { get; set; } - - /// UI theme preference per SEP-1865. - [JsonPropertyName("theme")] - public McpAppsHostContextDetailsTheme? Theme { get; set; } + /// Filesystem operation performed. + [JsonPropertyName("operation")] + public string Operation { get; set; } = string.Empty; +} - /// IANA timezone, e.g. 'America/New_York'. - [JsonPropertyName("timeZone")] - public string? TimeZone { get; set; } +/// Autopilot objective file content to persist. +[Experimental(Diagnostics.Experimental)] +internal sealed class WorkspacesWriteAutopilotObjectiveRequest +{ + /// Autopilot objective file content. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; - /// Host application identifier. - [JsonPropertyName("userAgent")] - public string? UserAgent { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Current host context advertised to MCP App guests. +/// Result of deleting the autopilot objective file. [Experimental(Diagnostics.Experimental)] -public sealed class McpAppsHostContext +public sealed class WorkspacesDeleteAutopilotObjectiveResult { - /// Current host context. - [JsonPropertyName("context")] - public McpAppsHostContextDetails Context { get => field ??= new(); set; } + /// True when a file was deleted. + [JsonPropertyName("deleted")] + public bool Deleted { get; set; } } /// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionMcpAppsGetHostContextRequest +internal sealed class SessionWorkspacesDeleteAutopilotObjectiveRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Capability negotiation snapshot. +/// Whether the autopilot objective file exists. [Experimental(Diagnostics.Experimental)] -public sealed class McpAppsDiagnoseCapability +public sealed class WorkspacesAutopilotObjectiveExistsResult { - /// 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; } + /// True when the objective file exists. + [JsonPropertyName("exists")] + public bool Exists { get; set; } } -/// What the server returned for this session. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -public sealed class McpAppsDiagnoseServer +internal sealed class SessionWorkspacesAutopilotObjectiveExistsRequest { - /// Whether the named server is currently connected. - [JsonPropertyName("connected")] - public bool Connected { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Up to 5 tool names with `_meta.ui` for quick inspection. - [JsonPropertyName("sampleToolNames")] - public IList SampleToolNames { get => field ??= []; set; } +/// RPC data type for WorkspacesSaveLargePasteResultSaved operations. +public sealed class WorkspacesSaveLargePasteResultSaved +{ + /// Filename within the workspace files directory. + [JsonPropertyName("filename")] + public string Filename { get; set; } = string.Empty; - /// Total tools returned by the server's tools/list. - [JsonPropertyName("toolCount")] - public double ToolCount { get; set; } + /// Absolute filesystem path to the saved paste file. + [JsonPropertyName("filePath")] + public string FilePath { get; set; } = string.Empty; - /// Tools whose `_meta.ui` is populated (resourceUri and/or visibility set). - [JsonPropertyName("toolsWithUiMeta")] - public double ToolsWithUiMeta { get; set; } + /// Size of the saved file in bytes. + [JsonPropertyName("sizeBytes")] + public long SizeBytes { get; set; } } -/// Diagnostic snapshot of MCP Apps wiring for the named server. +/// Descriptor for the saved paste file, or null when the workspace is unavailable. [Experimental(Diagnostics.Experimental)] -public sealed class McpAppsDiagnoseResult +public sealed class WorkspacesSaveLargePasteResult { - /// 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; } + /// 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; } } -/// MCP server to diagnose MCP Apps wiring for. +/// Pasted content to save as a UTF-8 file in the session workspace. [Experimental(Diagnostics.Experimental)] -internal sealed class McpAppsDiagnoseRequest +internal sealed class WorkspacesSaveLargePasteRequest { - /// 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; + /// Pasted content to save as a UTF-8 file. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// MCP resource content with URI, optional MIME type, text or base64 blob, and resource metadata. +/// A single changed file and its unified diff. [Experimental(Diagnostics.Experimental)] -public sealed class McpResourceContent +public sealed class WorkspaceDiffFileChange { - /// Resource-level metadata (CSP, permissions, etc.). - [JsonPropertyName("_meta")] - public IDictionary? Meta { get; set; } + /// Type of change represented by this file diff. + [JsonPropertyName("changeType")] + public WorkspaceDiffFileChangeType ChangeType { get; set; } - /// Base64-encoded binary content. - [JsonPropertyName("blob")] - public string? Blob { get; set; } + /// Unified diff content for the file. Empty when the diff was truncated. + [JsonPropertyName("diff")] + public string Diff { get; set; } = string.Empty; - /// MIME type of the content. - [JsonPropertyName("mimeType")] - public string? MimeType { get; set; } + /// Whether the diff content was omitted because it exceeded the per-file size limit. + [JsonPropertyName("isTruncated")] + public bool? IsTruncated { get; set; } - /// Text content (e.g. HTML). - [JsonPropertyName("text")] - public string? Text { get; set; } + /// Original file path for renamed files. + [JsonPropertyName("oldPath")] + public string? OldPath { get; set; } - /// The resource URI. - [JsonPropertyName("uri")] - public string Uri { get; set; } = string.Empty; + /// 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; } -/// Resource contents returned by the MCP server. +/// Workspace diff result for the requested mode. [Experimental(Diagnostics.Experimental)] -public sealed class McpResourcesReadResult +public sealed class WorkspaceDiffResult { - /// Resource contents returned by the server. - [JsonPropertyName("contents")] - public IList Contents { get => field ??= []; set; } + /// 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; } } -/// MCP server and resource URI to fetch. +/// Parameters for computing a workspace diff. [Experimental(Diagnostics.Experimental)] -internal sealed class McpResourcesReadRequest +internal sealed class WorkspacesDiffRequest { - /// 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; + /// 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; - - /// Resource URI. - [JsonPropertyName("uri")] - public string Uri { get; set; } = string.Empty; } -/// Standard MCP resource annotations plus preserved non-standard annotation fields. +/// 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 McpResourceAnnotations +public sealed class CompletionsGetTriggerCharactersResult { - /// 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; } + /// Trigger characters advertised by the host (e.g. `["@", "#"]`). Empty disables host-driven completions for the session. + [JsonPropertyName("triggerCharacters")] + public IList TriggerCharacters { get => field ??= []; set; } +} - /// Priority hint for model/client use. - [JsonPropertyName("priority")] - public double? Priority { get; set; } +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionCompletionsGetTriggerCharactersRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// A resource icon descriptor plus preserved non-standard icon fields. +/// 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 McpResourceIcon +public sealed class SessionCompletionItem { - /// Server-provided non-standard icon fields preserved from the MCP response. - [JsonPropertyName("additionalProperties")] - public IDictionary? AdditionalProperties { get; set; } + /// Text spliced into the composer when the item is accepted. + [JsonPropertyName("insertText")] + public string InsertText { get; set; } = string.Empty; - /// Icon MIME type, when known. - [JsonPropertyName("mimeType")] - public string? MimeType { 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; } - /// Icon sizes hint. - [JsonPropertyName("sizes")] - public string? Sizes { get; set; } + /// Primary display label for the picker row. Falls back to `insertText` when absent. + [JsonPropertyName("label")] + public string? Label { get; set; } - /// Icon URI. - [JsonPropertyName("src")] - public string Src { get; set; } = string.Empty; + /// End (exclusive) of the replacement range in `text`, in UTF-16 code units. + [JsonPropertyName("rangeEnd")] + public long? RangeEnd { get; set; } - /// Theme hint for this icon. - [JsonPropertyName("theme")] - public string? Theme { get; set; } + /// Start of the replacement range in `text`, in UTF-16 code units. + [JsonPropertyName("rangeStart")] + public long? RangeStart { 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`. +/// 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 McpResource +public sealed class CompletionsRequestResult { - /// Resource-level metadata. - [JsonPropertyName("_meta")] - public IDictionary? Meta { get; set; } + /// Completion items in host-ranked order. + [JsonPropertyName("items")] + public IList Items { get => field ??= []; set; } +} - /// Server-provided non-standard descriptor fields preserved from the MCP response. - [JsonPropertyName("additionalProperties")] - public IDictionary? AdditionalProperties { 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; } - /// Model/client annotations associated with this resource. - [JsonPropertyName("annotations")] - public McpResourceAnnotations? Annotations { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// Optional description of what this resource represents. - [JsonPropertyName("description")] - public string? Description { get; set; } + /// The full composed composer input. + [JsonPropertyName("text")] + public string Text { get; set; } = string.Empty; +} - /// 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; +/// Instruction sources loaded for the session, in merge order. +[Experimental(Diagnostics.Experimental)] +public sealed class InstructionsGetSourcesResult +{ + /// Instruction sources for the session. + [JsonPropertyName("sources")] + public IList Sources { get => field ??= []; set; } } -/// One page of resources advertised by the named MCP server. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -public sealed class McpResourcesListResult +internal sealed class SessionInstructionsGetSourcesRequest { - /// 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. +/// Indicates whether fleet mode was successfully activated. [Experimental(Diagnostics.Experimental)] -internal sealed class McpResourcesListRequest +public sealed class FleetStartResult { - /// Opaque MCP pagination cursor from a prior `nextCursor` value. - [JsonPropertyName("cursor")] - public string? Cursor { get; set; } + /// Whether fleet mode was successfully activated. + [JsonPropertyName("started")] + public bool Started { 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; +/// 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; } /// Target session identifier. [JsonPropertyName("sessionId")] 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`. +/// Agents available to the session. [Experimental(Diagnostics.Experimental)] -public sealed class McpResourceTemplate +public sealed class AgentList { - /// 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; - - /// Optional human-readable display title. - [JsonPropertyName("title")] - public string? Title { get; set; } - - /// An RFC 6570 URI template for constructing resource URIs. - [JsonPropertyName("uriTemplate")] - public string UriTemplate { get; set; } = string.Empty; + /// Available agents. + [JsonPropertyName("agents")] + public IList Agents { get => field ??= []; set; } } -/// One page of resource templates advertised by the named MCP server. +/// RPC data type for SessionAgentList operations. [Experimental(Diagnostics.Experimental)] -public sealed class McpResourcesListTemplatesResult +public sealed class SessionAgentListRequest { - /// Opaque cursor for the next page, if the server has more resource templates. - [JsonPropertyName("nextCursor")] - public string? NextCursor { 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; } - /// Resource templates advertised by the server (proxied MCP `resources/templates/list`). - [JsonPropertyName("resourceTemplates")] - public IList ResourceTemplates { get => field ??= []; 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; } } -/// MCP server whose resource templates to enumerate. +/// RPC data type for SessionAgentListRequestWithSession operations. [Experimental(Diagnostics.Experimental)] -internal sealed class McpResourcesListTemplatesRequest +internal sealed class SessionAgentListRequestWithSession { - /// Opaque MCP pagination cursor from a prior `nextCursor` value. - [JsonPropertyName("cursor")] - public string? Cursor { 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; } - /// 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; + /// 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; } -/// Session plugin metadata, with name, marketplace, optional version, and enabled state. +/// An in-memory authored prompt override for an available agent. [Experimental(Diagnostics.Experimental)] -public sealed class Plugin +internal sealed class AgentSetPromptRequest { - /// 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; + /// Stable effective agent id. Plugin namespace separators are normalized. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; - /// Plugin name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Replacement authored prompt. Empty text is valid. + [JsonPropertyName("prompt")] + public string Prompt { 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. +/// The currently selected custom agent, or null when using the default agent. [Experimental(Diagnostics.Experimental)] -public sealed class PluginList +public sealed class AgentGetCurrentResult { - /// Installed plugins. - [JsonPropertyName("plugins")] - public IList Plugins { 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 SessionPluginsListRequest +internal sealed class SessionAgentGetCurrentRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// RPC data type for SessionPluginsReload operations. +/// The newly selected custom agent. [Experimental(Diagnostics.Experimental)] -public sealed class SessionPluginsReloadRequest +public sealed class AgentSelectResult { - /// 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; } + /// The newly selected custom agent. + [JsonPropertyName("agent")] + public AgentInfo Agent { get => field ??= new(); set; } } -/// RPC data type for SessionPluginsReloadRequestWithSession operations. +/// Name of the custom agent to select for subsequent turns. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionPluginsReloadRequestWithSession +internal sealed class AgentSelectRequest { - /// 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; } + /// Name of the custom agent to select. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// 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. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -public sealed class ProviderSessionToken +internal sealed class SessionAgentDeselectRequest { - /// 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; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// A snapshot of the provider endpoint the session is currently configured to talk to. +/// Custom agents available to the session after reloading definitions from disk. [Experimental(Diagnostics.Experimental)] -public sealed class ProviderEndpoint +public sealed class AgentReloadResult { - /// 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; } - - /// Provider family. Matches the `type` field of a BYOK provider config. - [JsonPropertyName("type")] - public ProviderEndpointType Type { get; set; } + /// Reloaded custom agents. + [JsonPropertyName("agents")] + public IList Agents { get => field ??= []; set; } +} - /// Wire API to be used, when required for the provider type. - [JsonPropertyName("wireApi")] - public ProviderEndpointWireApi? WireApi { get; set; } +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionAgentReloadRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// RPC data type for SessionProviderGetEndpoint operations. +/// Identifier assigned to the newly started background agent task. [Experimental(Diagnostics.Experimental)] -public sealed class SessionProviderGetEndpointRequest +public sealed class TasksStartAgentResult { - /// 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; } + /// Generated agent ID for the background task. + [JsonPropertyName("agentId")] + public string AgentId { get; set; } = string.Empty; } -/// RPC data type for SessionProviderGetEndpointRequestWithSession operations. +/// Agent type, prompt, name, and optional description and model override for the new task. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionProviderGetEndpointRequestWithSession +internal sealed class TasksStartAgentRequest { - /// 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; } + /// 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; } + + /// Friendly, non-unique name used when displaying the agent. + [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; } -/// The selectable model entries synthesized for the models added by this call. +/// 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 ProviderAddResult +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "type", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(TaskInfoAgent), "agent")] +[JsonDerivedType(typeof(TaskInfoShell), "shell")] +public partial class TaskInfo { - /// 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; } + /// The type discriminator. + [JsonPropertyName("type")] + public virtual string Type { get; set; } = string.Empty; } -/// A BYOK model definition referencing a named provider. + +/// 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 ProviderModelConfig +public partial class TaskInfoAgent : TaskInfo { - /// Optional capability overrides (vision, tool_calls, reasoning, etc.). - [JsonPropertyName("capabilities")] - public ModelCapabilitiesOverride? Capabilities { get; set; } + /// + [JsonIgnore] + public override string Type => "agent"; - /// 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; + /// ISO 8601 timestamp when the current active period began. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("activeStartedAt")] + public DateTimeOffset? ActiveStartedAt { get; set; } - /// Maximum context window tokens for the model. - [JsonPropertyName("maxContextWindowTokens")] - public double? MaxContextWindowTokens { get; set; } + /// Accumulated active execution time in milliseconds. + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("activeTimeMs")] + public TimeSpan? ActiveTime { get; set; } - /// Maximum output tokens for the model. - [JsonPropertyName("maxOutputTokens")] - public double? MaxOutputTokens { get; set; } + /// Type of agent running this task. + [JsonPropertyName("agentType")] + public required string AgentType { get; set; } - /// Maximum prompt/input tokens for the model. - [JsonPropertyName("maxPromptTokens")] - public double? MaxPromptTokens { 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; } - /// Well-known base model id used for behavior/capability/config lookup. Defaults to `id`. - [JsonPropertyName("modelId")] - public string? ModelId { get; set; } + /// ISO 8601 timestamp when the task finished. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("completedAt")] + public DateTimeOffset? CompletedAt { get; set; } - /// Display name for model pickers. Defaults to the provider-qualified selection id (`provider/id`). - [JsonPropertyName("name")] - public string? Name { get; set; } + /// Short description of the task. + [JsonPropertyName("description")] + public required string Description { get; set; } - /// Name of the NamedProviderConfig that serves this model. - [JsonPropertyName("provider")] - public string Provider { get; set; } = string.Empty; + /// Friendly, non-unique name intended for display. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("displayName")] + public string? DisplayName { get; set; } - /// The model name sent to the provider API for inference. Defaults to `id`. - [JsonPropertyName("wireModel")] - public string? WireModel { get; set; } -} + /// Error message when the task failed. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("error")] + public string? Error { get; set; } -/// 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; } -} + /// Whether task execution is synchronously awaited or managed in the background. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("executionMode")] + public TaskExecutionMode? ExecutionMode { get; set; } -/// A named BYOK provider connection (transport + credentials). -[Experimental(Diagnostics.Experimental)] -public sealed class NamedProviderConfig -{ - /// API key. Optional for local providers like Ollama. - [JsonPropertyName("apiKey")] - public string? ApiKey { get; set; } + /// Unique task identifier. + [JsonPropertyName("id")] + public required string Id { get; set; } - /// Azure-specific provider options. - [JsonPropertyName("azure")] - public ProviderConfigAzure? Azure { get; set; } + /// ISO 8601 timestamp when the agent entered idle state. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("idleSince")] + public DateTimeOffset? IdleSince { get; set; } - /// API endpoint URL. - [JsonPropertyName("baseUrl")] - public string BaseUrl { get; set; } = string.Empty; + /// Most recent response text from the agent. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("latestResponse")] + public string? LatestResponse { 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; } + /// Requested model override for the task when specified. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("model")] + public string? Model { 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; } + /// Most recent prompt delivered to the agent. Updated whenever the agent receives a follow-up message. + [JsonPropertyName("prompt")] + public required string Prompt { get; set; } - /// Custom HTTP headers to include in all outbound requests to the provider. - [JsonPropertyName("headers")] - public IDictionary? Headers { get; set; } + /// Runtime model resolved for the task when available. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("resolvedModel")] + public string? ResolvedModel { get; set; } - /// Stable identifier referenced by BYOK model definitions. Must not contain '/'. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Result text from the task when available. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("result")] + public string? Result { get; set; } - /// Provider transport. Defaults to "http". - [JsonPropertyName("transport")] - public ProviderConfigTransport? Transport { get; set; } + /// ISO 8601 timestamp when the task was started. + [JsonPropertyName("startedAt")] + public required DateTimeOffset StartedAt { get; set; } - /// Provider type. Defaults to "openai" for generic OpenAI-compatible APIs. - [JsonPropertyName("type")] - public ProviderConfigType? Type { get; set; } + /// Current lifecycle status of the task. + [JsonPropertyName("status")] + public required TaskStatus Status { get; set; } - /// Wire API format (openai/azure only). Defaults to "completions". - [JsonPropertyName("wireApi")] - public ProviderConfigWireApi? WireApi { get; set; } + /// Tool call ID associated with this agent task. + [JsonPropertyName("toolCallId")] + public required string ToolCallId { 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. +/// Tracked shell task metadata, including ID, command, status, timing, attachment/execution mode, log path, and PID. +/// The shell variant of . [Experimental(Diagnostics.Experimental)] -internal sealed class ProviderAddRequest +public partial class TaskInfoShell : TaskInfo { - /// 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; } + /// + [JsonIgnore] + public override string Type => "shell"; - /// 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; } + /// 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; } + + /// 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; } +} + +/// Background tasks currently tracked by the session. +[Experimental(Diagnostics.Experimental)] +public sealed class TaskList +{ + /// Currently tracked tasks. + [JsonPropertyName("tasks")] + public IList Tasks { get => field ??= []; set; } +} + +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionTasksListRequest +{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Indicates whether the session options patch was applied successfully. +/// 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)] -public sealed class SessionUpdateOptionsResult +public sealed class TasksRefreshResult { - /// Number of hooks loaded from installed plugins, returned when installedPlugins is updated. - [JsonPropertyName("pluginHookCount")] - public long? PluginHookCount { get; set; } +} - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionTasksRefreshRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Source descriptor for a `session.options.update` content-exclusion rule, with source name and type. +/// 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)] -public sealed class OptionsUpdateAdditionalContentExclusionPolicyRuleSource +public sealed class TasksWaitForPendingResult { - /// Gets or sets the name value. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; +} + +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionTasksWaitForPendingRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Gets or sets the type value. +/// Polymorphic base type discriminated by type. +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "type", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(TasksGetProgressResultProgressAgent), "agent")] +[JsonDerivedType(typeof(TasksGetProgressResultProgressShell), "shell")] +public partial class TasksGetProgressResultProgress +{ + /// The type discriminator. [JsonPropertyName("type")] - public string Type { get; set; } = string.Empty; + public virtual string Type { get; set; } = string.Empty; } -/// Single content-exclusion rule supplied to `session.options.update`, with paths, match conditions, and source. + +/// Timestamped display line for task progress output or recent agent activity. [Experimental(Diagnostics.Experimental)] -public sealed class OptionsUpdateAdditionalContentExclusionPolicyRule +public sealed class TaskProgressLine { - /// Gets or sets the ifAnyMatch value. - [JsonPropertyName("ifAnyMatch")] - public IList? IfAnyMatch { get; set; } + /// Display message, e.g., "▸ bash", "✓ edit src/foo.ts". + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; - /// Gets or sets the ifNoneMatch value. - [JsonPropertyName("ifNoneMatch")] - public IList? IfNoneMatch { get; set; } + /// ISO 8601 timestamp when this event occurred. + [JsonPropertyName("timestamp")] + public DateTimeOffset Timestamp { get; set; } +} - /// Gets or sets the paths value. - [JsonPropertyName("paths")] - public IList Paths { get => field ??= []; set; } +/// Progress snapshot for an agent task, with recent activity lines and optional latest intent. +/// The agent variant of . +public partial class TasksGetProgressResultProgressAgent : TasksGetProgressResultProgress +{ + /// + [JsonIgnore] + public override string Type => "agent"; - /// Source descriptor for a `session.options.update` content-exclusion rule, with source name and type. - [JsonPropertyName("source")] - public OptionsUpdateAdditionalContentExclusionPolicyRuleSource Source { get => field ??= new(); 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; } } -/// Content-exclusion policy supplied to `session.options.update`, with rules, last-updated data, and scope. -[Experimental(Diagnostics.Experimental)] -public sealed class OptionsUpdateAdditionalContentExclusionPolicy +/// Progress snapshot for a shell task, with recent stdout/stderr output and optional process ID. +/// The shell variant of . +public partial class TasksGetProgressResultProgressShell : TasksGetProgressResultProgress { - /// Gets or sets the last_updated_at value. - [JsonPropertyName("last_updated_at")] - public JsonElement LastUpdatedAt { get; set; } + /// + [JsonIgnore] + public override string Type => "shell"; - /// Gets or sets the rules value. - [JsonPropertyName("rules")] - public IList Rules { get => field ??= []; set; } + /// Process ID when available. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("pid")] + public long? Pid { get; set; } - /// Allowed values for the `OptionsUpdateAdditionalContentExclusionPolicyScope` enumeration. - [JsonPropertyName("scope")] - public OptionsUpdateAdditionalContentExclusionPolicyScope Scope { get; set; } + /// Recent stdout/stderr lines from the running shell command. + [JsonPropertyName("recentOutput")] + public required string RecentOutput { get; set; } } -/// Options scoped to the built-in CAPI (Copilot API) provider. +/// Progress information for the task, or null when no task with that ID is tracked. [Experimental(Diagnostics.Experimental)] -public sealed class CapiSessionOptions +public sealed class TasksGetProgressResult { - /// 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; } + /// 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; } } -/// Installed plugin record for a session, with marketplace, version, install time, enabled state, cache path, and source. +/// Identifier of the background task to fetch progress for. [Experimental(Diagnostics.Experimental)] -public sealed class SessionInstalledPlugin +internal sealed class TasksGetProgressRequest { - /// Path where the plugin is cached locally. - [JsonPropertyName("cache_path")] - public string? CachePath { get; set; } - - /// Whether the plugin is currently enabled. - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } + /// Task identifier (agent ID or shell ID). + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; - /// Installation timestamp (ISO-8601). - [JsonPropertyName("installed_at")] - public string InstalledAt { get; set; } = string.Empty; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Marketplace the plugin came from (empty string for direct repo installs). - [JsonPropertyName("marketplace")] - public string Marketplace { get; set; } = string.Empty; +/// The first sync-waiting task that can currently be promoted to background mode. +[Experimental(Diagnostics.Experimental)] +public sealed class 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. + [JsonPropertyName("task")] + public TaskInfo? Task { get; set; } +} - /// Plugin name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionTasksGetCurrentPromotableRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Source descriptor for direct repo installs (when marketplace is empty). - [JsonPropertyName("source")] - public JsonElement? Source { get; set; } +/// Indicates whether the task was successfully promoted to background mode. +[Experimental(Diagnostics.Experimental)] +public sealed class TasksPromoteToBackgroundResult +{ + /// Whether the task was successfully promoted to background mode. + [JsonPropertyName("promoted")] + public bool Promoted { 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; } +/// Identifier of the task to promote to background mode. +[Experimental(Diagnostics.Experimental)] +internal sealed class TasksPromoteToBackgroundRequest +{ + /// Task identifier. + [JsonPropertyName("id")] + public string Id { 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). +/// The promoted task as it now exists in background mode, omitted if no promotable task was waiting. [Experimental(Diagnostics.Experimental)] -public sealed class ProviderConfig +public sealed class TasksPromoteCurrentToBackgroundResult { - /// API key. Optional for local providers like Ollama. - [JsonPropertyName("apiKey")] - public string? ApiKey { 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; } +} - /// 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; } - - /// Maximum context window tokens for the model. - [JsonPropertyName("maxContextWindowTokens")] - public double? MaxContextWindowTokens { get; set; } - - /// Maximum output tokens for the model. - [JsonPropertyName("maxOutputTokens")] - public double? MaxOutputTokens { get; set; } - - /// Maximum prompt/input tokens for the model. - [JsonPropertyName("maxPromptTokens")] - public double? MaxPromptTokens { 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; } - - /// 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; } - - /// 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; } -} - -/// macOS seatbelt experimental options. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -public sealed class SandboxConfigUserPolicyExperimentalSeatbelt +internal sealed class SessionTasksPromoteCurrentToBackgroundRequest { - /// 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; } -/// Platform-specific experimental policy fields. +/// Indicates whether the background task was successfully cancelled. [Experimental(Diagnostics.Experimental)] -public sealed class SandboxConfigUserPolicyExperimental +public sealed class TasksCancelResult { - /// macOS seatbelt experimental options. - [JsonPropertyName("seatbelt")] - public SandboxConfigUserPolicyExperimentalSeatbelt? Seatbelt { get; set; } + /// Whether the task was successfully cancelled. + [JsonPropertyName("cancelled")] + public bool Cancelled { get; set; } } -/// Filesystem rules to merge into the base policy. +/// Identifier of the background task to cancel. [Experimental(Diagnostics.Experimental)] -public sealed class SandboxConfigUserPolicyFilesystem +internal sealed class TasksCancelRequest { - /// Whether to clear the policy when the session exits. - [JsonPropertyName("clearPolicyOnExit")] - public bool? ClearPolicyOnExit { get; set; } - - /// Paths explicitly denied. - [JsonPropertyName("deniedPaths")] - public IList? DeniedPaths { get; set; } - - /// Paths granted read-only access. - [JsonPropertyName("readonlyPaths")] - public IList? ReadonlyPaths { get; set; } + /// Task identifier. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; - /// Paths granted read/write access. - [JsonPropertyName("readwritePaths")] - public IList? ReadwritePaths { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// HTTP proxy configuration for sandboxed traffic. +/// Indicates whether the task was removed. False when the task does not exist or is still running/idle. [Experimental(Diagnostics.Experimental)] -public sealed class SandboxConfigUserPolicyNetworkProxy +public sealed class TasksRemoveResult { - /// 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; } - - /// 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; } + /// 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; } } -/// Network rules to merge into the base policy. +/// Identifier of the completed or cancelled task to remove from tracking. [Experimental(Diagnostics.Experimental)] -public sealed class SandboxConfigUserPolicyNetwork +internal sealed class TasksRemoveRequest { - /// 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; } + /// Task identifier. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; - /// 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; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// macOS seatbelt-specific options. +/// Indicates whether the message was delivered, with an error message when delivery failed. [Experimental(Diagnostics.Experimental)] -public sealed class SandboxConfigUserPolicySeatbelt +public sealed class TasksSendMessageResult { - /// Whether the macOS seatbelt profile may access the keychain. - [JsonPropertyName("keychainAccess")] - public bool? KeychainAccess { get; set; } + /// Error message if delivery failed. + [JsonPropertyName("error")] + public string? Error { get; set; } + + /// Whether the message was successfully delivered or steered. + [JsonPropertyName("sent")] + public bool Sent { get; set; } } -/// User-managed sandbox policy fragment merged into the auto-discovered base policy. +/// Identifier of the target agent task, message content, and optional sender agent ID. [Experimental(Diagnostics.Experimental)] -public sealed class SandboxConfigUserPolicy +internal sealed class TasksSendMessageRequest { - /// Deprecated legacy location for `seatbelt`; read only when the top-level `seatbelt` is absent. - [JsonPropertyName("experimental")] - public SandboxConfigUserPolicyExperimental? Experimental { get; set; } + /// Agent ID of the sender, if sent on behalf of another agent. + [JsonPropertyName("fromAgentId")] + public string? FromAgentId { get; set; } - /// Filesystem rules to merge into the base policy. - [JsonPropertyName("filesystem")] - public SandboxConfigUserPolicyFilesystem? Filesystem { get; set; } + /// Agent task identifier. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; - /// Network rules to merge into the base policy. - [JsonPropertyName("network")] - public SandboxConfigUserPolicyNetwork? Network { get; set; } + /// Message content to send to the agent. + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; - /// macOS seatbelt options to merge into the base policy. - [JsonPropertyName("seatbelt")] - public SandboxConfigUserPolicySeatbelt? Seatbelt { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Resolved sandbox configuration. +/// Skill metadata available to a session, with name, description, source, enabled/invocable state, path, plugin, and argument hint. [Experimental(Diagnostics.Experimental)] -public sealed class SandboxConfig +public sealed class Skill { - /// Whether to auto-add the current working directory to readwritePaths. Default: true. - [JsonPropertyName("addCurrentWorkingDirectory")] - public bool? AddCurrentWorkingDirectory { get; set; } + /// Optional freeform hint describing the skill's expected arguments, from the `argument-hint` frontmatter field. + [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; } - /// 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 exporting CARGO_HOME/GOPATH/etc. Default: true (enabled by default; set to false to opt out). - [JsonPropertyName("allowDevToolCaches")] - public bool? AllowDevToolCaches { get; set; } + /// Description of what the skill does. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; - /// Whether sandboxing is enabled for the session. + /// Whether the skill is currently enabled. [JsonPropertyName("enabled")] public bool Enabled { 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; } + /// Unique identifier for the skill. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// 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; } + /// Absolute path to the skill file. + [JsonPropertyName("path")] + public string? Path { get; set; } - /// User-managed sandbox policy fragment merged into the auto-discovered base policy. - [JsonPropertyName("userPolicy")] - public SandboxConfigUserPolicy? UserPolicy { get; set; } -} + /// Name of the plugin that provides the skill, when source is 'plugin'. + [JsonPropertyName("pluginName")] + public string? PluginName { get; set; } -/// A host-provided script sourced before each built-in shell command when its shell target matches the active shell. -[Experimental(Diagnostics.Experimental)] -public sealed class ShellInitScript -{ - /// Path to the script to source. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// Source location type (e.g., project, personal-copilot, plugin, builtin). + [JsonPropertyName("source")] + public SkillSource Source { get; set; } - /// Built-in shell that may source this script. - [JsonPropertyName("shell")] - public ShellInitScriptShell Shell { get; set; } + /// Whether the skill can be invoked by the user as a slash command. + [JsonPropertyName("userInvocable")] + public bool UserInvocable { get; set; } } -/// Per-session settings for built-in shell tools. +/// Skills available to the session, with their enabled state. [Experimental(Diagnostics.Experimental)] -public sealed class ShellOptions +public sealed class SkillList { - /// Controls automatic non-interactive profile loading where supported. Explicit initScripts are unaffected. - [JsonPropertyName("initProfile")] - public ShellInitProfile? InitProfile { 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; } - - /// - /// 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; } + /// Available skills. + [JsonPropertyName("skills")] + public IList Skills { get => field ??= []; set; } } -/// Patch of mutable session options to apply to the running session. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionUpdateOptionsParams +internal sealed class SessionSkillsListRequest +{ + /// 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; + + /// Whether model invocation was disabled when this skill was invoked. + [JsonPropertyName("disableModelInvocation")] + public bool? DisableModelInvocation { get; set; } + + /// 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, or an empty string for an SDK-provided skill without a filesystem identity. + [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. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionSkillsGetInvokedRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Name of the skill to enable for the session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SkillsEnableRequest +{ + /// 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; +} + +/// Name of the skill to disable for the session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SkillsDisableRequest +{ + /// 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. +[Experimental(Diagnostics.Experimental)] +public sealed class SkillsLoadDiagnostics +{ + /// 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; } +} + +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionSkillsReloadRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionSkillsEnsureLoadedRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Recorded MCP server connection failure. +[Experimental(Diagnostics.Experimental)] +public sealed class McpServerFailureInfo +{ + /// 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; } +} + +/// Recorded MCP server pending-auth state. +[Experimental(Diagnostics.Experimental)] +public sealed class McpServerNeedsAuthInfo +{ + /// epoch-ms timestamp at which the server signalled it needs authentication. + [JsonPropertyName("timestamp")] + public long Timestamp { 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; } + + /// Configured servers that are explicitly disabled. + [JsonPropertyName("disabledServers")] + public IList DisabledServers { get => field ??= []; set; } + + /// Map of server name to recorded connection failure. + [JsonPropertyName("failedServers")] + public IDictionary FailedServers { get => field ??= new Dictionary(); set; } + + /// Configured servers filtered out by MCP server policy. + [JsonPropertyName("filteredServers")] + public IList FilteredServers { get => field ??= []; set; } + + /// Whether third-party MCP servers are policy-enabled for this session. + [JsonPropertyName("mcp3pEnabled")] + public bool Mcp3pEnabled { get; set; } + + /// Map of server name to recorded pending-auth state. + [JsonPropertyName("needsAuthServers")] + public IDictionary NeedsAuthServers { get => field ??= new Dictionary(); set; } + + /// 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("name")] + public string Name { 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; } +} + +/// MCP servers configured for the session, with their connection status and host-level state. +[Experimental(Diagnostics.Experimental)] +public sealed class McpServerList +{ + /// 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; } +} + +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionMcpListRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// 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; } + + /// Tool visibility advertised by the server. When absent, MCP Apps defaults apply. + [JsonPropertyName("visibility")] + public IList? Visibility { 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. + [JsonPropertyName("description")] + public string? Description { get; set; } + + /// Tool name. + [JsonPropertyName("name")] + public string Name { 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; } +} + +/// Tools exposed by the connected MCP server. Throws when the server is not connected. +[Experimental(Diagnostics.Experimental)] +public sealed class McpListToolsResult +{ + /// Tools exposed by the server. + [JsonPropertyName("tools")] + public IList Tools { get => field ??= []; set; } +} + +/// Server name whose tool list should be returned. +[Experimental(Diagnostics.Experimental)] +internal sealed class McpListToolsRequest +{ + /// 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. +[Experimental(Diagnostics.Experimental)] +internal sealed class McpEnableRequest +{ + /// 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; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Name of the MCP server to disable for the session. +[Experimental(Diagnostics.Experimental)] +internal sealed class McpDisableRequest +{ + /// 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 +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Result of moving in-flight MCP loading to the background. +[Experimental(Diagnostics.Experimental)] +public sealed class MoveMcpLoadingToBackgroundResult +{ + /// Whether an in-flight MCP load was moved to the background, releasing turns that were waiting on it. False when no MCP load was in flight or the waiting turns had already been released. + [JsonPropertyName("movedToBackground")] + public bool MovedToBackground { get; set; } +} + +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionMcpMoveLoadingToBackgroundRequest +{ + /// 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 +{ + /// 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; } +} + +/// MCP server whose connection attempt failed. +[Experimental(Diagnostics.Experimental)] +public sealed class McpFailedServer +{ + /// The captured connection failure detail. + [JsonPropertyName("error")] + public string? Error { get; set; } + + /// The config key of the server that failed to connect. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; +} + +/// 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; } + + /// 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; + + /// PII-free filter reason. + [JsonPropertyName("redactedReason")] + public string? RedactedReason { get; set; } +} + +/// MCP server startup filtering result. +[Experimental(Diagnostics.Experimental)] +internal sealed class McpStartServersResult +{ + /// Non-default servers allowed by policy. + [JsonPropertyName("allowedServers")] + public IList? AllowedServers { get; set; } + + /// Servers whose connection attempt failed. + [JsonPropertyName("failedServers")] + public IList? FailedServers { get; set; } + + /// Servers filtered out before startup. + [JsonPropertyName("filteredServers")] + public IList FilteredServers { get => field ??= []; set; } +} + +/// Opaque MCP reload configuration. +[Experimental(Diagnostics.Experimental)] +internal sealed class McpReloadWithConfigRequest +{ + /// 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 +{ + /// 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; } + + /// Error description, present when action='failure'. + [JsonPropertyName("error")] + public string? Error { 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; } +} + +/// 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 McpExecuteSamplingRequest +{ +} + +/// Identifiers and raw MCP CreateMessageRequest params used to run a sampling inference. +[Experimental(Diagnostics.Experimental)] +internal sealed class 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). + [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; + + /// 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. +[Experimental(Diagnostics.Experimental)] +public sealed class 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). + [JsonPropertyName("cancelled")] + public bool Cancelled { 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; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Env-value mode recorded on the session after the update. +[Experimental(Diagnostics.Experimental)] +public sealed class McpSetEnvValueModeResult +{ + /// Mode recorded on the session after the update. + [JsonPropertyName("mode")] + public McpSetEnvValueModeDetails Mode { get; set; } +} + +/// Mode controlling how MCP server env values are resolved (`direct` or `indirect`). +[Experimental(Diagnostics.Experimental)] +internal sealed class 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". + [JsonPropertyName("mode")] + public McpSetEnvValueModeDetails Mode { get; set; } + + /// 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). +[Experimental(Diagnostics.Experimental)] +public sealed class 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). + [JsonPropertyName("removed")] + public bool Removed { get; set; } +} + +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionMcpRemoveGitHubRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Result of configuring GitHub MCP. +[Experimental(Diagnostics.Experimental)] +internal sealed class McpConfigureGitHubResult +{ + /// Whether GitHub MCP configuration changed. + [JsonPropertyName("changed")] + public bool Changed { get; set; } +} + +/// Credential-free authentication identity used to configure GitHub MCP. +[Experimental(Diagnostics.Experimental)] +internal sealed class McpConfigureGitHubRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// 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 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). + [JsonPropertyName("config")] + public JsonElement? Config { get; set; } + + /// Name of the MCP server to start. + [JsonPropertyName("serverName")] + public string ServerName { 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. +[Experimental(Diagnostics.Experimental)] +internal sealed class 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). + [JsonPropertyName("config")] + public JsonElement? Config { get; set; } + + /// Name of the MCP server to restart. + [JsonPropertyName("serverName")] + public string ServerName { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Server name for an individual MCP server stop. +[Experimental(Diagnostics.Experimental)] +internal sealed class McpStopServerRequest +{ + /// Name of the MCP server to stop. + [JsonPropertyName("serverName")] + public string ServerName { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Registration parameters for an external MCP client. +[Experimental(Diagnostics.Experimental)] +internal sealed class McpRegisterExternalClientRequest +{ + /// Logical server name for the external client. + [JsonPropertyName("serverName")] + public string ServerName { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Server name identifying the external client to remove. +[Experimental(Diagnostics.Experimental)] +internal sealed class McpUnregisterExternalClientRequest +{ + /// Server name of the external client to unregister. + [JsonPropertyName("serverName")] + public string ServerName { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Whether the named MCP server is running. +[Experimental(Diagnostics.Experimental)] +public sealed class McpIsServerRunningResult +{ + /// True if the server has an active client and transport. + [JsonPropertyName("running")] + public bool Running { get; set; } +} + +/// Server name to check running status for. +[Experimental(Diagnostics.Experimental)] +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; +} + +/// Indicates whether the pending MCP OAuth response was accepted. +[Experimental(Diagnostics.Experimental)] +public sealed class McpOauthHandlePendingResult +{ + /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. + [JsonPropertyName("success")] + public bool Success { get; set; } +} + +/// Host response to the pending OAuth request. +/// Polymorphic base type discriminated by kind. +[Experimental(Diagnostics.Experimental)] +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(McpOauthPendingRequestResponseToken), "token")] +[JsonDerivedType(typeof(McpOauthPendingRequestResponseCancelled), "cancelled")] +public partial class McpOauthPendingRequestResponse +{ + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} + + +/// The token variant of . +[Experimental(Diagnostics.Experimental)] +public partial class McpOauthPendingRequestResponseToken : McpOauthPendingRequestResponse +{ + /// + [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; } + + /// OAuth token type. Defaults to bearer when omitted. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("tokenType")] + public string? TokenType { get; set; } +} + +/// The cancelled variant of . +[Experimental(Diagnostics.Experimental)] +public partial class McpOauthPendingRequestResponseCancelled : McpOauthPendingRequestResponse +{ + /// + [JsonIgnore] + public override string Kind => "cancelled"; +} + +/// Pending MCP OAuth request ID and host-provided token or cancellation response. +[Experimental(Diagnostics.Experimental)] +internal sealed class McpOauthHandlePendingRequest +{ + /// 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; +} + +/// Identifies the MCP server whose persisted OAuth credentials were updated. +[Experimental(Diagnostics.Experimental)] +internal sealed class McpOauthAuthenticationStateChangedRequest +{ + /// 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; } + + /// 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; +} + +/// OAuth authorization URL the caller should open, or empty when cached tokens already authenticated the server. +[Experimental(Diagnostics.Experimental)] +public sealed class 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. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] + [JsonPropertyName("authorizationUrl")] + public string? AuthorizationUrl { get; set; } +} + +/// 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)] +internal sealed class 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. + [JsonPropertyName("callbackSuccessMessage")] + public string? CallbackSuccessMessage { 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; } + + /// 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; } + + /// 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; } + + /// 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; } + + /// 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; } + + /// 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; } + + /// 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; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Passive MCP OAuth probe result. `authenticated` means the server accepted the probe request while an OAuth-origin access token was attached; it does not prove the server required or independently validated that token. The probe does not make a second unauthenticated request. Failed is an expected probe-domain outcome; JSON-RPC errors are reserved for API-call failures. +/// Polymorphic base type discriminated by status. +[Experimental(Diagnostics.Experimental)] +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "status", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(McpOauthProbeResultNoAuthRequired), "no-auth-required")] +[JsonDerivedType(typeof(McpOauthProbeResultAuthenticated), "authenticated")] +[JsonDerivedType(typeof(McpOauthProbeResultNeedsAuth), "needs-auth")] +[JsonDerivedType(typeof(McpOauthProbeResultFailed), "failed")] +public partial class McpOauthProbeResult +{ + /// The type discriminator. + [JsonPropertyName("status")] + public virtual string Status { get; set; } = string.Empty; +} + + +/// The no-auth-required variant of . +[Experimental(Diagnostics.Experimental)] +public partial class McpOauthProbeResultNoAuthRequired : McpOauthProbeResult +{ + /// + [JsonIgnore] + public override string Status => "no-auth-required"; + + /// HTTP response returned by the server. + [JsonPropertyName("httpResponse")] + public required McpOauthHttpResponse HttpResponse { get; set; } +} + +/// The authenticated variant of . +[Experimental(Diagnostics.Experimental)] +public partial class McpOauthProbeResultAuthenticated : McpOauthProbeResult +{ + /// + [JsonIgnore] + public override string Status => "authenticated"; + + /// HTTP response returned by the server. + [JsonPropertyName("httpResponse")] + public required McpOauthHttpResponse HttpResponse { get; set; } +} + +/// The needs-auth variant of . +[Experimental(Diagnostics.Experimental)] +public partial class McpOauthProbeResultNeedsAuth : McpOauthProbeResult +{ + /// + [JsonIgnore] + public override string Status => "needs-auth"; + + /// HTTP 401 or 403 response returned by the server. + [JsonPropertyName("httpResponse")] + public required McpOauthHttpResponse HttpResponse { get; set; } + + /// Why authentication is needed. + [JsonPropertyName("reason")] + public required McpOauthProbeNeedsAuthReason Reason { get; set; } + + /// Parsed WWW-Authenticate challenge parameters, when present and parseable. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("wwwAuthenticateParams")] + public McpOauthWWWAuthenticateParams? WwwAuthenticateParams { get; set; } +} + +/// The failed variant of . +[Experimental(Diagnostics.Experimental)] +public partial class McpOauthProbeResultFailed : McpOauthProbeResult +{ + /// + [JsonIgnore] + public override string Status => "failed"; + + /// Human-readable probe failure detail. + [JsonPropertyName("error")] + public required string Error { get; set; } + + /// HTTP response returned by the server, when the probe reached the server and captured the complete response. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("httpResponse")] + public McpOauthHttpResponse? HttpResponse { get; set; } +} + +/// Remote MCP server name for a passive OAuth status probe. +[Experimental(Diagnostics.Experimental)] +internal sealed class McpOauthProbeRequest +{ + /// Name of the configured remote 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; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Indicates whether the pending MCP OAuth response was accepted. +[Experimental(Diagnostics.Experimental)] +public sealed class McpOauthRespondResult +{ + /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. + [JsonPropertyName("success")] + public bool Success { get; set; } +} + +/// Pending MCP OAuth request id to respond to. +[Experimental(Diagnostics.Experimental)] +internal sealed class McpOauthRespondRequest +{ + /// 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 pending MCP headers refresh response was accepted. +[Experimental(Diagnostics.Experimental)] +public sealed class McpHeadersHandlePendingHeadersRefreshRequestResult +{ + /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. + [JsonPropertyName("success")] + public bool Success { get; set; } +} + +/// Host response: supply dynamic headers or decline this refresh. +/// Polymorphic base type discriminated by kind. +[Experimental(Diagnostics.Experimental)] +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(McpHeadersHandlePendingHeadersRefreshRequestHeaders), "headers")] +[JsonDerivedType(typeof(McpHeadersHandlePendingHeadersRefreshRequestNone), "none")] +public partial class McpHeadersHandlePendingHeadersRefreshRequest +{ + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { 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. +[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; + + /// Host response: supply dynamic headers or decline this refresh. + [JsonPropertyName("result")] + public McpHeadersHandlePendingHeadersRefreshRequest Result { get => field ??= new(); set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// MCP Apps resource content with URI, optional MIME type, text or base64 blob, and resource metadata. +[Experimental(Diagnostics.Experimental)] +public sealed class McpAppsResourceContent +{ + /// 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; } + + /// 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; +} + +/// Resource contents returned by the MCP server. +[Experimental(Diagnostics.Experimental)] +public sealed class McpAppsReadResourceResult +{ + /// Resource contents returned by the server. + [JsonPropertyName("contents")] + public IList Contents { get => field ??= []; set; } +} + +/// MCP server and resource URI to fetch. +[Experimental(Diagnostics.Experimental)] +internal sealed class McpAppsReadResourceRequest +{ + /// 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; + + /// 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. +[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; + + /// 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; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// MCP server, tool name, and arguments to invoke from an MCP App view. +[Experimental(Diagnostics.Experimental)] +internal sealed class McpAppsCallToolRequest +{ + /// 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. + [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; + + /// 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. +[Experimental(Diagnostics.Experimental)] +internal sealed class McpAppsSetHostContextRequest +{ + /// 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. +[Experimental(Diagnostics.Experimental)] +public sealed class McpAppsHostContextDetails +{ + /// Display modes the host supports. + [JsonPropertyName("availableDisplayModes")] + public IList? AvailableDisplayModes { get; set; } + + /// Current display mode (SEP-1865). + [JsonPropertyName("displayMode")] + public McpAppsHostContextDetailsDisplayMode? 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 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; } +} + +/// Current host context advertised to MCP App guests. +[Experimental(Diagnostics.Experimental)] +public sealed class McpAppsHostContext +{ + /// Current host context. + [JsonPropertyName("context")] + public McpAppsHostContextDetails Context { get => field ??= new(); set; } +} + +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionMcpAppsGetHostContextRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Capability negotiation snapshot. +[Experimental(Diagnostics.Experimental)] +public sealed class McpAppsDiagnoseCapability +{ + /// 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. +[Experimental(Diagnostics.Experimental)] +public sealed class McpAppsDiagnoseServer +{ + /// 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; } + + /// Total tools returned by the server's tools/list. + [JsonPropertyName("toolCount")] + public double ToolCount { get; set; } + + /// Tools whose `_meta.ui` is populated (resourceUri and/or visibility set). + [JsonPropertyName("toolsWithUiMeta")] + public double ToolsWithUiMeta { get; set; } +} + +/// Diagnostic snapshot of MCP Apps wiring for the named server. +[Experimental(Diagnostics.Experimental)] +public sealed class McpAppsDiagnoseResult +{ + /// 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. +[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; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// 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; } + + /// Base64-encoded binary content. + [JsonPropertyName("blob")] + public string? Blob { 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. + [JsonPropertyName("uri")] + public string Uri { get; set; } = string.Empty; +} + +/// 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; } +} + +/// 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; + + /// 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. +[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; } + + /// 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; } +} + +/// 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; } + + /// 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; } +} + +/// 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; } + + /// 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; +} + +/// 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; } + + /// Resources advertised by the server (proxied MCP `resources/list`). + [JsonPropertyName("resources")] + public IList Resources { get => field ??= []; 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; } + + /// 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; +} + +/// 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; } + + /// 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; + + /// Optional human-readable display title. + [JsonPropertyName("title")] + public string? Title { get; set; } + + /// An RFC 6570 URI template for constructing resource URIs. + [JsonPropertyName("uriTemplate")] + public string UriTemplate { get; set; } = string.Empty; +} + +/// 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; } + + /// Resource templates advertised by the server (proxied MCP `resources/templates/list`). + [JsonPropertyName("resourceTemplates")] + public IList ResourceTemplates { get => field ??= []; set; } +} + +/// MCP server whose resource templates to enumerate. +[Experimental(Diagnostics.Experimental)] +internal sealed class McpResourcesListTemplatesRequest +{ + /// 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)] + [JsonPropertyName("serverName")] + public string ServerName { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Session plugin metadata, with name, marketplace, optional version, and enabled state. +[Experimental(Diagnostics.Experimental)] +public sealed class Plugin +{ + /// 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; + + /// Installed version. + [JsonPropertyName("version")] + public string? Version { get; set; } +} + +/// Plugins installed for the session, with their enabled state and version metadata. +[Experimental(Diagnostics.Experimental)] +public sealed class PluginList +{ + /// Installed plugins. + [JsonPropertyName("plugins")] + public IList Plugins { get => field ??= []; set; } +} + +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionPluginsListRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// RPC data type for SessionPluginsReload operations. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionPluginsReloadRequest +{ + /// 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; } +} + +/// RPC data type for SessionPluginsReloadRequestWithSession operations. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionPluginsReloadRequestWithSession +{ + /// 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; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// 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)] +public sealed class 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`. + [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; +} + +/// A snapshot of the provider endpoint the session is currently configured to talk to. +[Experimental(Diagnostics.Experimental)] +public sealed class ProviderEndpoint +{ + /// 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; } + + /// Provider family. Matches the `type` field of a BYOK provider config. + [JsonPropertyName("type")] + public ProviderEndpointType Type { get; set; } + + /// Wire API to be used, when required for the provider type. + [JsonPropertyName("wireApi")] + public ProviderEndpointWireApi? WireApi { get; set; } +} + +/// RPC data type for SessionProviderGetEndpoint operations. +[Experimental(Diagnostics.Experimental)] +public sealed class 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. + [JsonPropertyName("modelId")] + public string? ModelId { get; set; } +} + +/// RPC data type for SessionProviderGetEndpointRequestWithSession operations. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionProviderGetEndpointRequestWithSession +{ + /// 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; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// The selectable model entries synthesized for the models added by this call. +[Experimental(Diagnostics.Experimental)] +public sealed class 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. + [JsonPropertyName("models")] + public IList Models { get => field ??= []; set; } +} + +/// A BYOK model definition referencing a named provider. +[Experimental(Diagnostics.Experimental)] +public sealed class ProviderModelConfig +{ + /// 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; + + /// Maximum context window tokens for the model. + [JsonPropertyName("maxContextWindowTokens")] + public double? MaxContextWindowTokens { get; set; } + + /// Maximum output tokens for the model. + [JsonPropertyName("maxOutputTokens")] + public double? MaxOutputTokens { get; set; } + + /// Maximum prompt/input tokens for the model. + [JsonPropertyName("maxPromptTokens")] + public double? MaxPromptTokens { get; set; } + + /// Well-known base model id used for behavior/capability/config lookup. Defaults to `id`. + [JsonPropertyName("modelId")] + public string? ModelId { get; set; } + + /// Display name for model pickers. Defaults to the provider-qualified selection id (`provider/id`). + [JsonPropertyName("name")] + public string? Name { get; set; } + + /// Name of the configured provider that serves this model. + [JsonPropertyName("provider")] + public string Provider { get; set; } = string.Empty; + + /// The model name sent to the provider API for inference. Defaults to `id`. + [JsonPropertyName("wireModel")] + public string? WireModel { get; set; } +} + +/// 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; } +} + +/// External SDK input for a named custom model provider. Ingested by the native protocol boundary before host dispatch. +[Experimental(Diagnostics.Experimental)] +public sealed class NamedProviderConfig +{ + /// Static API key used to authenticate provider requests. + [JsonPropertyName("apiKey")] + public string? ApiKey { get; set; } + + /// Azure authentication configuration for the provider. + [JsonPropertyName("azure")] + public ProviderConfigAzure? Azure { get; set; } + + /// Base URL for provider API requests. + [JsonPropertyName("baseUrl")] + public string BaseUrl { get; set; } = string.Empty; + + /// Static bearer token used to authenticate provider requests. + [JsonPropertyName("bearerToken")] + public string? BearerToken { get; set; } + + /// Whether the host supplies bearer tokens dynamically. + [JsonPropertyName("hasBearerTokenProvider")] + public bool? HasBearerTokenProvider { get; set; } + + /// Additional HTTP headers included with provider requests. + [JsonPropertyName("headers")] + public IDictionary? Headers { get; set; } + + /// Unique provider name used to qualify model selection IDs. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Transport used to communicate with the provider. + [JsonPropertyName("transport")] + public ProviderConfigTransport? Transport { get; set; } + + /// Provider protocol family. + [JsonPropertyName("type")] + public ProviderConfigType? Type { get; set; } + + /// Wire API used to communicate with the provider. + [JsonPropertyName("wireApi")] + public ProviderConfigWireApi? WireApi { 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. +[Experimental(Diagnostics.Experimental)] +internal sealed class 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. + [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; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Indicates whether the session options patch was applied successfully. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionUpdateOptionsResult +{ + /// Number of hooks loaded from installed plugins, returned when installedPlugins is updated. + [JsonPropertyName("pluginHookCount")] + public long? PluginHookCount { get; set; } + + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } +} + +/// Source descriptor for a `session.options.update` content-exclusion rule, with source name and type. +[Experimental(Diagnostics.Experimental)] +public sealed class OptionsUpdateAdditionalContentExclusionPolicyRuleSource +{ + /// Name of the policy source. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Type of the policy source. + [JsonPropertyName("type")] + public string Type { get; set; } = string.Empty; +} + +/// Single content-exclusion rule supplied to `session.options.update`, with paths, match conditions, and source. +[Experimental(Diagnostics.Experimental)] +public sealed class OptionsUpdateAdditionalContentExclusionPolicyRule +{ + /// Conditions of which at least one must match. + [JsonPropertyName("ifAnyMatch")] + public IList? IfAnyMatch { get; set; } + + /// Conditions none of which may match. + [JsonPropertyName("ifNoneMatch")] + public IList? IfNoneMatch { get; set; } + + /// Path patterns covered by this rule. + [JsonPropertyName("paths")] + public IList Paths { get => field ??= []; 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; } +} + +/// Content-exclusion policy supplied to `session.options.update`, with rules, last-updated data, and scope. +[Experimental(Diagnostics.Experimental)] +public sealed class OptionsUpdateAdditionalContentExclusionPolicy +{ + /// Opaque policy update timestamp supplied by the host. + [JsonPropertyName("last_updated_at")] + public JsonElement LastUpdatedAt { get; set; } + + /// Content-exclusion rules to apply. + [JsonPropertyName("rules")] + public IList Rules { get => field ??= []; set; } + + /// Allowed values for the `OptionsUpdateAdditionalContentExclusionPolicyScope` enumeration. + [JsonPropertyName("scope")] + public OptionsUpdateAdditionalContentExclusionPolicyScope Scope { get; set; } +} + +/// Options scoped to the built-in CAPI (Copilot API) provider. +[Experimental(Diagnostics.Experimental)] +public sealed class CapiSessionOptions +{ + /// Routing preference used when the session model is `auto`. The runtime persists the preference across cold resume. When omitted, the default routing behavior is used. Resuming an already-resident session cannot change its preference. + [JsonPropertyName("autoTier")] + public AutoTier? AutoTier { 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; } +} + +/// Installed plugin record for a session, with marketplace, version, install time, enabled state, cache path, and source. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionInstalledPlugin +{ + /// Path where the plugin is cached locally. + [JsonPropertyName("cache_path")] + public string? CachePath { get; set; } + + /// Whether the plugin is currently enabled. + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } + + /// Installation timestamp (ISO-8601). + [JsonPropertyName("installed_at")] + public string InstalledAt { get; set; } = string.Empty; + + /// Absolute path of the marketplace directory a live plugin was resolved from. Present only on live, never-persisted records — those synthesized at session start for a directory/local marketplace, whose cache_path points at the real plugin directory on disk rather than a copy under the installed-plugins cache. Its presence is what marks a record as live, and no record carrying it is ever written to the persisted installedPlugins key. + [JsonPropertyName("installed_from")] + public string? InstalledFrom { get; set; } + + /// Marketplace the plugin came from (empty string for direct repo installs). + [JsonPropertyName("marketplace")] + public string Marketplace { get; set; } = string.Empty; + + /// Plugin name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Source descriptor for direct repo installs (when marketplace is empty). + [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; } + + /// Installed version, if known. + [JsonPropertyName("version")] + public string? Version { get; set; } +} + +/// Custom model-provider configuration (BYOK). +[Experimental(Diagnostics.Experimental)] +public sealed class ProviderConfig +{ + /// 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; } + + /// Maximum context window tokens for the model. + [JsonPropertyName("maxContextWindowTokens")] + public double? MaxContextWindowTokens { get; set; } + + /// Maximum output tokens for the model. + [JsonPropertyName("maxOutputTokens")] + public double? MaxOutputTokens { get; set; } + + /// Maximum prompt/input tokens for the model. + [JsonPropertyName("maxPromptTokens")] + public double? MaxPromptTokens { get; set; } + + /// Overrides for model capabilities when they cannot be inferred from modelId. + [JsonPropertyName("modelCapabilities")] + public ModelCapabilitiesOverride? ModelCapabilities { 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; } + + /// Provider name used for model and telemetry attribution. + [JsonPropertyName("providerName")] + public string? ProviderName { get; set; } + + /// 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; } + + /// 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; } +} + +/// Credential-injection capability flags applied while the sandbox is enabled. For the same capability independent of sandboxing, and matched to the credential's GitHub host, see `shell.credentials`; the two are additive. +[Experimental(Diagnostics.Experimental)] +public sealed class 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). + [JsonPropertyName("gh")] + public bool? Gh { 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; } +} + +/// 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; } +} + +/// Platform-specific experimental policy fields. +[Experimental(Diagnostics.Experimental)] +public sealed class SandboxConfigUserPolicyExperimental +{ + /// macOS seatbelt experimental options. + [JsonPropertyName("seatbelt")] + public SandboxConfigUserPolicyExperimentalSeatbelt? Seatbelt { get; set; } +} + +/// Filesystem rules to merge into the base policy. +[Experimental(Diagnostics.Experimental)] +public sealed class SandboxConfigUserPolicyFilesystem +{ + /// Whether to clear the policy when the session exits. + [JsonPropertyName("clearPolicyOnExit")] + public bool? ClearPolicyOnExit { get; set; } + + /// Paths explicitly denied. + [JsonPropertyName("deniedPaths")] + public IList? DeniedPaths { get; set; } + + /// Paths granted read-only access. + [JsonPropertyName("readonlyPaths")] + public IList? ReadonlyPaths { get; set; } + + /// Paths granted read/write access. + [JsonPropertyName("readwritePaths")] + public IList? ReadwritePaths { get; set; } +} + +/// HTTP proxy configuration for sandboxed traffic. +[Experimental(Diagnostics.Experimental)] +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; } + + /// Proxy URL (e.g. http://proxy.example.com:8080). The port is optional and defaults to the scheme's standard port when omitted; an explicit port must be between 1 and 65535. 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 proxy 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; } +} + +/// Network rules to merge into the base policy. +[Experimental(Diagnostics.Experimental)] +public sealed class SandboxConfigUserPolicyNetwork +{ + /// 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; } + + /// HTTP proxy for sandboxed process traffic. Linux restricts egress to the proxy endpoint, requires that endpoint to be reachable over IPv4 (the [::] dual-stack wildcard is accepted and routed through the IPv4 gateway), and does not support proxy credentials. macOS relies on applications honoring proxy environment variables. Windows also configures a per-AppContainer WinHTTP proxy, but enforcement depends on the application's networking stack. Configure supported credentials in the separate `username` and `password` fields. A credential-free http:// loopback URL uses the localhost proxy form, while an https:// or authenticated loopback URL uses the URL form. + [JsonPropertyName("proxy")] + public SandboxConfigUserPolicyNetworkProxy? Proxy { get; set; } +} + +/// macOS seatbelt-specific options. +[Experimental(Diagnostics.Experimental)] +public sealed class SandboxConfigUserPolicySeatbelt +{ + /// Whether the macOS seatbelt profile may access the keychain. + [JsonPropertyName("keychainAccess")] + public bool? KeychainAccess { get; set; } +} + +/// User-managed sandbox policy fragment merged into the auto-discovered base policy. +[Experimental(Diagnostics.Experimental)] +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; } + + /// Filesystem rules to merge into the base policy. + [JsonPropertyName("filesystem")] + public SandboxConfigUserPolicyFilesystem? Filesystem { 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; } +} + +/// Resolved sandbox configuration. +[Experimental(Diagnostics.Experimental)] +public sealed class SandboxConfig +{ + /// Whether to auto-add the current working directory to readwritePaths. Default: true. + [JsonPropertyName("addCurrentWorkingDirectory")] + public bool? AddCurrentWorkingDirectory { get; set; } + + /// Whether to auto-grant read access to tool directories discovered on PATH and in toolchain environment variables (GOROOT, JAVA_HOME, VIRTUAL_ENV, and similar), and to common developer-tool caches, config, and toolchains. Writable grants cover scratch caches, the Unix GitHub CLI cache, and Cargo's registry, git store, and lock/tracker files. A relocated CARGO_HOME gets the same narrow split: registry and git are read-write; bin is read-only; the home root, config.toml, and credentials.toml stay ungranted. Set to false to disable every grant listed above; user-installed toolchains and caches then need explicit userPolicy.filesystem readonlyPaths and readwritePaths entries. The working directory (see addCurrentWorkingDirectory), temporary storage, session log paths, and system locations follow their own rules and stay granted. Default: true (enabled by default; set to false to opt out). + [JsonPropertyName("allowDevToolAccess")] + public bool? AllowDevToolAccess { get; set; } + + /// Credential-injection capability flags. + [JsonPropertyName("auth")] + public SandboxConfigAuth? Auth { 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; } +} + +/// +/// Command-scoped GitHub credential injection for the shell commands an agent runs. +/// +/// Each channel is opt-in and independent, and injection is scoped to the individual command +/// spawn: the credential is resolved from the session's *current* authentication at every spawn +/// and reaches only spawns whose script actually invokes `git` or `gh`. Because nothing is +/// retained between spawns, replacing the session credential (`session.gitHubAuth.setCredentials`) +/// changes what the next spawned command presents — which seeding a credential into the runtime +/// process's own environment cannot do, since a child's environment is fixed at `exec`. +/// +/// The credential is matched to the host it authenticates to, so a github.com credential is never +/// presented to a GitHub Enterprise host and vice versa. Where a channel cannot express that +/// boundary it injects nothing rather than crossing it -- see `gh` below. +/// +/// This is independent of `sandboxConfig`: it is a decision about which identity the agent +/// presents, not about what the agent may touch, and it works on every platform whether or not +/// an OS sandboxing backend is available. `sandboxConfig.auth` remains the sandbox-scoped +/// spelling and is additive with this one. +/// +[Experimental(Diagnostics.Experimental)] +public sealed class ShellCredentials +{ + /// + /// Whether to authenticate the agent's `gh` commands as the session's GitHub credential, by + /// exporting `GH_TOKEN` to a spawn that runs `gh`. Any inherited `gh` credential is removed from + /// spawns that do not, so the credential stays command-scoped. + /// + /// Applies to a github.com credential only. `gh` picks its credential variable from the host a + /// command targets rather than the one the credential belongs to, and the command can choose that + /// target, so `GH_ENTERPRISE_TOKEN` would offer a single-tenant enterprise credential to every + /// other enterprise host. A session whose credential is enterprise-scoped therefore runs `gh` + /// unauthenticated; its `git` commands are unaffected, because `http.<host>.extraheader` is scoped + /// to one host by construction. Default: false (opt-in). + /// + [JsonPropertyName("gh")] + public bool? Gh { get; set; } + + /// + /// Whether to authenticate the agent's `git` commands as the session's GitHub credential, by + /// injecting an `http.<host>.extraheader` (plus `insteadOf` rewrites so SSH-spelled remotes for + /// that host use the authenticated HTTPS transport). Applied only to a spawn that runs a + /// remote-contacting `git` subcommand. Default: false (opt-in). + /// + [JsonPropertyName("git")] + public bool? Git { get; set; } +} + +/// A host-provided script sourced before each built-in shell command when its shell target matches the active shell. +[Experimental(Diagnostics.Experimental)] +public sealed class ShellInitScript +{ + /// Path to the script to source. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; + + /// Built-in shell that may source this script. + [JsonPropertyName("shell")] + public ShellInitScriptShell Shell { get; set; } +} + +/// Per-session settings for built-in shell tools. +[Experimental(Diagnostics.Experimental)] +public sealed class ShellOptions +{ + /// Command-scoped GitHub credential injection for shell commands. + [JsonPropertyName("credentials")] + public ShellCredentials? Credentials { get; set; } + + /// Controls automatic non-interactive profile loading where supported. Explicit initScripts are unaffected. + [JsonPropertyName("initProfile")] + public ShellInitProfile? InitProfile { 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; } + + /// + /// 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; } +} + +/// Patch of mutable session options to apply to the running session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionUpdateOptionsParams { /// Additional content-exclusion policies to merge into the session's policy set. [Experimental(Diagnostics.Experimental)] [JsonPropertyName("additionalContentExclusionPolicies")] - public IList? AdditionalContentExclusionPolicies { get; set; } + public IList? AdditionalContentExclusionPolicies { get; set; } + + /// Runtime context discriminator (e.g., `cli`, `actions`). + [JsonPropertyName("agentContext")] + public string? AgentContext { 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; } + + /// Whether to disable the `ask_user` tool (encourages autonomous behavior). + [JsonPropertyName("askUserDisabled")] + public bool? AskUserDisabled { get; set; } + + /// 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; } + + /// Identifier of the client driving the session. + [JsonPropertyName("clientName")] + public string? ClientName { get; set; } + + /// Whether to include the `Co-authored-by` trailer in commit messages. + [JsonPropertyName("coauthorEnabled")] + public bool? CoauthorEnabled { 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; } + + /// Whether to allow auto-mode continuation across turns. + [JsonPropertyName("continueOnAutoMode")] + public bool? ContinueOnAutoMode { get; set; } + + /// Override URL for the Copilot API endpoint. + [JsonPropertyName("copilotUrl")] + public string? CopilotUrl { get; set; } + + /// Whether to default custom agents to local-only execution. + [JsonPropertyName("customAgentsLocalOnly")] + public bool? CustomAgentsLocalOnly { get; set; } + + /// Instruction source IDs to exclude from the system prompt. + [JsonPropertyName("disabledInstructionSources")] + public IList? DisabledInstructionSources { get; set; } + + /// Skill IDs that should be excluded from this session. + [JsonPropertyName("disabledSkills")] + public IList? DisabledSkills { 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; } + + /// Whether to enable host git operations (context resolution, child repo scanning, git info in system prompt). + [JsonPropertyName("enableHostGitOperations")] + public bool? EnableHostGitOperations { 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; } + + /// Whether to surface reasoning-summary events from the model. + [JsonPropertyName("enableReasoningSummaries")] + public bool? EnableReasoningSummaries { get; set; } + + /// Whether shell-script safety heuristics are enabled. + [JsonPropertyName("enableScriptSafety")] + public bool? EnableScriptSafety { get; set; } + + /// Whether to enable cross-session store writes and reads. + [JsonPropertyName("enableSessionStore")] + public bool? EnableSessionStore { get; set; } + + /// Whether skill loading is enabled. Explicit false disables every source, including a bound SDK provider; changing the value invalidates the loaded skill snapshot. When omitted, creation falls back to enableConfigDiscovery unless an SDK skill provider is registered. + [JsonPropertyName("enableSkills")] + public bool? EnableSkills { get; set; } + + /// Whether to stream model responses. + [JsonPropertyName("enableStreaming")] + public bool? EnableStreaming { 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; } + + /// 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; } + + /// Whether subagent callback events should be forwarded into the session event log sink. + [JsonPropertyName("eventsLogIncludesSubagents")] + public bool? EventsLogIncludesSubagents { 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; } + + /// Denylist of tool names for this session. + [JsonPropertyName("excludedTools")] + public IList? ExcludedTools { get; set; } + + /// Map of feature-flag IDs to their boolean enabled state. + [JsonPropertyName("featureFlags")] + public IDictionary? FeatureFlags { 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; } + + /// Built-in skill names to include in this session. When specified, only these runtime-bundled skills are available. Skills from other sources with the same name remain available. Set to null to remove the allowlist restriction. + [JsonPropertyName("includedBuiltinSkills")] + public IList? IncludedBuiltinSkills { 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; } + + /// 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; } + + /// Origin of the sandbox choice. The runtime uses this only for internal telemetry provenance; managed policy is derived independently. + [JsonInclude] + [JsonPropertyName("sandboxConfigSource")] + internal SandboxConfigSource? SandboxConfigSource { 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; + + /// 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; } +} + +/// Parameters for (re)loading the merged LSP configuration set. +[Experimental(Diagnostics.Experimental)] +internal sealed class LspInitializeRequest +{ + /// Force re-initialization even when LSP configs were already loaded for the working directory. + [JsonPropertyName("force")] + public bool? Force { get; set; } + + /// 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; } +} + +/// Discovered extension metadata, including source-qualified ID, name, discovery source, status, and optional process ID. +[Experimental(Diagnostics.Experimental)] +public sealed class Extension +{ + /// 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; } +} + +/// Extensions discovered for the session, with their current status. +[Experimental(Diagnostics.Experimental)] +public sealed class ExtensionList +{ + /// 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; +} + +/// Source-qualified extension identifier to enable for the session. +[Experimental(Diagnostics.Experimental)] +internal sealed class ExtensionsEnableRequest +{ + /// 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; +} + +/// Source-qualified extension identifier to disable for the session. +[Experimental(Diagnostics.Experimental)] +internal sealed class ExtensionsDisableRequest +{ + /// 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; +} + +/// 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. +[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 +{ + /// The type discriminator. + [JsonPropertyName("type")] + public virtual string Type { 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; } + + /// Start line number (1-based). + [JsonPropertyName("start")] + public long Start { get; set; } +} + +/// File attachment. +/// The file variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PushAttachmentFile : PushAttachment +{ + /// + [JsonIgnore] + public override string Type => "file"; + + /// User-facing display name for the attachment. + [JsonPropertyName("displayName")] + public required string DisplayName { get; set; } + + /// 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; } + + /// Absolute file path. + [JsonPropertyName("path")] + public required string Path { get; set; } +} + +/// Directory attachment. +/// The directory variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PushAttachmentDirectory : PushAttachment +{ + /// + [JsonIgnore] + public override string Type => "directory"; + + /// 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; } +} + +/// End position of the selection. +[Experimental(Diagnostics.Experimental)] +public sealed class PushAttachmentSelectionDetailsEnd +{ + /// 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; } +} + +/// Start position of the selection. +[Experimental(Diagnostics.Experimental)] +public sealed class PushAttachmentSelectionDetailsStart +{ + /// Start character offset within the line (0-based). + [JsonPropertyName("character")] + public long Character { get; set; } + + /// Start line number (0-based). + [JsonPropertyName("line")] + public long Line { get; set; } +} + +/// Position range of the selection within the file. +[Experimental(Diagnostics.Experimental)] +public sealed class PushAttachmentSelectionDetails +{ + /// 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; } +} + +/// Code selection attachment from an editor. +/// The selection variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PushAttachmentSelection : PushAttachment +{ + /// + [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; } + + /// 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; } +} + +/// 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"; + + /// 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; } +} + +/// Pointer to a GitHub repository. +[Experimental(Diagnostics.Experimental)] +public sealed class PushGitHubRepoRef +{ + /// Numeric GitHub repository id. + [JsonPropertyName("id")] + public long? Id { 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 . +[Experimental(Diagnostics.Experimental)] +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; } +} + +/// 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"; + + /// Human-readable release name. + [JsonPropertyName("name")] + public required string Name { get; set; } + + /// 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; } +} + +/// Pointer to a GitHub Actions job. +/// The github_actions_job variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PushAttachmentGitHubActionsJob : PushAttachment +{ + /// + [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; } + + /// 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; } +} + +/// Pointer to a GitHub repository. +/// The github_repository variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PushAttachmentGitHubRepository : PushAttachment +{ + /// + [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; } + + /// URL to the repository on GitHub. + [JsonPropertyName("url")] + public required string Url { get; set; } +} + +/// One side of a file diff (head or base). +[Experimental(Diagnostics.Experimental)] +public sealed class PushAttachmentGitHubFileDiffSide +{ + /// 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; + + /// Repository the file lives in. + [JsonPropertyName("repo")] + public PushGitHubRepoRef Repo { get => field ??= new(); set; } +} + +/// 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 partial class PushAttachmentGitHubFileDiff : PushAttachment +{ + /// + [JsonIgnore] + public override string Type => "github_file_diff"; + + /// File location on the base side of the diff. Absent for additions. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("base")] + public PushAttachmentGitHubFileDiffSide? Base { 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; } + + /// URL to the diff on GitHub (e.g., a commit, compare, or PR-file URL). + [JsonPropertyName("url")] + public required string Url { get; set; } +} + +/// One side of a tree comparison (head or base). +[Experimental(Diagnostics.Experimental)] +public sealed class PushAttachmentGitHubTreeComparisonSide +{ + /// Repository the revision belongs to. + [JsonPropertyName("repo")] + public PushGitHubRepoRef Repo { get => field ??= new(); set; } + + /// Git revision (branch, tag, or commit SHA). + [JsonPropertyName("revision")] + public string Revision { get; set; } = string.Empty; +} + +/// Pointer to a comparison between two git revisions. +/// The github_tree_comparison variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PushAttachmentGitHubTreeComparison : PushAttachment +{ + /// + [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; } +} + +/// Generic GitHub URL reference. +/// The github_url variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PushAttachmentGitHubUrl : PushAttachment +{ + /// + [JsonIgnore] + public override string Type => "github_url"; + + /// URL to the GitHub resource. + [JsonPropertyName("url")] + public required string Url { 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"; + + /// Repository-relative path to the file. + [JsonPropertyName("path")] + public required string Path { get; 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 file on GitHub. + [JsonPropertyName("url")] + public required string Url { get; set; } +} + +/// Pointer to a line range inside a file in a GitHub repository. +/// The github_snippet variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PushAttachmentGitHubSnippet : PushAttachment +{ + /// + [JsonIgnore] + public override string Type => "github_snippet"; + + /// Line range the snippet covers. + [JsonPropertyName("lineRange")] + public required PushAttachmentFileLineRange LineRange { get; set; } + + /// Repository-relative path to the file. + [JsonPropertyName("path")] + public required string Path { get; 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; } +} + +/// 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"; + + /// Base64-encoded content. + [Base64String] + [JsonPropertyName("data")] + public required string Data { 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; } +} + +/// 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"; + + /// Caller-supplied JSON payload (required, may be null but not undefined). + [JsonPropertyName("payload")] + public required JsonElement Payload { 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; } +} + +/// Parameters for session.extensions.sendAttachmentsToMessage. +[Experimental(Diagnostics.Experimental)] +internal sealed class SendAttachmentsToMessageParams +{ + /// 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; } + + /// 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; +} + +/// A tool name and arguments to execute through the session's native invocation pipeline. +[Experimental(Diagnostics.Experimental)] +internal sealed class ToolsExecuteRequest +{ + /// Arguments supplied to the tool. + [JsonPropertyName("arguments")] + public JsonElement Arguments { get; set; } + + /// Name of the currently offered tool to execute. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + /// Optional identifier used to correlate this invocation with its tool call. + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } +} + +/// Custom grammar input format accepted by a built-in tool. +[Experimental(Diagnostics.Experimental)] +public sealed class BuiltinToolFormat +{ + /// Grammar definition accepted by the tool. + [JsonPropertyName("definition")] + public string Definition { get; set; } = string.Empty; + + /// Grammar syntax used by the format definition. + [JsonPropertyName("syntax")] + public string Syntax { get; set; } = string.Empty; + + /// Custom input-format discriminator. + [JsonPropertyName("type")] + public BuiltinToolFormatType Type { get; set; } +} + +/// JSON Schema object accepted by a built-in tool. +[Experimental(Diagnostics.Experimental)] +public sealed class BuiltinToolInputSchema +{ + /// Root type of the tool input schema. + [JsonPropertyName("type")] + public BuiltinToolInputSchemaType Type { get; set; } +} + +/// Rust-owned metadata and input schema for a built-in tool. +[Experimental(Diagnostics.Experimental)] +public sealed class BuiltinToolDescriptor +{ + /// Model-facing description of the tool's behavior. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; + + /// Optional custom input format used instead of a JSON Schema. + [JsonPropertyName("format")] + public BuiltinToolFormat? Format { get; set; } + + /// Whether the tool provides a specialized intention summary. + [JsonPropertyName("hasSummariseIntention")] + public bool HasSummariseIntention { get; set; } + + /// JSON Schema for the tool input, or null when the tool uses a custom format. + [JsonPropertyName("inputSchema")] + public BuiltinToolInputSchema? InputSchema { get; set; } + + /// Optional supplemental usage instructions for the tool. + [JsonPropertyName("instructions")] + public string? Instructions { get; set; } + + /// Whether the tool executes commands in a terminal. + [JsonPropertyName("isTerminal")] + public bool IsTerminal { get; set; } + + /// Stable name used to invoke the built-in tool. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Policy describing which tool metadata may be recorded without obfuscation. + [JsonPropertyName("safeForTelemetry")] + public JsonElement SafeForTelemetry { get; set; } + + /// Optional human-readable title for the tool. + [JsonPropertyName("title")] + public string? Title { get; set; } + + /// Optional tool category discriminator. + [JsonPropertyName("type")] + public string? Type { get; set; } +} + +/// Rust-owned built-in tool descriptors for the session. +[Experimental(Diagnostics.Experimental)] +public sealed class ToolsGetBuiltinDescriptorsResult +{ + /// Built-in tool descriptors materialized for the session. + [JsonPropertyName("tools")] + public IList Tools { get => field ??= []; set; } +} + +/// Shell-specific names and description lines used to materialize built-in shell tool descriptors. +[Experimental(Diagnostics.Experimental)] +public sealed class ToolsShellDescriptorConfig +{ + /// Additional model-facing shell description lines. + [JsonPropertyName("descriptionLines")] + public IList DescriptionLines { get => field ??= []; set; } + + /// Human-readable shell name. + [JsonPropertyName("displayName")] + public string DisplayName { get; set; } = string.Empty; + + /// Tool name used to list active shells. + [JsonPropertyName("listShellsToolName")] + public string ListShellsToolName { get; set; } = string.Empty; + + /// Tool name used to read shell output. + [JsonPropertyName("readShellToolName")] + public string ReadShellToolName { get; set; } = string.Empty; + + /// Tool name used to start shell commands. + [JsonPropertyName("shellToolName")] + public string ShellToolName { get; set; } = string.Empty; + + /// Stable shell type identifier. + [JsonPropertyName("shellType")] + public string ShellType { get; set; } = string.Empty; + + /// Tool name used to stop shell commands. + [JsonPropertyName("stopShellToolName")] + public string StopShellToolName { get; set; } = string.Empty; +} + +/// Options controlling how Rust-owned built-in tool descriptors are materialized. +[Experimental(Diagnostics.Experimental)] +internal sealed class ToolsGetBuiltinDescriptorsRequest +{ + /// Whether background task completion notifications are enabled. + [JsonPropertyName("backgroundTaskNotificationsEnabled")] + public bool? BackgroundTaskNotificationsEnabled { get; set; } + + /// Whether tool descriptors should include authoring metadata. + [JsonPropertyName("includeAuthor")] + public bool? IncludeAuthor { get; set; } + + /// Whether descriptors should favor fewer user-intervention prompts. + [JsonPropertyName("reduceUserIntervention")] + public bool? ReduceUserIntervention { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + /// Shell-specific names and description lines for shell tools. + [JsonPropertyName("shellConfig")] + public ToolsShellDescriptorConfig? ShellConfig { get; set; } + + /// Whether the configured shell supports PowerShell 7 syntax. + [JsonPropertyName("shellSupportsPowerShell7Syntax")] + public bool? ShellSupportsPowerShell7Syntax { get; set; } + + /// Default shell timeout in milliseconds. + [JsonPropertyName("shellTimeoutMs")] + public double? ShellTimeoutMs { get; set; } + + /// Whether semantic skill lookup is available. + [JsonPropertyName("skillEmbeddingEnabled")] + public bool? SkillEmbeddingEnabled { get; set; } +} + +/// Task completion notification with summary from the agent. +[Experimental(Diagnostics.Experimental)] +public sealed class TaskCompleteData +{ + /// Active autopilot objective ID evaluated by the completion reviewer. + [JsonPropertyName("objectiveId")] + public long? ObjectiveId { get; set; } + + /// Semantic completion decision. Absent on legacy events and invalid tool calls. + [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. + [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. + [JsonPropertyName("success")] + public bool? Success { get; set; } + + /// Summary of the completed task, provided by the agent. + [JsonPropertyName("summary")] + public string? Summary { get; set; } +} + +/// Binary result returned by a tool for the model. +[Experimental(Diagnostics.Experimental)] +public sealed class ExternalToolTextResultForLlmBinaryResultsForLlm +{ + /// Base64-encoded binary data. + [Base64String] + [JsonPropertyName("data")] + public string Data { get; set; } = string.Empty; + + /// Human-readable description of the binary data. + [JsonPropertyName("description")] + public string? Description { get; set; } + + /// Optional metadata from the producing tool. + [JsonPropertyName("metadata")] + public IDictionary? Metadata { get; set; } + + /// MIME type of the binary data. + [JsonPropertyName("mimeType")] + public string MimeType { get; set; } = string.Empty; + + /// Binary result type discriminator. Use "image" for images and "resource" for other binary data. + [JsonPropertyName("type")] + public ExternalToolTextResultForLlmBinaryResultsForLlmType Type { get; set; } +} + +/// A content block within a tool result, which may be text, terminal output, image, audio, or a resource. +/// Polymorphic base type discriminated by type. +[Experimental(Diagnostics.Experimental)] +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "type", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(ExternalToolTextResultForLlmContentText), "text")] +[JsonDerivedType(typeof(ExternalToolTextResultForLlmContentTerminal), "terminal")] +[JsonDerivedType(typeof(ExternalToolTextResultForLlmContentShellExit), "shell_exit")] +[JsonDerivedType(typeof(ExternalToolTextResultForLlmContentImage), "image")] +[JsonDerivedType(typeof(ExternalToolTextResultForLlmContentAudio), "audio")] +[JsonDerivedType(typeof(ExternalToolTextResultForLlmContentResourceLink), "resource_link")] +[JsonDerivedType(typeof(ExternalToolTextResultForLlmContentResource), "resource")] +public partial class ExternalToolTextResultForLlmContent +{ + /// The type discriminator. + [JsonPropertyName("type")] + public virtual string Type { get; set; } = string.Empty; +} + + +/// Plain text content block. +/// The text variant of . +[Experimental(Diagnostics.Experimental)] +public partial class ExternalToolTextResultForLlmContentText : ExternalToolTextResultForLlmContent +{ + /// + [JsonIgnore] + public override string Type => "text"; + + /// The text content. + [JsonPropertyName("text")] + public required string Text { get; set; } +} + +/// Terminal/shell output content block with optional exit code and working directory. +/// The terminal variant of . +[Experimental(Diagnostics.Experimental)] +public partial class ExternalToolTextResultForLlmContentTerminal : ExternalToolTextResultForLlmContent +{ + /// + [JsonIgnore] + public override string Type => "terminal"; + + /// Working directory where the command was executed. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("cwd")] + public string? Cwd { get; set; } + + /// Process exit code, if the command has completed. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("exitCode")] + public long? ExitCode { get; set; } + + /// Terminal/shell output text. + [JsonPropertyName("text")] + public required string Text { get; set; } +} + +/// Shell command exit metadata with optional output preview. +/// The shell_exit variant of . +[Experimental(Diagnostics.Experimental)] +public partial class ExternalToolTextResultForLlmContentShellExit : ExternalToolTextResultForLlmContent +{ + /// + [JsonIgnore] + public override string Type => "shell_exit"; + + /// Working directory where the shell command was executed. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("cwd")] + public string? Cwd { get; set; } + + /// Exit code from the completed shell command. + [JsonPropertyName("exitCode")] + public required long ExitCode { get; set; } + + /// Path reported in the shell session's filesystem namespace when shell output exceeded the configured large-output threshold. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("outputFilePath")] + public string? OutputFilePath { get; set; } + + /// Output associated with this shell command, if available. May be partial, truncated, or a preview; not guaranteed to be full output. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("outputPreview")] + public string? OutputPreview { get; set; } + + /// Whether outputPreview is known to be incomplete or truncated. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("outputTruncated")] + public bool? OutputTruncated { get; set; } + + /// Shell id, as assigned by Copilot runtime. + [JsonPropertyName("shellId")] + public required string ShellId { get; set; } +} + +/// Image content block with base64-encoded data. +/// The image variant of . +[Experimental(Diagnostics.Experimental)] +public partial class ExternalToolTextResultForLlmContentImage : ExternalToolTextResultForLlmContent +{ + /// + [JsonIgnore] + public override string Type => "image"; + + /// Base64-encoded image data. + [Base64String] + [JsonPropertyName("data")] + public required string Data { get; set; } + + /// MIME type of the image (e.g., image/png, image/jpeg). + [JsonPropertyName("mimeType")] + public required string MimeType { get; set; } +} + +/// Audio content block with base64-encoded data. +/// The audio variant of . +[Experimental(Diagnostics.Experimental)] +public partial class ExternalToolTextResultForLlmContentAudio : ExternalToolTextResultForLlmContent +{ + /// + [JsonIgnore] + public override string Type => "audio"; + + /// Base64-encoded audio data. + [Base64String] + [JsonPropertyName("data")] + public required string Data { get; set; } + + /// MIME type of the audio (e.g., audio/wav, audio/mpeg). + [JsonPropertyName("mimeType")] + public required string MimeType { get; set; } +} + +/// Icon image for a resource. +[Experimental(Diagnostics.Experimental)] +public sealed class ExternalToolTextResultForLlmContentResourceLinkIcon +{ + /// MIME type of the icon image. + [JsonPropertyName("mimeType")] + public string? MimeType { get; set; } + + /// Available icon sizes (e.g., ['16x16', '32x32']). + [JsonPropertyName("sizes")] + public IList? Sizes { get; set; } + + /// URL or path to the icon image. + [JsonPropertyName("src")] + public string Src { get; set; } = string.Empty; + + /// Theme variant this icon is intended for. + [JsonPropertyName("theme")] + public ExternalToolTextResultForLlmContentResourceLinkIconTheme? Theme { get; set; } +} + +/// Resource link content block referencing an external resource. +/// The resource_link variant of . +[Experimental(Diagnostics.Experimental)] +public partial class ExternalToolTextResultForLlmContentResourceLink : ExternalToolTextResultForLlmContent +{ + /// + [JsonIgnore] + public override string Type => "resource_link"; + + /// Human-readable description of the resource. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("description")] + public string? Description { get; set; } + + /// Icons associated with this resource. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("icons")] + public IList? Icons { get; set; } + + /// MIME type of the resource content. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("mimeType")] + public string? MimeType { get; set; } + + /// Resource name identifier. + [JsonPropertyName("name")] + public required string Name { get; set; } + + /// Size of the resource in bytes. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("size")] + public long? Size { get; set; } + + /// Human-readable display title for the resource. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("title")] + public string? Title { get; set; } + + /// URI identifying the resource. + [JsonPropertyName("uri")] + public required string Uri { get; set; } +} + +/// Embedded resource content block with inline text or binary data. +/// The resource variant of . +[Experimental(Diagnostics.Experimental)] +public partial class ExternalToolTextResultForLlmContentResource : ExternalToolTextResultForLlmContent +{ + /// + [JsonIgnore] + public override string Type => "resource"; + + /// The embedded resource contents, either text or base64-encoded binary. + [JsonPropertyName("resource")] + public required JsonElement Resource { get; set; } +} + +/// A message injected by a tool result. +[Experimental(Diagnostics.Experimental)] +public sealed class ToolResultNewMessage +{ + /// Message content to inject after the tool result. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; + + /// Source attributed to the injected message. + [JsonPropertyName("source")] + public string Source { get; set; } = string.Empty; +} + +/// RPC data type for TaskCompletionDecision operations. +[Experimental(Diagnostics.Experimental)] +public sealed class TaskCompletionDecision +{ + /// Objective eligibility token captured when the decision was evaluated. + [JsonPropertyName("completionEligibilityToken")] + public long? CompletionEligibilityToken { get; set; } + + /// Whether completion was accepted after the reviewer-rejection budget was exhausted. + [JsonPropertyName("completionRejectionBudgetExhausted")] + public bool? CompletionRejectionBudgetExhausted { get; set; } + + /// Active autopilot objective evaluated by the completion reviewer. + [JsonPropertyName("objectiveId")] + public long? ObjectiveId { get; set; } + + /// Semantic result of evaluating the task completion request. + [JsonPropertyName("outcome")] + public TaskCompletionOutcome Outcome { get; set; } + + /// Rationale for the completion decision, when one is available. + [JsonPropertyName("reason")] + public string? Reason { get; set; } + + /// Whether the rationale was derived from completion-reviewer output. + [JsonPropertyName("reviewerDerived")] + public bool? ReviewerDerived { get; set; } + + /// Information-flow metadata captured from the completion reviewer. + [JsonPropertyName("reviewerResultMeta")] + public JsonElement? ReviewerResultMeta { get; set; } +} + +/// Expanded canonical result returned by a session tool. +[Experimental(Diagnostics.Experimental)] +public sealed class ToolResultExpanded +{ + /// Base64-encoded binary results returned to the model. + [JsonPropertyName("binaryResultsForLlm")] + public IList? BinaryResultsForLlm { get; set; } + + /// Sources returned by the tool that the model may cite. + [JsonPropertyName("citableSources")] + public IList? CitableSources { get; set; } + + /// Structured content blocks returned to the model. + [JsonPropertyName("contents")] + public IList? Contents { get; set; } + + /// Error message for an unsuccessful execution. + [JsonPropertyName("error")] + public string? Error { get; set; } + + /// Metadata propagated with the tool result, including information-flow labels. + [JsonPropertyName("mcpMeta")] + public IDictionary? McpMeta { get; set; } + + /// Messages to inject after the tool result. + [JsonPropertyName("newMessages")] + public IList? NewMessages { get; set; } + + /// Whether post-tool-use failure hooks have already processed this result. + [JsonPropertyName("postToolUseFailureHooksProcessed")] + public bool? PostToolUseFailureHooksProcessed { get; set; } + + /// Execution outcome classification. + [JsonPropertyName("resultType")] + public ToolResultType ResultType { get; set; } + + /// Detailed log content available for session display. + [JsonPropertyName("sessionLog")] + public string? SessionLog { get; set; } + + /// Skill invocation metadata produced by the tool. + [JsonPropertyName("skillInvocation")] + public JsonElement? SkillInvocation { get; set; } + + /// Whether large-output post-processing should be skipped. + [JsonPropertyName("skipLargeOutputProcessing")] + public bool? SkipLargeOutputProcessing { get; set; } + + /// Structured result content in addition to the model-facing text. + [JsonPropertyName("structuredContent")] + public JsonElement? StructuredContent { get; set; } + + /// Completion-review decision produced by the task-completion tool. + [JsonPropertyName("taskCompletionDecision")] + public TaskCompletionDecision? TaskCompletionDecision { get; set; } + + /// Text result returned to the model. + [JsonPropertyName("textResultForLlm")] + public string TextResultForLlm { get; set; } = string.Empty; + + /// Deferred tool names made available by this result. + [JsonPropertyName("toolReferences")] + public IList? ToolReferences { get; set; } + + /// Tool-specific telemetry payload. + [JsonPropertyName("toolTelemetry")] + public JsonElement? ToolTelemetry { get; set; } + + /// Optional UI resource produced by the tool. + [JsonPropertyName("uiResource")] + public JsonElement? UiResource { get; set; } +} + +/// Task-completion tool arguments and final result used to build a label-safe session event payload. +[Experimental(Diagnostics.Experimental)] +internal sealed class ToolsTaskCompleteEventDataRequest +{ + /// Final expanded result returned by the task_complete tool. + [JsonPropertyName("finalResult")] + public ToolResultExpanded FinalResult { get => field ??= new(); set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + /// Arguments supplied to the completed task_complete tool call. + [JsonPropertyName("toolArgs")] + public JsonElement ToolArgs { get; set; } +} + +/// Indicates whether the external tool call result was handled successfully. +[Experimental(Diagnostics.Experimental)] +public sealed class HandlePendingToolCallResult +{ + /// Whether the tool call result was handled successfully. + [JsonPropertyName("success")] + public bool Success { get; set; } +} + +/// Pending external tool call request ID, with the tool result or an error describing why it failed. +[Experimental(Diagnostics.Experimental)] +internal sealed class HandlePendingToolCallRequest +{ + /// Error message if the tool call failed. + [JsonPropertyName("error")] + public string? Error { get; set; } + + /// Request ID of the pending tool call. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; + + /// 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; +} + +/// 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 ToolsInitializeAndValidateResult +{ +} + +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionToolsInitializeAndValidateRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Lightweight metadata for a currently initialized session tool. +[Experimental(Diagnostics.Experimental)] +public sealed class CurrentToolMetadata +{ + /// 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; } + + /// 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; } +} + +/// Current lightweight tool metadata snapshot for the session. +[Experimental(Diagnostics.Experimental)] +public sealed class ToolsGetCurrentMetadataResult +{ + /// 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; +} + +/// Empty result after replacing the calling connection's externally implemented tools. +[Experimental(Diagnostics.Experimental)] +public sealed class ToolsSetResult +{ +} + +/// Serializable definition of a caller-implemented tool whose execution is handled over the SDK connection. +[Experimental(Diagnostics.Experimental)] +public sealed class ProtocolExternalToolDefinition +{ + /// Tool-loading deferral policy. + [JsonPropertyName("defer")] + public ProtocolExternalToolDefer? Defer { get; set; } + + /// Model-visible explanation of what the tool does. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; + + /// Whether the tool executes commands in a terminal. + [JsonPropertyName("isTerminal")] + public bool? IsTerminal { get; set; } + + /// Optional caller-defined metadata associated with the tool. + [JsonPropertyName("metadata")] + public IDictionary? Metadata { get; set; } + + /// Unique model-visible tool name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Whether this definition replaces a built-in tool with the same name. + [JsonPropertyName("overridesBuiltInTool")] + public bool? OverridesBuiltInTool { get; set; } + + /// JSON Schema describing the tool's input arguments. + [JsonPropertyName("parameters")] + public IDictionary? Parameters { get; set; } + + /// Whether execution bypasses the normal tool permission prompt. + [JsonPropertyName("skipPermission")] + public bool? SkipPermission { get; set; } + + /// Optional human-readable display title. + [JsonPropertyName("title")] + public string? Title { get; set; } +} + +/// Complete externally implemented tool list for the calling connection. An empty list removes every tool previously supplied by that connection. +[Experimental(Diagnostics.Experimental)] +internal sealed class ToolsSetRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + /// Complete replacement list for the calling connection. + [JsonPropertyName("tools")] + public IList Tools { get => field ??= []; set; } +} + +/// Empty result after applying subagent settings. +[Experimental(Diagnostics.Experimental)] +public sealed class ToolsUpdateSubagentSettingsResult +{ +} + +/// 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; } + + /// Reasoning effort override for matching subagents. + [JsonPropertyName("effortLevel")] + public string? EffortLevel { get; set; } + + /// Model override for matching subagents. + [JsonPropertyName("model")] + public string? Model { get; set; } + + /// Whether the configured model strategy is preferred or required. + [JsonPropertyName("modelPolicy")] + public AgentModelPolicy? ModelPolicy { get; set; } +} + +/// Configured per-agent subagent overrides. +public sealed class UpdateSubagentSettingsRequestSubagents +{ + /// Per-agent settings keyed by subagent agent_type. + [JsonPropertyName("agents")] + public IDictionary? Agents { 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; } +} + +/// Subagent settings to apply to the current session. +[Experimental(Diagnostics.Experimental)] +internal sealed class UpdateSubagentSettingsRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + /// Subagent settings to apply, or null to clear the live session override. + [JsonPropertyName("subagents")] + public UpdateSubagentSettingsRequestSubagents? Subagents { get; set; } +} + +/// RPC data type for SessionCommandsList operations. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionCommandsListRequest +{ + /// Include runtime built-in commands. + [JsonPropertyName("includeBuiltins")] + public bool? IncludeBuiltins { 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; } +} + +/// RPC data type for SessionCommandsListRequestWithSession operations. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionCommandsListRequestWithSession +{ + /// Include runtime built-in commands. + [JsonPropertyName("includeBuiltins")] + public bool? IncludeBuiltins { 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; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// 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")] +[JsonDerivedType(typeof(SlashCommandInvocationResultAddTimelineEntry), "add-timeline-entry")] +[JsonDerivedType(typeof(SlashCommandInvocationResultShowDialog), "show-dialog")] +[JsonDerivedType(typeof(SlashCommandInvocationResultSetModel), "set-model")] +[JsonDerivedType(typeof(SlashCommandInvocationResultSetPlanModel), "set-plan-model")] +public partial class SlashCommandInvocationResult +{ + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} + + +/// 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"; + + /// 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; } +} + +/// 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"; + + /// 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; } +} + +/// 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"; + + /// Optional user-facing message describing the completed command. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("message")] + public string? Message { get; set; } + + /// Optional target session mode applied without submitting an agent prompt. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("mode")] + public SessionMode? Mode { 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; } +} + +/// 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; + + /// Optional group label for organizing options. + [JsonPropertyName("group")] + public string? Group { get; set; } + + /// Subcommand name to invoke. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; +} + +/// 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"; + + /// 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; } +} + +/// RPC data type for SlashCommandTimelineEntry operations. +[Experimental(Diagnostics.Experimental)] +public sealed class SlashCommandTimelineEntry +{ + /// Text displayed for the timeline entry. + [JsonPropertyName("text")] + public string Text { get; set; } = string.Empty; + + /// Timeline entry presentation type. + [JsonPropertyName("type")] + public string Type { get; set; } = string.Empty; + + /// Optional URL associated with the timeline entry. + [JsonPropertyName("url")] + public string? Url { get; set; } +} + +/// The add-timeline-entry variant of . +[Experimental(Diagnostics.Experimental)] +public partial class SlashCommandInvocationResultAddTimelineEntry : SlashCommandInvocationResult +{ + /// + [JsonIgnore] + public override string Kind => "add-timeline-entry"; + + /// Timeline entry the host should append. + [JsonPropertyName("entry")] + public required SlashCommandTimelineEntry Entry { get; set; } + + /// Optional text the host should prefill into the input editor. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("prefillInput")] + public string? PrefillInput { get; set; } + + /// Whether command execution changed persisted runtime settings. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("runtimeSettingsChanged")] + public bool? RuntimeSettingsChanged { get; set; } +} + +/// RPC data type for SlashCommandModelPickerDialog operations. +[Experimental(Diagnostics.Experimental)] +public sealed class SlashCommandModelPickerDialog +{ + /// Discriminator for a model-picker dialog. + [JsonPropertyName("kind")] + public string Kind { get; set; } = string.Empty; + + /// Model that should be enabled before it can be selected. + [JsonPropertyName("modelToEnable")] + public string? ModelToEnable { get; set; } + + /// Settings scope the picker should modify. + [JsonPropertyName("scope")] + public string? Scope { get; set; } + + /// Model-selection target represented by the picker. + [JsonPropertyName("target")] + public string? Target { get; set; } +} + +/// The show-dialog variant of . +[Experimental(Diagnostics.Experimental)] +public partial class SlashCommandInvocationResultShowDialog : SlashCommandInvocationResult +{ + /// + [JsonIgnore] + public override string Kind => "show-dialog"; + + /// Dialog the host should display. + [JsonPropertyName("dialog")] + public required SlashCommandModelPickerDialog Dialog { get; set; } + + /// Whether command execution changed persisted runtime settings. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("runtimeSettingsChanged")] + public bool? RuntimeSettingsChanged { get; set; } +} + +/// User-settings snapshot to restore if the host cancels the model switch. +public sealed class SlashCommandInvocationResultSetModelRevertOnCancel +{ +} + +/// The set-model variant of . +[Experimental(Diagnostics.Experimental)] +public partial class SlashCommandInvocationResultSetModel : SlashCommandInvocationResult +{ + /// + [JsonIgnore] + public override string Kind => "set-model"; + + /// Model selected by the command. + [JsonPropertyName("model")] + public required string Model { get; set; } + + /// Reasoning effort selected for the model. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("reasoningEffort")] + public string? ReasoningEffort { get; set; } + + /// Repository settings scope modified by the command. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("repoScope")] + public string? RepoScope { get; set; } + + /// User-settings snapshot to restore if the host cancels the model switch. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("revertOnCancel")] + public SlashCommandInvocationResultSetModelRevertOnCancel? RevertOnCancel { get; set; } + + /// Whether command execution changed persisted runtime settings. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("runtimeSettingsChanged")] + public bool? RuntimeSettingsChanged { get; set; } + + /// Settings scope modified by the command. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("scope")] + public string? Scope { get; set; } + + /// User-facing warning produced while selecting the model. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("warning")] + public string? Warning { get; set; } +} + +/// The set-plan-model variant of . +[Experimental(Diagnostics.Experimental)] +public partial class SlashCommandInvocationResultSetPlanModel : SlashCommandInvocationResult +{ + /// + [JsonIgnore] + public override string Kind => "set-plan-model"; + + /// User-facing confirmation message for the plan-model selection. + [JsonPropertyName("message")] + public required string Message { get; set; } + + /// Dedicated model selected for plan mode. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("planModel")] + public string? PlanModel { get; set; } + + /// Whether command execution changed persisted runtime settings. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("runtimeSettingsChanged")] + public bool? RuntimeSettingsChanged { 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; } + + /// Command name. Leading slashes are stripped and the name is matched case-insensitively. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Optional client surface that initiated the invocation. + [JsonPropertyName("origin")] + public CommandsInvocationOrigin? Origin { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Whether finalizing the invocation effect succeeded, and the failure reason when it did not. +[Experimental(Diagnostics.Experimental)] +internal sealed class CommandsFinalizeInvocationEffectResult +{ + /// Failure reason when the invocation effect could not be finalized. + [JsonPropertyName("error")] + public string? Error { get; set; } + + /// Whether the pending invocation effect was finalized successfully. + [JsonPropertyName("success")] + public bool Success { get; set; } +} + +/// The slash-command result object that produced the pending effect, echoed back unchanged. +public sealed class CommandsFinalizeInvocationEffectRequestEffect +{ +} + +/// The pending slash-command invocation effect to finalize, plus whether the host applied or cancelled it. +[Experimental(Diagnostics.Experimental)] +internal sealed class CommandsFinalizeInvocationEffectRequest +{ + /// The slash-command result object that produced the pending effect, echoed back unchanged. + [JsonPropertyName("effect")] + public CommandsFinalizeInvocationEffectRequestEffect Effect { get => field ??= new(); set; } + + /// Whether the host applied or cancelled the pending invocation effect. + [JsonPropertyName("outcome")] + public CommandsInvocationEffectOutcome Outcome { 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; } +} + +/// Pending command request ID and an optional error if the client handler failed. +[Experimental(Diagnostics.Experimental)] +internal sealed class CommandsHandlePendingCommandRequest +{ + /// Error message if the command handler failed. + [JsonPropertyName("error")] + public string? Error { 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; +} + +/// Error message produced while executing the command, if any. +[Experimental(Diagnostics.Experimental)] +public sealed class ExecuteCommandResult +{ + /// Error message produced while executing the command, if any. Omitted when the handler succeeded. + [JsonPropertyName("error")] + public string? Error { get; set; } +} + +/// Slash command name and argument string to execute synchronously. +[Experimental(Diagnostics.Experimental)] +internal sealed class ExecuteCommandParams +{ + /// 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; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Indicates whether the command was accepted into the local execution queue. +[Experimental(Diagnostics.Experimental)] +public sealed class EnqueueCommandResult +{ + /// 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; + + /// Optional user-facing text for the queue row. The command string is shown when omitted. + [JsonPropertyName("displayText")] + public string? DisplayText { get; set; } + + /// 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). +[Experimental(Diagnostics.Experimental)] +internal sealed class CommandsRespondToQueuedCommandRequest +{ + /// 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; } + + /// 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 +{ + /// 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. +[Experimental(Diagnostics.Experimental)] +internal sealed class TelemetrySetFeatureOverridesRequest +{ + /// 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; +} + +/// Completed transient query. Ordered chunks and the terminal outcome are also delivered through `ui.ephemeral_query` session events while it runs. +[Experimental(Diagnostics.Experimental)] +public sealed class UIEphemeralQueryResult +{ + /// Answer returned by the model. + [JsonPropertyName("answer")] + public string Answer { get; set; } = string.Empty; +} + +/// Transient question to answer without adding it to conversation history. +[Experimental(Diagnostics.Experimental)] +internal sealed class UIEphemeralQueryRequest +{ + /// Question to answer from the current conversation context. + [JsonPropertyName("question")] + public string Question { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// MCP response metadata. +public sealed class UIElicitationResponseMeta +{ +} + +/// The elicitation response (accept with form values, decline, or cancel). +[Experimental(Diagnostics.Experimental)] +public sealed class UIElicitationResponse +{ + /// MCP response metadata. + [JsonPropertyName("_meta")] + public UIElicitationResponseMeta? Meta { get; set; } + + /// The user's response: accept (submitted), decline (rejected), or cancel (dismissed). + [JsonPropertyName("action")] + public UIElicitationResponseAction Action { get; set; } + + /// The form values submitted by the user (present when action is 'accept'). + [JsonPropertyName("content")] + public IDictionary? Content { get; set; } +} + +/// MCP request metadata. +public sealed class UIElicitationRequestMeta +{ +} + +/// JSON Schema describing the form fields to present to the user. +[Experimental(Diagnostics.Experimental)] +public sealed class UIElicitationSchema +{ + /// 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; } + + /// Schema type indicator (always 'object'). + [JsonPropertyName("type")] + public string Type { get; set; } = string.Empty; +} + +/// Metadata controlling an MCP task's lifetime. +[Experimental(Diagnostics.Experimental)] +public sealed class McpTaskMetadata +{ + /// Task time-to-live. + [JsonPropertyName("ttl")] + public long? Ttl { get; set; } +} + +/// Prompt message and JSON schema describing the form fields to elicit from the user. +[Experimental(Diagnostics.Experimental)] +internal sealed class UIElicitationRequest +{ + /// MCP request metadata. + [JsonPropertyName("_meta")] + public UIElicitationRequestMeta? Meta { get; set; } + + /// Message describing what information is needed from the user. + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; + + /// Elicitation mode. Omitted and form are equivalent for structured elicitation. + [JsonPropertyName("mode")] + public McpElicitationFormMode? Mode { get; 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; + + /// MCP task metadata. + [JsonPropertyName("task")] + public McpTaskMetadata? Task { get; set; } +} + +/// Indicates whether the elicitation response was accepted; false if it was already resolved by another client. +[Experimental(Diagnostics.Experimental)] +public sealed class UIElicitationResult +{ + /// Whether the response was accepted. False if the request was already resolved by another client. + [JsonPropertyName("success")] + public bool Success { get; set; } +} + +/// Pending elicitation request ID and the user's response (accept/decline/cancel + form values). +[Experimental(Diagnostics.Experimental)] +internal sealed class UIHandlePendingElicitationRequest +{ + /// 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; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// 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; } +} + +/// User response for a pending user-input request, with answer text and whether it was typed freeform. +[Experimental(Diagnostics.Experimental)] +public sealed class UIUserInputResponse +{ + /// 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; } +} + +/// Request ID of a pending `user_input.requested` event and the user's response. +[Experimental(Diagnostics.Experimental)] +internal sealed class UIHandlePendingUserInputRequest +{ + /// The unique request ID from the user_input.requested event. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; + + /// 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 sampling result payload. Omit to reject/cancel the sampling request without providing a result. +[Experimental(Diagnostics.Experimental)] +public sealed class UIHandlePendingSamplingResponse +{ +} + +/// Request ID of a pending `sampling.requested` event and an optional sampling result payload (omit to reject). +[Experimental(Diagnostics.Experimental)] +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; +} + +/// Request ID of a pending `auto_mode_switch.requested` event and the user's response. +[Experimental(Diagnostics.Experimental)] +internal sealed class UIHandlePendingAutoModeSwitchRequest +{ + /// The unique request ID from the auto_mode_switch.requested event. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; + + /// 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; +} + +/// The user's selected action for an exhausted session limit. +[Experimental(Diagnostics.Experimental)] +public sealed class UISessionLimitsExhaustedResponse +{ + /// Action selected by the user. + [JsonPropertyName("action")] + public UISessionLimitsExhaustedResponseAction Action { get; set; } + + /// AI Credits to add to the current max when action is 'add'. + [JsonPropertyName("additionalAiCredits")] + public double? AdditionalAiCredits { get; set; } + + /// New absolute max AI Credits when action is 'set'. + [JsonPropertyName("maxAiCredits")] + public double? MaxAiCredits { get; set; } +} + +/// Request ID of a pending `session_limits_exhausted.requested` event and the user's selected limit action. +[Experimental(Diagnostics.Experimental)] +internal sealed class UIHandlePendingSessionLimitsExhaustedRequest +{ + /// 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; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// 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 UIExitPlanModeResponse +{ + /// 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; } +} + +/// Request ID of a pending `exit_plan_mode.requested` event and the user's response. +[Experimental(Diagnostics.Experimental)] +internal sealed class UIHandlePendingExitPlanModeRequest +{ + /// 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; +} + +/// 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 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. + [JsonPropertyName("handle")] + public string Handle { get; set; } = string.Empty; +} + +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionUiRegisterDirectAutoModeSwitchHandlerRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// 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; } +} + +/// Opaque handle previously returned by `registerDirectAutoModeSwitchHandler` to release. +[Experimental(Diagnostics.Experimental)] +internal sealed class UIUnregisterDirectAutoModeSwitchHandlerRequest +{ + /// Handle previously returned by `registerDirectAutoModeSwitchHandler`. + [JsonPropertyName("handle")] + public string Handle { 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 PermissionsConfigureResult +{ + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } +} + +/// Source descriptor for a `session.permissions.configure` content-exclusion rule, with source name and type. +[Experimental(Diagnostics.Experimental)] +public sealed class PermissionsConfigureAdditionalContentExclusionPolicyRuleSource +{ + /// Name of the policy source. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Type of the policy source. + [JsonPropertyName("type")] + public string Type { get; set; } = string.Empty; +} + +/// Single content-exclusion rule supplied to `session.permissions.configure`, with paths, match conditions, and source. +[Experimental(Diagnostics.Experimental)] +public sealed class PermissionsConfigureAdditionalContentExclusionPolicyRule +{ + /// Conditions of which at least one must match. + [JsonPropertyName("ifAnyMatch")] + public IList? IfAnyMatch { get; set; } + + /// Conditions none of which may match. + [JsonPropertyName("ifNoneMatch")] + public IList? IfNoneMatch { get; set; } + + /// Path patterns covered by this rule. + [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)] +public sealed class PermissionsConfigureAdditionalContentExclusionPolicy +{ + /// Opaque policy update timestamp supplied by the host. + [JsonPropertyName("last_updated_at")] + public JsonElement LastUpdatedAt { get; set; } + + /// Content-exclusion rules to apply. + [JsonPropertyName("rules")] + public IList Rules { get => field ??= []; set; } + + /// Allowed values for the `PermissionsConfigureAdditionalContentExclusionPolicyScope` enumeration. + [JsonPropertyName("scope")] + public PermissionsConfigureAdditionalContentExclusionPolicyScope Scope { 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. +[Experimental(Diagnostics.Experimental)] +public sealed class PermissionPathsConfig +{ + /// Additional directories to allow tool access to (in addition to the session's working directory). Conventional `.github/skills/` and `.github/agents/` definitions under them also join the session catalogs when their subsystem gates are enabled, so supplying a directory is a trust decision for configuration stored there. 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; } +} + +/// If specified, replaces the session's approved/denied permission rules. Omit to leave the current rules unchanged. +[Experimental(Diagnostics.Experimental)] +public sealed class PermissionRulesSet +{ + /// Rules that auto-approve matching requests. + [JsonPropertyName("approved")] + public IList Approved { get => field ??= []; set; } + + /// Rules that auto-deny matching requests. + [JsonPropertyName("denied")] + public IList Denied { get => field ??= []; set; } +} + +/// 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 PermissionUrlsConfig +{ + /// 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; } +} + +/// Patch of permission policy fields to apply (omit a field to leave it unchanged). +[Experimental(Diagnostics.Experimental)] +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; + + /// 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. +[Experimental(Diagnostics.Experimental)] +public sealed class PermissionRequestResult +{ + /// Whether the permission request was handled successfully. + [JsonPropertyName("success")] + public bool Success { 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; } + + /// Whether the responding client could ask a user interactively, was running headlessly, or had no response path. Omit when the client cannot determine this authoritatively. + [JsonPropertyName("responseCapability")] + public PermissionResponseCapability? ResponseCapability { get; set; } + + /// Controlled reason or actor responsible for the response. + [JsonPropertyName("source")] + public PermissionDecisionSource Source { get; set; } + + /// Client surface that submitted the response. + [JsonPropertyName("surface")] + public PermissionDecisionSurface Surface { get; set; } +} + +/// The client's response to the pending permission prompt. +/// Polymorphic base type discriminated by kind. +[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 +{ + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} + + +/// Permission-decision request variant to approve only the current permission request. +/// The approve-once variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionDecisionApproveOnce : PermissionDecision +{ + /// + [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; } +} + +/// 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(PermissionDecisionApproveForSessionApprovalFactory), "factory")] +[JsonDerivedType(typeof(PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess), "extension-permission-access")] +[JsonDerivedType(typeof(PermissionDecisionApproveForSessionApprovalExtensionEnvAccess), "extension-env-access")] +public partial class PermissionDecisionApproveForSessionApproval +{ + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} + + +/// 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"; + + /// Command identifiers covered by this approval. + [JsonPropertyName("commandIdentifiers")] + public required IList CommandIdentifiers { get; set; } +} + +/// Session-scoped approval details for read-only filesystem operations. +/// The read variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionDecisionApproveForSessionApprovalRead : PermissionDecisionApproveForSessionApproval +{ + /// + [JsonIgnore] + public override string Kind => "read"; +} + +/// Session-scoped approval details for filesystem write operations. +/// The write variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionDecisionApproveForSessionApprovalWrite : PermissionDecisionApproveForSessionApproval +{ + /// + [JsonIgnore] + public override string Kind => "write"; +} + +/// 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 PermissionDecisionApproveForSessionApprovalMcp : PermissionDecisionApproveForSessionApproval +{ + /// + [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 . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionDecisionApproveForSessionApprovalMcpSampling : PermissionDecisionApproveForSessionApproval +{ + /// + [JsonIgnore] + public override string Kind => "mcp-sampling"; + + /// MCP server name. + [JsonPropertyName("serverName")] + public required string ServerName { 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"; +} + +/// 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 +{ + /// + [JsonIgnore] + public override string Kind => "custom-tool"; + + /// Custom tool name. + [JsonPropertyName("toolName")] + public required string ToolName { get; set; } +} + +/// Session-scoped approval details for extension-management operations, optionally narrowed by operation. +/// The extension-management variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionDecisionApproveForSessionApprovalExtensionManagement : PermissionDecisionApproveForSessionApproval +{ + /// + [JsonIgnore] + public override string Kind => "extension-management"; + + /// Optional operation identifier; when omitted, the approval covers all extension management operations. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("operation")] + public string? Operation { get; set; } +} + +/// Session-scoped factory approval, optionally narrowed by approval key. +/// The factory variant of . +[Experimental(Diagnostics.Experimental)] +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] + public override string Kind => "extension-permission-access"; + + /// Extension name. + [JsonPropertyName("extensionName")] + public required string ExtensionName { get; set; } +} + +/// Session-scoped approval details for an extension's access to sensitive environment variables, keyed by extension name and the exact set of variable names. +/// The extension-env-access variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionDecisionApproveForSessionApprovalExtensionEnvAccess : PermissionDecisionApproveForSessionApproval +{ + /// + [JsonIgnore] + public override string Kind => "extension-env-access"; + + /// Names of the sensitive environment variables this approval covers. Values are never persisted. + [JsonPropertyName("environmentVariables")] + public required IList EnvironmentVariables { get; set; } + + /// Extension name. + [JsonPropertyName("extensionName")] + public required string ExtensionName { 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 . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionDecisionApproveForSession : PermissionDecision +{ + /// + [JsonIgnore] + public override string Kind => "approve-for-session"; + + /// Session-scoped approval to remember (tool prompts only; omitted for path/url prompts). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("approval")] + public PermissionDecisionApproveForSessionApproval? Approval { 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; } +} + +/// Approval to persist for this location. +/// 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(PermissionDecisionApproveForLocationApprovalFactory), "factory")] +[JsonDerivedType(typeof(PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess), "extension-permission-access")] +[JsonDerivedType(typeof(PermissionDecisionApproveForLocationApprovalExtensionEnvAccess), "extension-env-access")] +public partial class PermissionDecisionApproveForLocationApproval +{ + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} + + +/// Location-scoped approval details for specific command identifiers. +/// The commands variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionDecisionApproveForLocationApprovalCommands : PermissionDecisionApproveForLocationApproval +{ + /// + [JsonIgnore] + public override string Kind => "commands"; + + /// Command identifiers covered by this approval. + [JsonPropertyName("commandIdentifiers")] + public required IList CommandIdentifiers { get; set; } +} + +/// Location-scoped approval details for read-only filesystem operations. +/// The read variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionDecisionApproveForLocationApprovalRead : PermissionDecisionApproveForLocationApproval +{ + /// + [JsonIgnore] + public override string Kind => "read"; +} + +/// Location-scoped approval details for filesystem write operations. +/// The write variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionDecisionApproveForLocationApprovalWrite : PermissionDecisionApproveForLocationApproval +{ + /// + [JsonIgnore] + public override string Kind => "write"; +} + +/// 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 partial class PermissionDecisionApproveForLocationApprovalMcp : PermissionDecisionApproveForLocationApproval +{ + /// + [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; } +} + +/// Location-scoped approval details for MCP sampling requests from a server. +/// The mcp-sampling variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionDecisionApproveForLocationApprovalMcpSampling : PermissionDecisionApproveForLocationApproval +{ + /// + [JsonIgnore] + public override string Kind => "mcp-sampling"; + + /// MCP server name. + [JsonPropertyName("serverName")] + public required string ServerName { get; set; } +} + +/// Location-scoped approval details for writes to long-term memory. +/// The memory variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionDecisionApproveForLocationApprovalMemory : PermissionDecisionApproveForLocationApproval +{ + /// + [JsonIgnore] + public override string Kind => "memory"; +} + +/// 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"; + + /// Custom tool name. + [JsonPropertyName("toolName")] + public required string ToolName { get; set; } +} + +/// Location-scoped approval details for extension-management operations, optionally narrowed by operation. +/// The extension-management variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionDecisionApproveForLocationApprovalExtensionManagement : PermissionDecisionApproveForLocationApproval +{ + /// + [JsonIgnore] + public override string Kind => "extension-management"; + + /// Optional operation identifier; when omitted, the approval covers all extension management operations. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("operation")] + public string? Operation { 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"; + + /// 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; } +} + +/// 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 +{ + /// + [JsonIgnore] + public override string Kind => "extension-permission-access"; + + /// Extension name. + [JsonPropertyName("extensionName")] + public required string ExtensionName { get; set; } +} + +/// Location-scoped approval details for an extension's access to sensitive environment variables, keyed by extension name and the exact set of variable names. +/// The extension-env-access variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionDecisionApproveForLocationApprovalExtensionEnvAccess : PermissionDecisionApproveForLocationApproval +{ + /// + [JsonIgnore] + public override string Kind => "extension-env-access"; + + /// Names of the sensitive environment variables this approval covers. Values are never persisted. + [JsonPropertyName("environmentVariables")] + public required IList EnvironmentVariables { get; set; } + + /// Extension name. + [JsonPropertyName("extensionName")] + public required string ExtensionName { 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"; + + /// Approval to persist for this location. + [JsonPropertyName("approval")] + public required PermissionDecisionApproveForLocationApproval Approval { get; set; } + + /// Location key (git root or cwd) to persist the approval to. + [JsonPropertyName("locationKey")] + public required string LocationKey { 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"; + + /// URL domain to approve permanently. + [JsonPropertyName("domain")] + public required string Domain { get; set; } +} + +/// Permission-decision request variant to reject a pending permission request, with optional feedback. +/// The reject variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionDecisionReject : PermissionDecision +{ + /// + [JsonIgnore] + public override string Kind => "reject"; + + /// Optional feedback explaining the rejection. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("feedback")] + public string? Feedback { 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"; +} + +/// 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"; +} + +/// 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"; + + /// The approval to add as a session-scoped rule. + [JsonPropertyName("approval")] + public required UserToolSessionApproval Approval { 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 . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionDecisionApprovedForLocation : PermissionDecision +{ + /// + [JsonIgnore] + public override string Kind => "approved-for-location"; + + /// The approval to persist for this location. + [JsonPropertyName("approval")] + public required UserToolSessionApproval Approval { get; set; } + + /// The location key (git root or cwd) to persist the approval to. + [JsonPropertyName("locationKey")] + public required string LocationKey { 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"; + + /// Optional explanation of why the request was cancelled. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("reason")] + public string? Reason { 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"; + + /// Rules that denied the request. + [JsonPropertyName("rules")] + public required IList Rules { get; set; } +} + +/// 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)] +public partial class PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser : PermissionDecision +{ + /// + [JsonIgnore] + public override string Kind => "denied-no-approval-rule-and-could-not-request-from-user"; +} + +/// 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 partial class PermissionDecisionDeniedInteractivelyByUser : PermissionDecision +{ + /// + [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; } +} + +/// Permission-decision variant indicating denial by content-exclusion policy, with path and message. +/// The denied-by-content-exclusion-policy variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionDecisionDeniedByContentExclusionPolicy : PermissionDecision +{ + /// + [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; } +} + +/// 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 partial class PermissionDecisionDeniedByPermissionRequestHook : PermissionDecision +{ + /// + [JsonIgnore] + public override string Kind => "denied-by-permission-request-hook"; + + /// 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; } +} + +/// Pending permission request ID and the decision to apply (approve/reject and scope). +[Experimental(Diagnostics.Experimental)] +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; +} + +/// Pending permission prompt reconstructed from event history, with request ID and user-facing prompt details. +[Experimental(Diagnostics.Experimental)] +public sealed class PendingPermissionRequest +{ + /// 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; +} + +/// List of pending permission requests reconstructed from event history. +[Experimental(Diagnostics.Experimental)] +public sealed class 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. + [JsonPropertyName("items")] + public IList Items { get => field ??= []; 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. +[Experimental(Diagnostics.Experimental)] +public sealed class PermissionsSetApproveAllResult +{ + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } +} + +/// Allow-all toggle for tool permission requests, with an optional telemetry source. +[Experimental(Diagnostics.Experimental)] +internal sealed class PermissionsSetApproveAllRequest +{ + /// Whether to auto-approve all tool permission requests. + [JsonPropertyName("enabled")] + public bool Enabled { 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 PermissionsSetApproveAllSource? Source { get; set; } +} + +/// Indicates whether the requested permission mode was applied and reports the authoritative post-mutation mode. +[Experimental(Diagnostics.Experimental)] +public sealed class PermissionsSetModeResult +{ + /// Authoritative permission mode after the mutation. + [JsonPropertyName("mode")] + public PermissionMode Mode { get; set; } + + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } +} + +/// Permission mode to apply for the session. +[Experimental(Diagnostics.Experimental)] +internal sealed class PermissionsSetModeRequest +{ + /// Optional judge model id for assisted mode. When omitted, the session resolves the provider default: `gpt-5.5` for CAPI sessions and the active session model for BYOK sessions. + [JsonPropertyName("assistedApprovalModel")] + public string? AssistedApprovalModel { get; set; } + + /// Permission mode to apply. + [JsonPropertyName("mode")] + public PermissionMode Mode { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + /// Optional source for permission-mode telemetry. Defaults to `rpc` when omitted for SDK callers. + [JsonPropertyName("source")] + public PermissionModeSource? Source { get; set; } +} + +/// Current permission mode. +[Experimental(Diagnostics.Experimental)] +public sealed class PermissionsGetModeResult +{ + /// Current permission mode. + [JsonPropertyName("mode")] + public PermissionMode Mode { get; set; } +} + +/// No parameters. +[Experimental(Diagnostics.Experimental)] +internal sealed class PermissionsGetModeRequest +{ + /// 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. +[Experimental(Diagnostics.Experimental)] +internal sealed class PermissionsModifyRulesParams +{ + /// 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; } + + /// 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; +} + +/// Indicates whether the operation succeeded. +[Experimental(Diagnostics.Experimental)] +public sealed class PermissionsSetRequiredResult +{ + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { 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; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Indicates whether the operation succeeded. +[Experimental(Diagnostics.Experimental)] +public sealed class PermissionsResetSessionApprovalsResult +{ + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } +} + +/// Clears session-scoped tool permission approvals, and optionally the location-scoped ones. +[Experimental(Diagnostics.Experimental)] +internal sealed class PermissionsResetSessionApprovalsRequest +{ + /// Whether location-scoped approvals are cleared too. Defaults to `true`. + [JsonPropertyName("includeLocation")] + public bool? IncludeLocation { 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 PermissionsNotifyPromptShownResult +{ + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } +} + +/// Notification payload describing the permission prompt that the client just rendered. +[Experimental(Diagnostics.Experimental)] +internal sealed class 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). + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Snapshot of the session's allow-listed directories and primary working directory. +[Experimental(Diagnostics.Experimental)] +public sealed class PermissionPathsList +{ + /// 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; +} + +/// No parameters; returns the session's allow-listed directories. +[Experimental(Diagnostics.Experimental)] +internal sealed class PermissionsPathsListRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { 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; } +} + +/// Directory path to add to the session's allowed directories. +[Experimental(Diagnostics.Experimental)] +internal sealed class PermissionPathsAddParams +{ + /// Directory to add to the allow-list. The runtime resolves and validates the path before adding, then loads conventional `.github/skills/` and `.github/agents/` definitions under it when their subsystem gates are enabled. Adding the directory is therefore also a trust decision for configuration stored there. + [JsonPropertyName("path")] + public string Path { 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 PermissionsPathsUpdatePrimaryResult +{ + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } +} + +/// Directory path to set as the session's new primary working directory. +[Experimental(Diagnostics.Experimental)] +internal sealed class PermissionPathsUpdatePrimaryParams +{ + /// 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; +} + +/// Indicates whether the supplied path is within the session's allowed directories. +[Experimental(Diagnostics.Experimental)] +public sealed class PermissionPathsAllowedCheckResult +{ + /// Whether the path is within the session's allowed directories. + [JsonPropertyName("allowed")] + public bool Allowed { get; set; } +} + +/// Path to evaluate against the session's allowed directories. +[Experimental(Diagnostics.Experimental)] +internal sealed class PermissionPathsAllowedCheckParams +{ + /// Path to check against the session's allowed directories. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// 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; } +} + +/// Path to evaluate against the session's workspace (primary) directory. +[Experimental(Diagnostics.Experimental)] +internal sealed class PermissionPathsWorkspaceCheckParams +{ + /// Path to check against the session workspace directory. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// 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; + + /// Whether the location is a git repo or directory. + [JsonPropertyName("locationType")] + public PermissionLocationType LocationType { get; set; } +} + +/// Working directory to resolve into a location-permissions key. +[Experimental(Diagnostics.Experimental)] +internal sealed class PermissionLocationResolveParams +{ + /// 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; +} + +/// Summary of persisted location permissions applied to the session. +[Experimental(Diagnostics.Experimental)] +public sealed class PermissionLocationApplyResult +{ + /// 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 a different location was applied since the previous apply call. + [JsonPropertyName("changed")] + public bool Changed { get; set; } + + /// Location key used in the location-permissions store. + [JsonPropertyName("locationKey")] + public string LocationKey { get; set; } = string.Empty; + + /// Whether the location is a git repo or directory. + [JsonPropertyName("locationType")] + public PermissionLocationType LocationType { 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; + + /// Working directory whose persisted location permissions should be applied. + [JsonPropertyName("workingDirectory")] + public string WorkingDirectory { get; set; } = string.Empty; +} + +/// Indicates whether the operation succeeded. +[Experimental(Diagnostics.Experimental)] +public sealed class PermissionsLocationsAddToolApprovalResult +{ + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } +} + +/// Tool approval to persist and apply. +/// 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(PermissionsLocationsAddToolApprovalDetailsFactory), "factory")] +[JsonDerivedType(typeof(PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess), "extension-permission-access")] +[JsonDerivedType(typeof(PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess), "extension-env-access")] +public partial class PermissionsLocationsAddToolApprovalDetails +{ + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} + + +/// 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"; + + /// Command identifiers covered by this approval. + [JsonPropertyName("commandIdentifiers")] + public required IList CommandIdentifiers { 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"; +} + +/// 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"; +} + +/// 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"; + + /// 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; } +} + +/// Location-persisted tool approval details for MCP sampling requests from a server. +/// The mcp-sampling variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionsLocationsAddToolApprovalDetailsMcpSampling : PermissionsLocationsAddToolApprovalDetails +{ + /// + [JsonIgnore] + public override string Kind => "mcp-sampling"; + + /// MCP server name. + [JsonPropertyName("serverName")] + public required string ServerName { get; 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"; +} + +/// 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"; + + /// Custom tool name. + [JsonPropertyName("toolName")] + public required string ToolName { get; set; } +} - /// Runtime context discriminator (e.g., `cli`, `actions`). - [JsonPropertyName("agentContext")] - public string? AgentContext { get; 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"; - /// Whether to include instructions from every MCP server in the system prompt instead of only allowlisted servers. - [JsonPropertyName("allowAllMcpServerInstructions")] - public bool? AllowAllMcpServerInstructions { 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; } +} - /// Whether to disable the `ask_user` tool (encourages autonomous behavior). - [JsonPropertyName("askUserDisabled")] - public bool? AskUserDisabled { get; set; } +/// Location-persisted factory approval, optionally narrowed by approval key. +/// The factory variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionsLocationsAddToolApprovalDetailsFactory : PermissionsLocationsAddToolApprovalDetails +{ + /// + [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; } +} + +/// 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)] +public partial class PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess : PermissionsLocationsAddToolApprovalDetails +{ + /// + [JsonIgnore] + public override string Kind => "extension-permission-access"; + + /// Extension name. + [JsonPropertyName("extensionName")] + public required string ExtensionName { get; set; } +} + +/// Location-persisted tool approval details for an extension's access to sensitive environment variables, keyed by extension name and the exact set of variable names. +/// The extension-env-access variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess : PermissionsLocationsAddToolApprovalDetails +{ + /// + [JsonIgnore] + public override string Kind => "extension-env-access"; + + /// Names of the sensitive environment variables this approval covers. Values are never persisted. + [JsonPropertyName("environmentVariables")] + public required IList EnvironmentVariables { get; set; } + + /// Extension name. + [JsonPropertyName("extensionName")] + public required string ExtensionName { get; set; } +} + +/// Location-scoped tool approval to persist. +[Experimental(Diagnostics.Experimental)] +internal sealed class PermissionLocationAddToolApprovalParams +{ + /// 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; +} + +/// Folder trust check result. +[Experimental(Diagnostics.Experimental)] +public sealed class FolderTrustCheckResult +{ + /// Whether the folder is trusted. + [JsonPropertyName("trusted")] + public bool Trusted { get; set; } +} + +/// Folder path to check for trust. +[Experimental(Diagnostics.Experimental)] +internal sealed class FolderTrustCheckParams +{ + /// 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; +} + +/// Indicates whether the operation succeeded. +[Experimental(Diagnostics.Experimental)] +public sealed class PermissionsFolderTrustAddTrustedResult +{ + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } +} + +/// Folder path to add to trusted folders. +[Experimental(Diagnostics.Experimental)] +internal sealed class FolderTrustAddParams +{ + /// 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; +} + +/// Indicates whether the operation succeeded. +[Experimental(Diagnostics.Experimental)] +public sealed class PermissionsUrlsSetUnrestrictedModeResult +{ + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } +} + +/// Whether the URL-permission policy should run in unrestricted mode. +[Experimental(Diagnostics.Experimental)] +internal sealed class PermissionUrlsSetUnrestrictedModeParams +{ + /// 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; +} + +/// The repository the remote session targets. +[Experimental(Diagnostics.Experimental)] +public sealed class MetadataSnapshotRemoteMetadataRepository +{ + /// 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; +} + +/// Remote-session-specific metadata. Populated only when `isRemote` is true. Fields are immutable for the lifetime of the session. +[Experimental(Diagnostics.Experimental)] +public sealed class MetadataSnapshotRemoteMetadata +{ + /// The pull request number the remote session is associated with, if any. + [JsonPropertyName("pullRequestNumber")] + public long? PullRequestNumber { get; set; } + + /// 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; } +} + +/// Public-facing projection of workspace metadata for SDK / TUI consumers. +public sealed class SessionMetadataSnapshotWorkspace +{ + /// Branch checked out at session start, if any. + [JsonPropertyName("branch")] + public string? Branch { get; set; } + + /// ISO 8601 timestamp when the workspace was created. + [JsonPropertyName("created_at")] + public DateTimeOffset? CreatedAt { get; set; } + + /// Current working directory at session start. + [JsonPropertyName("cwd")] + public string? Cwd { get; set; } + + /// Resolved git root for cwd, if any. + [JsonPropertyName("git_root")] + public string? GitRoot { get; set; } + + /// Repository host type, if known. + [JsonPropertyName("host_type")] + public WorkspaceSummaryHostType? HostType { 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; } +} + +/// Point-in-time snapshot of slow-changing session identifier and state fields. +[Experimental(Diagnostics.Experimental)] +public sealed class 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. + [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; } + + /// User-provided name supplied at session construction (via `--name`), if any. Immutable after construction. + [JsonPropertyName("initialName")] + public string? InitialName { 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; } + + /// 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; } - /// Allowlist of tool names available to this session. - [JsonPropertyName("availableTools")] - public IList? AvailableTools { 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; } - /// Options scoped to the built-in CAPI (Copilot API) provider. - [JsonPropertyName("capi")] - public CapiSessionOptions? Capi { get; set; } + /// Currently selected model identifier, if any. + [JsonPropertyName("selectedModel")] + public string? SelectedModel { get; set; } - /// Identifier of the client driving the session. - [JsonPropertyName("clientName")] - public string? ClientName { get; set; } + /// The unique identifier of the session. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// Whether to include the `Co-authored-by` trailer in commit messages. - [JsonPropertyName("coauthorEnabled")] - public bool? CoauthorEnabled { get; set; } + /// Current session limits, or null when no limits are active. + [JsonPropertyName("sessionLimits")] + public SessionLimitsConfig? SessionLimits { 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; } + /// ISO 8601 timestamp of when the session started. + [JsonPropertyName("startTime")] + public DateTimeOffset StartTime { get; set; } - /// Whether to allow auto-mode continuation across turns. - [JsonPropertyName("continueOnAutoMode")] - public bool? ContinueOnAutoMode { 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; } - /// Override URL for the Copilot API endpoint. - [JsonPropertyName("copilotUrl")] - public string? CopilotUrl { get; set; } + /// Absolute path to the session's current working directory. + [JsonPropertyName("workingDirectory")] + public string WorkingDirectory { get; set; } = string.Empty; - /// Whether to default custom agents to local-only execution. - [JsonPropertyName("customAgentsLocalOnly")] - public bool? CustomAgentsLocalOnly { 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; } - /// Instruction source IDs to exclude from the system prompt. - [JsonPropertyName("disabledInstructionSources")] - public IList? DisabledInstructionSources { 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; } +} - /// Skill IDs that should be excluded from this session. - [JsonPropertyName("disabledSkills")] - public IList? DisabledSkills { get; set; } +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionMetadataSnapshotRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Whether to enable loading of `.github/hooks/` filesystem hooks. Separate from the SDK callback hook mechanism. - [JsonPropertyName("enableFileHooks")] - public bool? EnableFileHooks { get; set; } +/// Indicates whether the local session is currently processing a turn or background continuation. +[Experimental(Diagnostics.Experimental)] +public sealed class 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. + [JsonPropertyName("processing")] + public bool Processing { 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; } +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionMetadataIsProcessingRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// 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; } +/// Current activity flags for the session. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionActivity +{ + /// Whether an in-flight operation can currently be aborted. + [JsonPropertyName("abortable")] + public bool Abortable { get; set; } - /// Whether to surface reasoning-summary events from the model. - [JsonPropertyName("enableReasoningSummaries")] - public bool? EnableReasoningSummaries { get; set; } + /// Whether the session currently has active work, including running turns or tasks. + [JsonPropertyName("hasActiveWork")] + public bool HasActiveWork { get; set; } +} - /// Whether shell-script safety heuristics are enabled. - [JsonPropertyName("enableScriptSafety")] - public bool? EnableScriptSafety { get; set; } +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionMetadataActivityRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Whether to enable cross-session store writes and reads. - [JsonPropertyName("enableSessionStore")] - public bool? EnableSessionStore { get; set; } +/// Token-usage breakdown for the session's current context window. +public sealed class MetadataContextInfoResultContextInfo +{ + /// Output reserve plus tokens after the buffer-exhaustion blocking threshold (default 95%). + [JsonPropertyName("bufferTokens")] + public long BufferTokens { get; set; } - /// Whether to enable skill directory scanning and loading. Falls back to enableConfigDiscovery when unset. - [JsonPropertyName("enableSkills")] - public bool? EnableSkills { get; set; } + /// Token count at which background compaction starts (configurable percentage of promptTokenLimit). + [JsonPropertyName("compactionThreshold")] + public long CompactionThreshold { get; set; } - /// Whether to stream model responses. - [JsonPropertyName("enableStreaming")] - public bool? EnableStreaming { get; set; } + /// Tokens consumed by user/assistant/tool messages. + [JsonPropertyName("conversationTokens")] + public long ConversationTokens { 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; } + /// Prompt token limit plus the model's full output token limit. + [JsonPropertyName("limit")] + public long Limit { 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; } + /// Tokens consumed by MCP tool definitions (subset of toolDefinitionsTokens, excludes deferred tools). + [JsonPropertyName("mcpToolsTokens")] + public long McpToolsTokens { get; set; } - /// Whether subagent callback events should be forwarded into the session event log sink. - [JsonPropertyName("eventsLogIncludesSubagents")] - public bool? EventsLogIncludesSubagents { get; set; } + /// The model used for token counting. + [JsonPropertyName("modelName")] + public string ModelName { get; set; } = string.Empty; - /// 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; } + /// Maximum prompt tokens allowed by the model (or DEFAULT_TOKEN_LIMIT if unspecified). + [JsonPropertyName("promptTokenLimit")] + public long PromptTokenLimit { get; set; } - /// Denylist of tool names for this session. - [JsonPropertyName("excludedTools")] - public IList? ExcludedTools { get; set; } + /// Tokens consumed by the system prompt. + [JsonPropertyName("systemTokens")] + public long SystemTokens { get; set; } - /// Map of feature-flag IDs to their boolean enabled state. - [JsonPropertyName("featureFlags")] - public IDictionary? FeatureFlags { get; set; } + /// Tokens consumed by tool definitions sent to the model (excludes deferred tools). + [JsonPropertyName("toolDefinitionsTokens")] + public long ToolDefinitionsTokens { 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; } + /// Sum of system, conversation and tool-definition tokens. + [JsonPropertyName("totalTokens")] + public long TotalTokens { 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; } +/// Token breakdown for the session's current context window, or null if uninitialized. +[Experimental(Diagnostics.Experimental)] +public sealed class 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). + [JsonPropertyName("contextInfo")] + public MetadataContextInfoResultContextInfo? ContextInfo { get; set; } +} - /// Stable integration identifier used for analytics and rate-limit attribution. - [JsonPropertyName("integrationId")] - public string? IntegrationId { get; set; } +/// Model identifier and token limits used to compute the context-info breakdown. +[Experimental(Diagnostics.Experimental)] +internal sealed class MetadataContextInfoRequest +{ + /// Maximum output tokens allowed by the target model. Pass 0 if unknown. + [JsonPropertyName("outputTokenLimit")] + public long OutputTokenLimit { get; set; } - /// Whether experimental capabilities are enabled. - [JsonPropertyName("isExperimentalMode")] - public bool? IsExperimentalMode { get; set; } + /// Maximum prompt tokens allowed by the target model. Pass 0 to use the runtime default. + [JsonPropertyName("promptTokenLimit")] + public long PromptTokenLimit { get; set; } - /// Whether interactive shell sessions are logged. - [JsonPropertyName("logInteractiveShells")] - public bool? LogInteractiveShells { 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; } - /// Identifier sent to LSP-style integrations. - [JsonPropertyName("lspClientName")] - public string? LspClientName { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// 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; } +/// 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 +{ + /// Output reserve plus post-blocking-threshold buffer. + [JsonPropertyName("buffer")] + public long Buffer { 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; } + /// Custom-instructions tokens (0 when none are configured). + [JsonPropertyName("customInstructions")] + public long CustomInstructions { get; set; } - /// The model ID to use for assistant turns. - [JsonPropertyName("model")] - public string? Model { get; set; } + /// Remaining unused window capacity (clamped at 0). + [JsonPropertyName("freeSpace")] + public long FreeSpace { get; set; } - /// Per-property model capability overrides for the selected model. - [JsonPropertyName("modelCapabilitiesOverrides")] - public ModelCapabilitiesOverride? ModelCapabilitiesOverrides { get; set; } + /// MCP tool-definition tokens. + [JsonPropertyName("mcpTools")] + public long McpTools { get; set; } - /// Organization-level custom instructions to inject into the system prompt. - [JsonPropertyName("organizationCustomInstructions")] - public string? OrganizationCustomInstructions { get; set; } + /// Conversation (user/assistant/tool) message tokens. + [JsonPropertyName("messages")] + public long Messages { get; set; } - /// Custom model-provider configuration (BYOK). - [JsonPropertyName("provider")] - public ProviderConfig? Provider { get; set; } + /// System prompt tokens, excluding custom instructions. + [JsonPropertyName("systemPrompt")] + public long SystemPrompt { 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; } + /// Non-MCP tool-definition tokens. + [JsonPropertyName("systemTools")] + public long SystemTools { get; set; } +} - /// Reasoning summary mode for supported model clients. - [JsonPropertyName("reasoningSummary")] - public OptionsUpdateReasoningSummary? ReasoningSummary { get; set; } +/// Successful compaction history for the session. +public sealed class MetadataContextAttributionResultContextAttributionCompactions +{ + /// Number of successful compactions in this session. + [JsonPropertyName("count")] + public long Count { get; set; } +} - /// Whether the session is running in an interactive UI. - [JsonPropertyName("runningInInteractiveMode")] - public bool? RunningInInteractiveMode { get; set; } +/// RPC data type for MetadataContextAttributionResultContextAttributionEntry operations. +public sealed class MetadataContextAttributionResultContextAttributionEntry +{ + /// 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; } - /// Resolved sandbox configuration. - [JsonPropertyName("sandboxConfig")] - public SandboxConfig? SandboxConfig { 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; - /// 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; } + /// 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; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// 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 session limits. Pass null to clear the session limits. - [JsonPropertyName("sessionLimits")] - public SessionLimitsConfig? SessionLimits { 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; } - /// Per-session settings for built-in shell tools. - [JsonPropertyName("shell")] - public ShellOptions? Shell { get; set; } + /// Token count currently in context attributable to this entry. + [JsonPropertyName("tokens")] + public long Tokens { 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; } +/// Per-source token attribution snapshot for the current context window. The heaviest individual messages are available separately via `metadata.getContextHeaviestMessages`. +public sealed class MetadataContextAttributionResultContextAttribution +{ + /// Output reserve plus the tokens past the buffer-exhaustion blocking threshold. Mirrors `SessionContextInfo.bufferTokens`. + [JsonPropertyName("bufferTokens")] + public long BufferTokens { get; set; } - /// PowerShell process flags applied to built-in and user-requested shell commands. - [JsonPropertyName("shellProcessFlags")] - public IList? ShellProcessFlags { 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; } - /// Additional directories to search for skills. - [JsonPropertyName("skillDirectories")] - public IList? SkillDirectories { get; set; } + /// Successful compaction history for the session. + [JsonPropertyName("compactions")] + public MetadataContextAttributionResultContextAttributionCompactions Compactions { get => field ??= new(); set; } - /// Whether to skip loading custom instruction sources. - [JsonPropertyName("skipCustomInstructions")] - public bool? SkipCustomInstructions { get; set; } + /// Token count at which background compaction starts. Mirrors `SessionContextInfo.compactionThreshold`. + [JsonPropertyName("compactionThreshold")] + public long CompactionThreshold { get; set; } - /// Whether to skip embedding retrieval pipeline initialization and execution. - [JsonPropertyName("skipEmbeddingRetrieval")] - public bool? SkipEmbeddingRetrieval { 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; } - /// 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; } + /// 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; } - /// Controls how availableTools (allowlist) and excludedTools (denylist) combine when both are set. - [JsonPropertyName("toolFilterPrecedence")] - public OptionsUpdateToolFilterPrecedence? ToolFilterPrecedence { 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; - /// Optional path for trajectory output. - [JsonPropertyName("trajectoryFile")] - public string? TrajectoryFile { get; set; } + /// 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; - /// Output verbosity level for supported models. - [JsonPropertyName("verbosity")] - public Verbosity? Verbosity { get; set; } + /// 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; } - /// Absolute working-directory path for shell tools. - [JsonPropertyName("workingDirectory")] - public string? WorkingDirectory { 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; } } -/// Parameters for (re)loading the merged LSP configuration set. +/// Per-source attribution breakdown for the session's current context window, or null if uninitialized. [Experimental(Diagnostics.Experimental)] -internal sealed class LspInitializeRequest +public sealed class MetadataContextAttributionResult { - /// Force re-initialization even when LSP configs were already loaded for the working directory. - [JsonPropertyName("force")] - public bool? Force { get; set; } - - /// Git root used as the boundary when traversing for project-level LSP configs (supports monorepos). - [JsonPropertyName("gitRoot")] - public string? GitRoot { get; set; } + /// 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 SessionMetadataGetContextAttributionRequest +{ /// 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. +/// A single large message currently in context. [Experimental(Diagnostics.Experimental)] -public sealed class Extension +public sealed class ContextHeaviestMessage { - /// Source-qualified ID (e.g., 'project:my-ext', 'user:auth-helper', 'plugin:my-plugin:my-ext'). + /// Stable identifier for this message within the snapshot. [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; } + /// Human-readable source label, e.g. `tool: bash` or `skill: tmux`. Presentation-only. + [JsonPropertyName("label")] + public string Label { get; set; } = string.Empty; - /// Discovery source: project (.github/extensions/), user (~/.copilot/extensions/), plugin (installed plugin), or session (session-state/<id>/extensions/). - [JsonPropertyName("source")] - public ExtensionSource Source { get; set; } + /// Role of the chat message (`user`, `assistant`, or `tool`). + [JsonPropertyName("role")] + public string Role { get; set; } = string.Empty; - /// Current status: running, disabled, failed, or starting. - [JsonPropertyName("status")] - public ExtensionStatus Status { get; set; } + /// Token count currently in context for this individual message. + [JsonPropertyName("tokens")] + public long Tokens { get; set; } } -/// Extensions discovered for the session, with their current status. +/// The heaviest individual messages in the session's context window, most-expensive first. [Experimental(Diagnostics.Experimental)] -public sealed class ExtensionList +public sealed class MetadataContextHeaviestMessagesResult { - /// Discovered extensions and their current status. - [JsonPropertyName("extensions")] - public IList Extensions { get => field ??= []; 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; } } -/// Identifies the target session. +/// Parameters for the heaviest-messages query. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionExtensionsListRequest +internal sealed class MetadataContextHeaviestMessagesRequest { + /// 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; } -/// Source-qualified extension identifier to enable for the session. +/// 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)] -internal sealed class ExtensionsEnableRequest +public sealed class MetadataRecordContextChangeResult { - /// 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; } -/// Source-qualified extension identifier to disable for the session. +/// Updated working directory and git context. Emitted as the new payload of `session.context_changed`. [Experimental(Diagnostics.Experimental)] -internal sealed class ExtensionsDisableRequest +public sealed class SessionWorkingDirectoryContext { - /// Source-qualified extension ID to disable. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// Merge-base commit SHA (fork point from the remote default branch). + [JsonPropertyName("baseCommit")] + public string? BaseCommit { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Current git branch name. + [JsonPropertyName("branch")] + public string? Branch { get; set; } + + /// Current working directory path. + [JsonPropertyName("cwd")] + public string Cwd { 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; } } -/// Identifies the target session. +/// Updated working-directory/git context to record on the session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionExtensionsReloadRequest +internal sealed class MetadataRecordContextChangeRequest { + /// Updated working directory and git context. Emitted as the new payload of `session.context_changed`. + [JsonPropertyName("context")] + public SessionWorkingDirectoryContext Context { get => field ??= new(); set; } + /// 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. +/// 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)] -[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 MetadataSetWorkingDirectoryResult { - /// The type discriminator. - [JsonPropertyName("type")] - public virtual string Type { get; set; } = string.Empty; + /// Working directory after the update. + [JsonPropertyName("workingDirectory")] + public string WorkingDirectory { get; set; } = string.Empty; } - -/// Optional line range to scope the attachment to a specific section of the file. +/// 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 PushAttachmentFileLineRange +internal sealed class MetadataSetWorkingDirectoryRequest { - /// End line number (1-based, inclusive). - [JsonPropertyName("end")] - public long End { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// Start line number (1-based). - [JsonPropertyName("start")] - public long Start { 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; } -/// File attachment. -/// The file variant of . +/// 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 partial class PushAttachmentFile : PushAttachment +public sealed class MetadataRecomputeContextTokensResult { - /// - [JsonIgnore] - public override string Type => "file"; - - /// User-facing display name for the attachment. - [JsonPropertyName("displayName")] - public required string DisplayName { get; set; } + /// Tokens contributed by user/assistant/tool messages (excludes system/developer prompts). + [JsonPropertyName("messagesTokenCount")] + public long MessagesTokenCount { get; set; } - /// 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; } + /// Tokens contributed by system/developer prompt snapshots. + [JsonPropertyName("systemTokenCount")] + public long SystemTokenCount { get; set; } - /// Absolute file path. - [JsonPropertyName("path")] - public required string Path { get; set; } + /// Sum of tokens across chat-context and system-context messages currently held by the session. + [JsonPropertyName("totalTokens")] + public long TotalTokens { get; set; } } -/// Directory attachment. -/// The directory variant of . +/// Model identifier to use when re-tokenizing the session's existing messages. [Experimental(Diagnostics.Experimental)] -public partial class PushAttachmentDirectory : PushAttachment +internal sealed class MetadataRecomputeContextTokensRequest { - /// - [JsonIgnore] - public override string Type => "directory"; + /// 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; - /// User-facing display name for the attachment. - [JsonPropertyName("displayName")] - public required string DisplayName { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Absolute directory path. - [JsonPropertyName("path")] - public required string Path { get; set; } +/// Availability of built-in job tools surfaced to boundary consumers. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionSettingsBuiltInToolAvailabilitySnapshot +{ + /// Whether the create-pull-request tool is available. + [JsonPropertyName("createPullRequest")] + public bool? CreatePullRequest { get; set; } + + /// Whether the report-progress tool is available. + [JsonPropertyName("reportProgress")] + public bool? ReportProgress { get; set; } } -/// End position of the selection. +/// Redacted job settings for a session. The job nonce is excluded. [Experimental(Diagnostics.Experimental)] -public sealed class PushAttachmentSelectionDetailsEnd +public sealed class SessionSettingsJobSnapshot { - /// End character offset within the line (0-based). - [JsonPropertyName("character")] - public long Character { get; set; } + /// Availability of job-specific built-in tools. + [JsonPropertyName("builtInToolAvailability")] + public SessionSettingsBuiltInToolAvailabilitySnapshot? BuiltInToolAvailability { get; set; } - /// End line number (0-based). - [JsonPropertyName("line")] - public long Line { get; set; } + /// GitHub Actions event type for the job. + [JsonPropertyName("eventType")] + public string? EventType { get; set; } + + /// Whether this is the workflow's trigger job. + [JsonPropertyName("isTriggerJob")] + public bool? IsTriggerJob { get; set; } } -/// Start position of the selection. +/// Redacted model routing settings for a session. [Experimental(Diagnostics.Experimental)] -public sealed class PushAttachmentSelectionDetailsStart +public sealed class SessionSettingsModelSnapshot { - /// Start character offset within the line (0-based). - [JsonPropertyName("character")] - public long Character { get; set; } + /// Agent service callback URL for job and progress updates. + [JsonPropertyName("callbackUrl")] + public string? CallbackUrl { get; set; } - /// Start line number (0-based). - [JsonPropertyName("line")] - public long Line { get; set; } + /// Default reasoning effort for the selected model. + [JsonPropertyName("defaultReasoningEffort")] + public string? DefaultReasoningEffort { get; set; } + + /// Agent job identifier for the session. + [JsonPropertyName("instanceId")] + public string? InstanceId { get; set; } + + /// Selected model identifier. + [JsonPropertyName("model")] + public string? Model { get; set; } } -/// Position range of the selection within the file. +/// Online-evaluation settings safe to expose across the SDK boundary. [Experimental(Diagnostics.Experimental)] -public sealed class PushAttachmentSelectionDetails +public sealed class SessionSettingsOnlineEvaluationSnapshot { - /// End position of the selection. - [JsonPropertyName("end")] - public PushAttachmentSelectionDetailsEnd End { get => field ??= new(); set; } + /// Whether online evaluation is disabled. + [JsonPropertyName("disableOnlineEvaluation")] + public bool? DisableOnlineEvaluation { get; set; } - /// Start position of the selection. - [JsonPropertyName("start")] - public PushAttachmentSelectionDetailsStart Start { get => field ??= new(); set; } + /// Whether online-evaluation output-file generation is enabled. + [JsonPropertyName("enableOnlineEvaluationOutputFile")] + public bool? EnableOnlineEvaluationOutputFile { get; set; } } -/// Code selection attachment from an editor. -/// The selection variant of . +/// Redacted repository and GitHub host settings for a session. [Experimental(Diagnostics.Experimental)] -public partial class PushAttachmentSelection : PushAttachment +public sealed class SessionSettingsRepoSnapshot { - /// - [JsonIgnore] - public override string Type => "selection"; + /// Checked-out repository branch. + [JsonPropertyName("branch")] + public string? Branch { get; set; } - /// User-facing display name for the selection. - [JsonPropertyName("displayName")] - public required string DisplayName { get; set; } + /// Checked-out commit SHA. + [JsonPropertyName("commit")] + public string? Commit { get; set; } - /// Absolute path to the file containing the selection. - [JsonPropertyName("filePath")] - public required string FilePath { get; set; } + /// GitHub server host name. + [JsonPropertyName("host")] + public string? Host { get; set; } - /// Position range of the selection within the file. - [JsonPropertyName("selection")] - public required PushAttachmentSelectionDetails Selection { get; set; } + /// Protocol used to access the GitHub host. + [JsonPropertyName("hostProtocol")] + public string? HostProtocol { get; set; } - /// The selected text content. - [JsonPropertyName("text")] - public required string Text { get; set; } -} + /// GitHub repository database ID. + [JsonPropertyName("id")] + public double? Id { 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"; + /// Repository name. + [JsonPropertyName("name")] + public string? Name { get; set; } - /// Issue, pull request, or discussion number. - [JsonPropertyName("number")] - public required long Number { get; set; } + /// GitHub repository owner database ID. + [JsonPropertyName("ownerId")] + public double? OwnerId { get; set; } - /// Type of GitHub reference. - [JsonPropertyName("referenceType")] - public required PushAttachmentGitHubReferenceType ReferenceType { get; set; } + /// Repository owner login. + [JsonPropertyName("ownerName")] + public string? OwnerName { get; set; } - /// Current state of the referenced item (e.g., open, closed, merged). - [JsonPropertyName("state")] - public required string State { get; set; } + /// Number of commits in the pull request. + [JsonPropertyName("prCommitCount")] + public double? PrCommitCount { get; set; } - /// Title of the referenced item. - [JsonPropertyName("title")] - public required string Title { get; set; } + /// Whether the repository is writable. + [JsonPropertyName("readWrite")] + public bool? ReadWrite { get; set; } - /// URL to the referenced item on GitHub. - [JsonPropertyName("url")] - public required string Url { get; set; } + /// GitHub secret-scanning service URL. + [JsonPropertyName("secretScanningUrl")] + public string? SecretScanningUrl { get; set; } + + /// GitHub server base URL. + [JsonPropertyName("serverUrl")] + public string? ServerUrl { get; set; } } -/// Pointer to a GitHub repository. +/// Redacted validation and memory-tool settings for a session. [Experimental(Diagnostics.Experimental)] -public sealed class PushGitHubRepoRef +public sealed class SessionSettingsValidationSnapshot { - /// Numeric GitHub repository id. - [JsonPropertyName("id")] - public long? Id { get; set; } + /// Whether advisory validation is enabled. + [JsonPropertyName("advisoryEnabled")] + public bool? AdvisoryEnabled { get; set; } - /// Repository name (without owner). - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Whether CodeQL validation is enabled. + [JsonPropertyName("codeqlEnabled")] + public bool? CodeqlEnabled { get; set; } - /// Repository owner login (user or organization). - [JsonPropertyName("owner")] - public string Owner { get; set; } = string.Empty; + /// Whether code-review validation is enabled. + [JsonPropertyName("codeReviewEnabled")] + public bool? CodeReviewEnabled { get; set; } + + /// Model used for code-review validation. + [JsonPropertyName("codeReviewModel")] + public string? CodeReviewModel { get; set; } + + /// Dependabot validation timeout budget in seconds. + [JsonPropertyName("dependabotTimeout")] + public double? DependabotTimeout { get; set; } + + /// Whether the memory-store tool is enabled. + [JsonPropertyName("memoryStoreEnabled")] + public bool? MemoryStoreEnabled { get; set; } + + /// Whether the memory-vote tool is enabled. + [JsonPropertyName("memoryVoteEnabled")] + public bool? MemoryVoteEnabled { get; set; } + + /// Whether secret-scanning validation is enabled. + [JsonPropertyName("secretScanningEnabled")] + public bool? SecretScanningEnabled { get; set; } + + /// General validation timeout budget in seconds. + [JsonPropertyName("timeout")] + public double? Timeout { get; set; } } -/// Pointer to a GitHub commit. -/// The github_commit variant of . +/// Redacted, serializable view of session runtime settings for SDK boundary consumers. Secrets and raw feature flags are intentionally excluded. [Experimental(Diagnostics.Experimental)] -public partial class PushAttachmentGitHubCommit : PushAttachment +internal sealed class SessionSettingsSnapshot { - /// - [JsonIgnore] - public override string Type => "github_commit"; + /// Name of the SDK client that created the session. + [JsonPropertyName("clientName")] + public string? ClientName { get; set; } + + /// Redacted job settings. + [JsonPropertyName("job")] + public SessionSettingsJobSnapshot Job { get => field ??= new(); set; } + + /// Redacted model routing settings. + [JsonPropertyName("model")] + public SessionSettingsModelSnapshot Model { get => field ??= new(); set; } + + /// Online-evaluation settings safe for SDK consumers. + [JsonPropertyName("onlineEvaluation")] + public SessionSettingsOnlineEvaluationSnapshot OnlineEvaluation { get => field ??= new(); set; } + + /// Redacted repository and host settings. + [JsonPropertyName("repo")] + public SessionSettingsRepoSnapshot Repo { get => field ??= new(); set; } - /// First line of the commit message. - [JsonPropertyName("message")] - public required string Message { get; set; } + /// Session start time as Unix epoch milliseconds. + [JsonPropertyName("startTimeMs")] + public double? StartTimeMs { get; set; } - /// Full commit SHA. - [JsonPropertyName("oid")] - public required string Oid { get; set; } + /// Session timeout in milliseconds. + [JsonPropertyName("timeoutMs")] + public double? TimeoutMs { get; set; } - /// Repository the commit belongs to. - [JsonPropertyName("repo")] - public required PushGitHubRepoRef Repo { get; set; } + /// Redacted validation and memory-tool settings. + [JsonPropertyName("validation")] + public SessionSettingsValidationSnapshot Validation { get => field ??= new(); set; } - /// URL to the commit on GitHub. - [JsonPropertyName("url")] - public required string Url { get; set; } + /// Agent runtime version selector copied from the session settings, such as `latest` or a runtime release identifier. + [JsonPropertyName("version")] + public string? Version { get; set; } } -/// Pointer to a GitHub release. -/// The github_release variant of . +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -public partial class PushAttachmentGitHubRelease : PushAttachment +internal sealed class SessionSettingsSnapshotRequest { - /// - [JsonIgnore] - public override string Type => "github_release"; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Human-readable release name. - [JsonPropertyName("name")] - public required string Name { get; set; } +/// Result of evaluating a Rust-owned settings predicate. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionSettingsEvaluatePredicateResult +{ + /// Whether the named settings predicate evaluated to enabled. + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } +} - /// Repository the release belongs to. - [JsonPropertyName("repo")] - public required PushGitHubRepoRef Repo { get; set; } +/// Named Rust-owned settings predicate to evaluate for this session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionSettingsEvaluatePredicateRequest +{ + /// Predicate name. The runtime owns the raw feature-flag names and composition logic. + [JsonPropertyName("name")] + public SessionSettingsPredicateName Name { get; set; } - /// Git tag the release is anchored to. - [JsonPropertyName("tagName")] - public required string TagName { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// URL to the release on GitHub. - [JsonPropertyName("url")] - public required string Url { get; set; } + /// Tool name for tool-scoped predicates such as trivial-change handling. + [JsonPropertyName("toolName")] + public string? ToolName { get; set; } } -/// Pointer to a GitHub Actions job. -/// The github_actions_job variant of . +/// Content-exclusion decision for one requested path. [Experimental(Diagnostics.Experimental)] -public partial class PushAttachmentGitHubActionsJob : PushAttachment +public sealed class ContentExclusionPathCheck { - /// - [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; } + /// Whether the session's complete content-exclusion policy excludes the path. + [JsonPropertyName("excluded")] + public bool Excluded { get; set; } - /// Job id within the workflow run. - [JsonPropertyName("jobId")] - public required long JobId { get; set; } + /// The path supplied by the caller. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; +} - /// Display name of the job. - [JsonPropertyName("jobName")] - public required string JobName { get; set; } +/// Batch content-exclusion result. Callers must fail closed when policy evaluation is unavailable. +[Experimental(Diagnostics.Experimental)] +public sealed class 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. + [JsonPropertyName("available")] + public bool Available { get; set; } - /// Repository the workflow run belongs to. - [JsonPropertyName("repo")] - public required PushGitHubRepoRef Repo { get; set; } + /// Per-path decisions in request order. Empty when available is false. + [JsonPropertyName("checks")] + public IList Checks { get => field ??= []; set; } +} - /// URL to the job on GitHub. - [JsonPropertyName("url")] - public required string Url { get; set; } +/// Local file system absolute paths within the session working directory to check against its content-exclusion policy. +[Experimental(Diagnostics.Experimental)] +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; } - /// 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 . +/// Identifier of the spawned process, used to correlate streamed output and exit notifications. [Experimental(Diagnostics.Experimental)] -public partial class PushAttachmentGitHubRepository : PushAttachment +public sealed class ShellExecResult { - /// - [JsonIgnore] - public override string Type => "github_repository"; + /// Unique identifier for tracking streamed output. + [JsonPropertyName("processId")] + public string ProcessId { get; set; } = string.Empty; +} - /// Short description of the repository. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("description")] - public string? Description { get; set; } +/// Shell command to run, with optional working directory and timeout in milliseconds. +[Experimental(Diagnostics.Experimental)] +internal sealed class ShellExecRequest +{ + /// Shell command to execute. + [JsonPropertyName("command")] + public string Command { get; set; } = string.Empty; - /// 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; } + /// Working directory (defaults to session working directory). + [JsonPropertyName("cwd")] + public string? Cwd { get; set; } - /// Repository pointer. - [JsonPropertyName("repo")] - public required PushGitHubRepoRef Repo { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// URL to the repository on GitHub. - [JsonPropertyName("url")] - public required string Url { get; set; } + /// Timeout in milliseconds (default: 30000). + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] + [JsonPropertyName("timeout")] + public TimeSpan? Timeout { get; set; } } -/// One side of a file diff (head or base). +/// Indicates whether the signal was delivered; false if the process was unknown or already exited. [Experimental(Diagnostics.Experimental)] -public sealed class PushAttachmentGitHubFileDiffSide +public sealed class ShellKillResult { - /// 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; - - /// Repository the file lives in. - [JsonPropertyName("repo")] - public PushGitHubRepoRef Repo { get => field ??= new(); set; } + /// Whether the signal was sent successfully. + [JsonPropertyName("killed")] + public bool Killed { get; set; } } -/// Pointer to a single-file diff. At least one of `head` and `base` must be present. -/// The github_file_diff variant of . +/// Identifier of a process previously returned by "shell.exec" and the signal to send. [Experimental(Diagnostics.Experimental)] -public partial class PushAttachmentGitHubFileDiff : PushAttachment +internal sealed class ShellKillRequest { - /// - [JsonIgnore] - public override string Type => "github_file_diff"; - - /// File location on the base side of the diff. Absent for additions. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("base")] - public PushAttachmentGitHubFileDiffSide? Base { get; set; } + /// Process identifier returned by shell.exec. + [JsonPropertyName("processId")] + public string ProcessId { get; set; } = string.Empty; - /// File location on the head side of the diff. Absent for deletions. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("head")] - public PushAttachmentGitHubFileDiffSide? Head { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// URL to the diff on GitHub (e.g., a commit, compare, or PR-file URL). - [JsonPropertyName("url")] - public required string Url { get; set; } + /// Signal to send (default: SIGTERM). + [JsonPropertyName("signal")] + public ShellKillSignal? Signal { get; set; } } -/// One side of a tree comparison (head or base). +/// Result of a user-requested shell command. [Experimental(Diagnostics.Experimental)] -public sealed class PushAttachmentGitHubTreeComparisonSide +public sealed class UserRequestedShellCommandResult { - /// Repository the revision belongs to. - [JsonPropertyName("repo")] - public PushGitHubRepoRef Repo { get => field ??= new(); set; } + /// Error output when the execution failed. + [JsonPropertyName("error")] + public string? Error { get; set; } - /// Git revision (branch, tag, or commit SHA). - [JsonPropertyName("revision")] - public string Revision { get; set; } = string.Empty; + /// 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; } -/// Pointer to a comparison between two git revisions. -/// The github_tree_comparison variant of . +/// User-requested shell command and cancellation handle. [Experimental(Diagnostics.Experimental)] -public partial class PushAttachmentGitHubTreeComparison : PushAttachment +internal sealed class ShellExecuteUserRequestedRequest { - /// - [JsonIgnore] - public override string Type => "github_tree_comparison"; + /// Shell command to execute. + [JsonPropertyName("command")] + public string Command { get; set; } = string.Empty; - /// Base side of the comparison. - [JsonPropertyName("base")] - public required PushAttachmentGitHubTreeComparisonSide Base { get; set; } + /// Caller-provided cancellation handle for this execution. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; - /// Head side of the comparison. - [JsonPropertyName("head")] - public required PushAttachmentGitHubTreeComparisonSide Head { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// URL to the comparison on GitHub. - [JsonPropertyName("url")] - public required string Url { get; set; } +/// Cancellation result for a user-requested shell command. +[Experimental(Diagnostics.Experimental)] +public sealed class CancelUserRequestedShellCommandResult +{ + /// Whether an in-flight execution was found and signalled to cancel. + [JsonPropertyName("cancelled")] + public bool Cancelled { get; set; } } -/// Generic GitHub URL reference. -/// The github_url variant of . +/// User-requested shell execution cancellation handle. [Experimental(Diagnostics.Experimental)] -public partial class PushAttachmentGitHubUrl : PushAttachment +internal sealed class ShellCancelUserRequestedRequest { - /// - [JsonIgnore] - public override string Type => "github_url"; + /// Request ID previously passed to executeUserRequested. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; - /// URL to the GitHub resource. - [JsonPropertyName("url")] - public required string Url { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Pointer to a file in a GitHub repository at a specific ref. -/// The github_file variant of . +/// Post-compaction context window usage breakdown. [Experimental(Diagnostics.Experimental)] -public partial class PushAttachmentGitHubFile : PushAttachment +public sealed class HistoryCompactContextWindow { - /// - [JsonIgnore] - public override string Type => "github_file"; + /// Token count from non-system messages (user, assistant, tool). + [JsonPropertyName("conversationTokens")] + public long? ConversationTokens { get; set; } - /// Repository-relative path to the file. - [JsonPropertyName("path")] - public required string Path { get; set; } + /// Current total tokens in the context window (system + conversation + tool definitions). + [JsonPropertyName("currentTokens")] + public long CurrentTokens { get; set; } - /// Git ref the file is read at (branch, tag, or commit SHA). - [JsonPropertyName("ref")] - public required string Ref { get; set; } + /// Current number of messages in the conversation. + [JsonPropertyName("messagesLength")] + public long MessagesLength { get; set; } - /// Repository the file lives in. - [JsonPropertyName("repo")] - public required PushGitHubRepoRef Repo { get; set; } + /// Token count from system message(s). + [JsonPropertyName("systemTokens")] + public long? SystemTokens { get; set; } - /// URL to the file on GitHub. - [JsonPropertyName("url")] - public required string Url { 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; } } -/// Pointer to a line range inside a file in a GitHub repository. -/// The github_snippet variant of . +/// Compaction outcome with the number of tokens and messages removed, summary text, and the resulting context window breakdown. [Experimental(Diagnostics.Experimental)] -public partial class PushAttachmentGitHubSnippet : PushAttachment +public sealed class HistoryCompactResult { - /// - [JsonIgnore] - public override string Type => "github_snippet"; + /// Post-compaction context window usage breakdown. + [JsonPropertyName("contextWindow")] + public HistoryCompactContextWindow? ContextWindow { get; set; } - /// Line range the snippet covers. - [JsonPropertyName("lineRange")] - public required PushAttachmentFileLineRange LineRange { get; set; } + /// Number of messages removed during compaction. + [JsonPropertyName("messagesRemoved")] + public long MessagesRemoved { get; set; } - /// Repository-relative path to the file. - [JsonPropertyName("path")] - public required string Path { get; set; } + /// Whether compaction completed successfully. + [JsonPropertyName("success")] + public bool Success { get; set; } - /// Git ref the file is read at (branch, tag, or commit SHA). - [JsonPropertyName("ref")] - public required string Ref { 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; } - /// Repository the file lives in. - [JsonPropertyName("repo")] - public required PushGitHubRepoRef Repo { get; set; } + /// Number of tokens freed by compaction. + [JsonPropertyName("tokensRemoved")] + public long TokensRemoved { get; set; } +} - /// URL to the snippet on GitHub (with line anchor). - [JsonPropertyName("url")] - public required string Url { get; set; } +/// RPC data type for SessionHistoryCompact operations. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionHistoryCompactRequest +{ + /// 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; } + + /// 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; } } -/// Blob attachment with inline base64-encoded data. -/// The blob variant of . +/// RPC data type for SessionHistoryCompactRequestWithSession operations. [Experimental(Diagnostics.Experimental)] -public partial class PushAttachmentBlob : PushAttachment +internal sealed class SessionHistoryCompactRequestWithSession { - /// - [JsonIgnore] - public override string Type => "blob"; + /// 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; } - /// Base64-encoded content. - [Base64String] - [JsonPropertyName("data")] - public required string Data { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// User-facing display name for the attachment. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("displayName")] - public string? DisplayName { get; set; } + /// 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; } - /// MIME type of the inline data. - [JsonPropertyName("mimeType")] - public required string MimeType { 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; } } -/// Slim input shape for extension_context attachments; identity fields are runtime-derived. -/// The extension_context variant of . +/// Number of events that were removed by the truncation. [Experimental(Diagnostics.Experimental)] -public partial class PushAttachmentExtensionContext : PushAttachment +public sealed class HistoryTruncateResult { - /// - [JsonIgnore] - public override string Type => "extension_context"; + /// Failure detail when checkpointCleanupFailed is true. + [JsonPropertyName("checkpointCleanupError")] + public string? CheckpointCleanupError { get; set; } - /// Caller-supplied JSON payload (required, may be null but not undefined). - [JsonPropertyName("payload")] - public required JsonElement Payload { get; set; } + /// 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; } - /// 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; } + /// Number of events that were removed. + [JsonPropertyName("eventsRemoved")] + public long EventsRemoved { get; set; } } -/// Parameters for session.extensions.sendAttachmentsToMessage. +/// Identifier of the event to truncate to; this event and all later events are removed. [Experimental(Diagnostics.Experimental)] -internal sealed class SendAttachmentsToMessageParams +internal sealed class HistoryTruncateRequest { - /// 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; } - - /// 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; } + /// 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 the external tool call result was handled successfully. +/// A root user turn that the session can rewind to. [Experimental(Diagnostics.Experimental)] -public sealed class HandlePendingToolCallResult +public sealed class HistoryRewindPoint { - /// Whether the tool call result was handled successfully. - [JsonPropertyName("success")] - public bool Success { get; set; } -} + /// Whether at least one file in this turn or a later turn can be restored. + [JsonPropertyName("canRestoreFiles")] + public bool CanRestoreFiles { get; set; } -/// Pending external tool call request ID, with the tool result or an error describing why it failed. -[Experimental(Diagnostics.Experimental)] -internal sealed class HandlePendingToolCallRequest -{ - /// Error message if the tool call failed. - [JsonPropertyName("error")] - public string? Error { get; set; } + /// ID of the user.message event that begins the discarded suffix. + [JsonPropertyName("eventId")] + public string EventId { get; set; } = string.Empty; - /// Request ID of the pending tool call. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// Number of unique files in this turn and all later turns that have captured changes. + [JsonPropertyName("fileCount")] + public long FileCount { get; set; } - /// Tool call result (string or expanded result object). - [JsonPropertyName("result")] - public JsonElement? Result { get; set; } + /// Whether this turn was an automatically injected autopilot continuation. + [JsonPropertyName("isAutopilotContinuation")] + public bool IsAutopilotContinuation { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Lines added by this turn's captured file changes. + [JsonPropertyName("linesAdded")] + public long LinesAdded { 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; } -/// 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. +/// Rewind points and file-change-tracking availability for the session. [Experimental(Diagnostics.Experimental)] -public sealed class ToolsInitializeAndValidateResult +public sealed class HistoryListRewindPointsResult { + /// 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 SessionToolsInitializeAndValidateRequest +internal sealed class SessionHistoryListRewindPointsRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Lightweight metadata for a currently initialized session tool. +/// A file that a conversation-and-files rewind would restore. [Experimental(Diagnostics.Experimental)] -public sealed class CurrentToolMetadata +public sealed class HistoryRewindFilePreview { - /// 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; } + /// Aggregate change made across the discarded turns. + [JsonPropertyName("changeType")] + public HistoryRewindChangeType ChangeType { get; set; } - /// Raw MCP tool name for MCP-backed tools. - [JsonPropertyName("mcpToolName")] - public string? McpToolName { get; set; } + /// Lines added across the discarded turns. + [JsonPropertyName("linesAdded")] + public long LinesAdded { get; set; } - /// Model-facing tool name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Lines removed across the discarded turns. + [JsonPropertyName("linesRemoved")] + public long LinesRemoved { get; set; } - /// Optional MCP/config namespaced tool name. - [JsonPropertyName("namespacedName")] - public string? NamespacedName { get; set; } + /// Absolute path of the captured file. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; } -/// Current lightweight tool metadata snapshot for the session. +/// Files and aggregate changes for a prospective rewind. [Experimental(Diagnostics.Experimental)] -public sealed class ToolsGetCurrentMetadataResult +public sealed class HistoryPreviewRewindResult { - /// Current tool metadata, or null when tools have not been initialized yet. - [JsonPropertyName("tools")] - public IList? Tools { get; 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; } + + /// 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; } } -/// Identifies the target session. +/// Event boundary to preview for conversation-and-files rewind. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionToolsGetCurrentMetadataRequest +internal sealed class HistoryPreviewRewindRequest { + /// 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; } -/// Empty result after applying subagent settings. +/// A captured file that rewind intentionally left unchanged. [Experimental(Diagnostics.Experimental)] -public sealed class ToolsUpdateSubagentSettingsResult +public sealed class HistorySkippedFileRestore { + /// Absolute path of the skipped file. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; + + /// Reason the file was not restored. + [JsonPropertyName("reason")] + public HistoryFileRestoreSkipReason Reason { get; set; } } -/// Subagent model, reasoning effort, and context tier settings. +/// Structured outcome of a rewind request. [Experimental(Diagnostics.Experimental)] -public sealed class SubagentSettingsEntry +public sealed class HistoryRewindResult { - /// Context tier override for matching subagents. - [JsonPropertyName("contextTier")] - public SubagentSettingsEntryContextTier? ContextTier { get; set; } + /// 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 string? Error { get; set; } - /// Reasoning effort override for matching subagents. - [JsonPropertyName("effortLevel")] - public string? EffortLevel { 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; } - /// Model override for matching subagents. - [JsonPropertyName("model")] - public string? Model { 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; } } -/// Configured per-agent subagent overrides. -public sealed class UpdateSubagentSettingsRequestSubagents +/// Boundary and mode for rewinding session history. +[Experimental(Diagnostics.Experimental)] +internal sealed class HistoryRewindRequest { - /// Per-agent settings keyed by subagent agent_type. - [JsonPropertyName("agents")] - public IDictionary? Agents { get; set; } + /// ID of the user.message event that begins the discarded suffix. + [JsonPropertyName("eventId")] + public string EventId { get; set; } = string.Empty; - /// Names of subagents the user has turned off; they cannot be dispatched. - [JsonPropertyName("disabledSubagents")] - public IList? DisabledSubagents { get; set; } + /// Whether to rewind only conversation history or also restore captured files. + [JsonPropertyName("mode")] + public HistoryRewindMode Mode { get; set; } - /// Maximum number of subagents that can run concurrently; applies to usage-based billing users only. - [JsonPropertyName("maxConcurrency")] - public int? MaxConcurrency { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Maximum subagent nesting depth; applies to usage-based billing users only. - [JsonPropertyName("maxDepth")] - public int? MaxDepth { get; set; } +/// Indicates whether an in-progress background compaction was cancelled. +[Experimental(Diagnostics.Experimental)] +public sealed class 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. + [JsonPropertyName("cancelled")] + public bool Cancelled { get; set; } } -/// Subagent settings to apply to the current session. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class UpdateSubagentSettingsRequest +internal sealed class SessionHistoryCancelBackgroundCompactionRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; - - /// Subagent settings to apply, or null to clear the live session override. - [JsonPropertyName("subagents")] - public UpdateSubagentSettingsRequestSubagents? Subagents { get; set; } } -/// RPC data type for SessionCommandsList operations. +/// Indicates whether an in-progress manual compaction was aborted. [Experimental(Diagnostics.Experimental)] -public sealed class SessionCommandsListRequest +public sealed class HistoryAbortManualCompactionResult { - /// Include runtime built-in commands. - [JsonPropertyName("includeBuiltins")] - public bool? IncludeBuiltins { 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; } + /// 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; } } -/// RPC data type for SessionCommandsListRequestWithSession operations. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionCommandsListRequestWithSession +internal sealed class SessionHistoryAbortManualCompactionRequest { - /// Include runtime built-in commands. - [JsonPropertyName("includeBuiltins")] - public bool? IncludeBuiltins { 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; } - /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Result of invoking the slash command (text output, prompt to send to the agent, completion, or subcommand selection). -/// Polymorphic base type discriminated by kind. +/// Markdown summary of the conversation context (empty when not available). [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 +public sealed class HistorySummarizeForHandoffResult { - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; + /// 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; } - -/// Slash-command invocation result containing text output plus Markdown/ANSI rendering flags. -/// The text variant of . +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -public partial class SlashCommandInvocationResultText : SlashCommandInvocationResult +internal sealed class SessionHistorySummarizeForHandoffRequest { - /// - [JsonIgnore] - public override string Kind => "text"; - - /// 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; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Text output for the client to render. - [JsonPropertyName("text")] - public required string Text { get; set; } +/// What a successful clear removed. A clear that could not be applied rejects instead of reporting a count. +[Experimental(Diagnostics.Experimental)] +public sealed class HistoryClearContextResult +{ + /// 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; } } -/// 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 . +/// Parameters for clearing the conversation and seeding the window that replaces it. [Experimental(Diagnostics.Experimental)] -public partial class SlashCommandInvocationResultAgentPrompt : SlashCommandInvocationResult +internal sealed class HistoryClearContextRequest { - /// - [JsonIgnore] - 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. + /// 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 required string Prompt { get; set; } + public string Prompt { get; set; } = string.Empty; - /// True when the invocation mutated user runtime settings; consumers caching settings should refresh. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("runtimeSettingsChanged")] - public bool? RuntimeSettingsChanged { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Slash-command invocation result indicating completion, with optional message and settings-change flag. -/// The completed variant of . +/// User-facing pending queue entry, with kind and display text for a queued message, slash command, or model change. [Experimental(Diagnostics.Experimental)] -public partial class SlashCommandInvocationResultCompleted : SlashCommandInvocationResult +public sealed class QueuePendingItems { - /// - [JsonIgnore] - public override string Kind => "completed"; + /// 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; } - /// Optional user-facing message describing the completed command. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("message")] - public string? Message { get; set; } + /// Human-readable text to display for this queue entry in the UI. + [JsonPropertyName("displayText")] + public string DisplayText { get; set; } = string.Empty; - /// True when the invocation mutated user runtime settings; consumers caching settings should refresh. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("runtimeSettingsChanged")] - public bool? RuntimeSettingsChanged { 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; } + + /// Stable identity of the queued user message. Present for message rows and absent for slash commands and model changes. + [JsonPropertyName("messageId")] + public string? MessageId { get; set; } } -/// Selectable slash-command subcommand option with name, description, and optional group label. +/// Snapshot of the session's pending queued items and immediate-steering messages. [Experimental(Diagnostics.Experimental)] -public sealed class SlashCommandSelectSubcommandOption +public sealed class QueuePendingItemsResult { - /// Human-readable description of the subcommand. - [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; + /// How many leading entries of `steeringMessages` have already been folded into the running turn (and so have an emitted `user.message`), as opposed to still waiting for one. Absent for hosts that do not distinguish the two. + [JsonPropertyName("inFlightSteeringCount")] + public long? InFlightSteeringCount { get; set; } - /// Optional group label for organizing options. - [JsonPropertyName("group")] - public string? Group { 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; } - /// Subcommand name to invoke. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Display text for messages currently in the immediate steering queue (interjections sent during a running turn). + [JsonPropertyName("steeringMessages")] + public IList SteeringMessages { get => field ??= []; set; } } -/// Slash-command invocation result asking the client to present subcommand options for a parent command. -/// The select-subcommand variant of . +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -public partial class SlashCommandInvocationResultSelectSubcommand : SlashCommandInvocationResult +internal sealed class SessionQueuePendingItemsRequest { - /// - [JsonIgnore] - public override string Kind => "select-subcommand"; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Parent command name that requires subcommand selection. - [JsonPropertyName("command")] - public required string Command { get; set; } +/// Internal snapshot of native queue state for local session orchestration. +[Experimental(Diagnostics.Experimental)] +internal sealed class QueueSnapshotResult +{ + /// Insertion orders for queued items, aligned with `items`. + [JsonPropertyName("itemOrders")] + public IList? ItemOrders { get; set; } - /// Available subcommand options for the client to present. - [JsonPropertyName("options")] - public required IList Options { get; set; } + /// User-facing pending items in FIFO order. + [JsonPropertyName("items")] + public IList Items { get => field ??= []; 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; } + /// Insertion orders for immediate steering messages, aligned with `steeringMessages`. + [JsonPropertyName("steeringMessageOrders")] + public IList? SteeringMessageOrders { get; set; } - /// Human-readable title for the selection UI. - [JsonPropertyName("title")] - public required string Title { get; set; } + /// Immediate steering messages waiting for an active turn. + [JsonPropertyName("steeringMessages")] + public IList SteeringMessages { get => field ??= []; set; } } -/// Slash command name and optional raw input string to invoke. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class CommandsInvokeRequest +internal sealed class SessionQueueSnapshotRequest { - /// 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; } -/// Indicates whether the pending client-handled command was completed successfully. +/// Result of moving a queued item. [Experimental(Diagnostics.Experimental)] -public sealed class CommandsHandlePendingCommandResult +public sealed class QueueMoveItemResult { - /// Whether the command was handled successfully. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// True when the item changed position; false when it was already at the requested position. + [JsonPropertyName("changed")] + public bool Changed { get; set; } } -/// Pending command request ID and an optional error if the client handler failed. +/// Parameters for moving a queued item by stable id. [Experimental(Diagnostics.Experimental)] -internal sealed class CommandsHandlePendingCommandRequest +internal sealed class QueueMoveItemRequest { - /// Error message if the command handler failed. - [JsonPropertyName("error")] - public string? Error { get; set; } - - /// Request ID from the command invocation event. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// Stable opaque queued-item id. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { 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; } } -/// Error message produced while executing the command, if any. +/// Result of inserting a queued message. [Experimental(Diagnostics.Experimental)] -public sealed class ExecuteCommandResult +public sealed class QueueInsertAtResult { - /// Error message produced while executing the command, if any. Omitted when the handler succeeded. - [JsonPropertyName("error")] - public string? Error { get; set; } + /// Fresh stable opaque id assigned to the inserted item. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; } -/// Slash command name and argument string to execute synchronously. +/// Serializable message fields accepted by queue.insertAt. [Experimental(Diagnostics.Experimental)] -internal sealed class ExecuteCommandParams +public sealed class QueueInsertMessage { - /// Argument string to pass to the command (empty string if none). - [JsonPropertyName("args")] - public string Args { get; set; } = string.Empty; + /// Optional explicit agent mode. When omitted, the session's current mode is assigned. + [JsonPropertyName("agentMode")] + public SendAgentMode? AgentMode { get; set; } - /// Name of the slash command to invoke (without the leading '/'). - [JsonPropertyName("commandName")] - public string CommandName { get; set; } = string.Empty; + /// 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 +{ + /// Queued message contents and delivery metadata. + [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; } -/// Indicates whether the command was accepted into the local execution queue. +/// Result of removing a queued item. [Experimental(Diagnostics.Experimental)] -public sealed class EnqueueCommandResult +public sealed class QueueRemoveAtResult { - /// 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; } + /// True when the addressed item was removed. + [JsonPropertyName("removed")] + public bool Removed { get; set; } } -/// Slash-prefixed command string to enqueue for FIFO processing. +/// Parameters for removing a queued item by stable id. [Experimental(Diagnostics.Experimental)] -internal sealed class EnqueueCommandParams +internal sealed class QueueRemoveAtRequest { - /// 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; + /// Stable opaque ID of the queued item to remove. + [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. +/// Result of editing a queued message. [Experimental(Diagnostics.Experimental)] -public sealed class CommandsRespondToQueuedCommandResult +public sealed class QueueUpdateTextResult { - /// 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; } + /// True when the stored text changed. + [JsonPropertyName("updated")] + public bool Updated { get; set; } } -/// Result of the queued command execution. -/// Data type discriminated by handled. +/// Parameters for editing a single queued message. [Experimental(Diagnostics.Experimental)] -public partial class QueuedCommandResult +internal sealed class QueueUpdateTextRequest { - /// 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; } -} + /// Optional replacement prompt displayed to the user. + [JsonPropertyName("displayPrompt")] + public string? DisplayPrompt { get; set; } -/// Queued-command request ID and the result indicating whether the host executed it (and whether to stop processing further queued commands). -[Experimental(Diagnostics.Experimental)] -internal sealed class CommandsRespondToQueuedCommandRequest -{ - /// Request ID from the `command.queued` event the host is responding to. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// Stable opaque ID of the queued item to edit. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; - /// Result of the queued command execution. - [JsonPropertyName("result")] - public QueuedCommandResult Result { get => field ??= new(); set; } + /// Replacement prompt sent to the model. + [JsonPropertyName("prompt")] + public string Prompt { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Telemetry engagement ID for the session, when available. +/// Result of duplicating a queued item. [Experimental(Diagnostics.Experimental)] -public sealed class SessionTelemetryEngagement +public sealed class QueueDuplicateAtResult { - /// Current telemetry engagement ID, when available. - [JsonPropertyName("engagementId")] - public string? EngagementId { get; set; } + /// Fresh stable opaque id assigned to the duplicate. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; } -/// Identifies the target session. +/// Parameters for duplicating a queued item. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionTelemetryGetEngagementIdRequest +internal sealed class QueueDuplicateAtRequest { + /// Stable opaque ID of the queued item to duplicate. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + /// 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. +/// 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 TelemetrySetFeatureOverridesRequest +internal sealed class QueueSetDrainPausedRequest { - /// 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; } + /// Whether queued-lane draining should be paused. + [JsonPropertyName("paused")] + public bool Paused { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Transient answer generated from current conversation context. +/// Result of trying to steer a queued message into a live turn. [Experimental(Diagnostics.Experimental)] -public sealed class UIEphemeralQueryResult +public sealed class QueueSendNowResult { - /// Full assistant response text. - [JsonPropertyName("answer")] - public string Answer { get; set; } = string.Empty; + /// True when the item was accepted into the steering lane; false when no main turn was live. + [JsonPropertyName("steered")] + public bool Steered { get; set; } } -/// Transient question to answer without adding it to conversation history. +/// Parameters for steering a queued message into a live turn. [Experimental(Diagnostics.Experimental)] -internal sealed class UIEphemeralQueryRequest +internal sealed class QueueSendNowRequest { - /// 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; } - - /// 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; } - - /// Question to answer from the current conversation context. - [JsonPropertyName("question")] - public string Question { get; set; } = string.Empty; + /// Stable opaque ID of the queued item to steer into the live turn. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// The elicitation response (accept with form values, decline, or cancel). +/// Whether the native queue has pending work. [Experimental(Diagnostics.Experimental)] -public sealed class UIElicitationResponse +internal sealed class QueueHasPendingResult { - /// The user's response: accept (submitted), decline (rejected), or cancel (dismissed). - [JsonPropertyName("action")] - public UIElicitationResponseAction Action { get; set; } - - /// The form values submitted by the user (present when action is 'accept'). - [JsonPropertyName("content")] - public IDictionary? Content { get; set; } + /// True when queued or immediate native work is pending. + [JsonPropertyName("hasPending")] + public bool HasPending { get; set; } } -/// JSON Schema describing the form fields to present to the user. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -public sealed class UIElicitationSchema +internal sealed class SessionQueueHasPendingRequest { - /// 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; } - - /// Schema type indicator (always 'object'). - [JsonPropertyName("type")] - public string Type { get; set; } = string.Empty; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Prompt message and JSON schema describing the form fields to elicit from the user. +/// Whether a deferred-idle drain should run. [Experimental(Diagnostics.Experimental)] -internal sealed class UIElicitationRequest +internal sealed class QueueBeginDeferredIdleDrainResult { - /// Message describing what information is needed from the user. - [JsonPropertyName("message")] - public string Message { get; set; } = string.Empty; + /// True when the host should run finishDeferredIdleDrain asynchronously. + [JsonPropertyName("shouldDrain")] + public bool ShouldDrain { get; set; } +} - /// JSON Schema describing the form fields to present to the user. - [JsonPropertyName("requestedSchema")] - public UIElicitationSchema RequestedSchema { get => field ??= new(); 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; } -/// Indicates whether the elicitation response was accepted; false if it was already resolved by another client. +/// Action selected by the native deferred-idle drain. [Experimental(Diagnostics.Experimental)] -public sealed class UIElicitationResult +internal sealed class QueueFinishDeferredIdleDrainResult { - /// Whether the response was accepted. False if the request was already resolved by another client. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// 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; } -/// Pending elicitation request ID and the user's response (accept/decline/cancel + form values). +/// Inputs for completing a deferred-idle drain. [Experimental(Diagnostics.Experimental)] -internal sealed class UIHandlePendingElicitationRequest +internal sealed class QueueFinishDeferredIdleDrainRequest { - /// The unique request ID from the elicitation.requested event. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// Whether the host still has active background work. + [JsonPropertyName("activeBackgroundWork")] + public bool ActiveBackgroundWork { get; set; } - /// The elicitation response (accept with form values, decline, or cancel). - [JsonPropertyName("result")] - public UIElicitationResponse Result { get => field ??= new(); 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; } -/// Indicates whether the pending UI request was resolved by this call. +/// Inputs for marking session.idle deferred in native state. [Experimental(Diagnostics.Experimental)] -public sealed class UIHandlePendingResult +internal sealed class QueueDeferSessionIdleRequest { - /// 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; } + /// 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; } -/// User response for a pending user-input request, with answer text and whether it was typed freeform. +/// Indicates whether a user-facing pending item was removed. [Experimental(Diagnostics.Experimental)] -public sealed class UIUserInputResponse +public sealed class QueueRemoveMostRecentResult { - /// 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; } + /// 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; } } -/// Request ID of a pending `user_input.requested` event and the user's response. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class UIHandlePendingUserInputRequest +internal sealed class SessionQueueRemoveMostRecentRequest { - /// The unique request ID from the user_input.requested event. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; - - /// 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 sampling result payload. Omit to reject/cancel the sampling request without providing a result. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -public sealed class UIHandlePendingSamplingResponse +internal sealed class SessionQueueClearRequest { + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Request ID of a pending `sampling.requested` event and an optional sampling result payload (omit to reject). +/// Internal filter for consuming queued system notifications. [Experimental(Diagnostics.Experimental)] -internal sealed class UIHandlePendingSamplingRequest +internal sealed class QueueConsumeSystemNotificationsRequest { - /// 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; } + /// Opaque runtime-owned filter object. + [JsonPropertyName("filter")] + public JsonElement Filter { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Request ID of a pending `auto_mode_switch.requested` event and the user's response. +/// Result of enqueueing the resume-pending wake item. [Experimental(Diagnostics.Experimental)] -internal sealed class UIHandlePendingAutoModeSwitchRequest +internal sealed class QueueEnqueueResumePendingResult { - /// The unique request ID from the auto_mode_switch.requested event. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; - - /// 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; } + /// 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; } -/// The user's selected action for an exhausted session limit. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -public sealed class UISessionLimitsExhaustedResponse +internal sealed class SessionQueueProcessRequest { - /// Action selected by the user. - [JsonPropertyName("action")] - public UISessionLimitsExhaustedResponseAction Action { get; set; } - - /// AI Credits to add to the current max when action is 'add'. - [JsonPropertyName("additionalAiCredits")] - public double? AdditionalAiCredits { get; set; } - - /// New absolute max AI Credits when action is 'set'. - [JsonPropertyName("maxAiCredits")] - public double? MaxAiCredits { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Request ID of a pending `session_limits_exhausted.requested` event and the user's selected limit action. +/// Cursor, batch size, and optional long-poll/filter parameters for reading session events. [Experimental(Diagnostics.Experimental)] -internal sealed class UIHandlePendingSessionLimitsExhaustedRequest +internal sealed class EventLogReadRequest { - /// The unique request ID from the session_limits_exhausted.requested event. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// 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; } - /// The selected session-limit action. - [JsonPropertyName("response")] - public UISessionLimitsExhaustedResponse Response { get => field ??= new(); 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; } } -/// User response for a pending exit-plan-mode request, with approval state, selected action, auto-approve flag, and feedback. +/// 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 UIExitPlanModeResponse +public sealed class EventLogTailResult { - /// 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; } + /// 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; } -/// Request ID of a pending `exit_plan_mode.requested` event and the user's response. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class UIHandlePendingExitPlanModeRequest +internal sealed class SessionEventLogTailRequest { - /// 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; } -/// 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). +/// Opaque handle representing an event-type interest registration. [Experimental(Diagnostics.Experimental)] -public sealed class UIRegisterDirectAutoModeSwitchHandlerResult +public sealed class RegisterEventInterestResult { - /// 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. + /// 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; } -/// Identifies the target session. +/// Event type to register consumer interest for, used by runtime gating logic. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionUiRegisterDirectAutoModeSwitchHandlerRequest +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 handle was active and the registration count was decremented. +/// Indicates whether the operation succeeded. [Experimental(Diagnostics.Experimental)] -public sealed class UIUnregisterDirectAutoModeSwitchHandlerResult +public sealed class EventLogReleaseInterestResult { - /// True if the handle was active and decremented the counter; false if the handle was unknown. - [JsonPropertyName("unregistered")] - public bool Unregistered { get; set; } + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// Opaque handle previously returned by `registerDirectAutoModeSwitchHandler` to release. +/// Opaque handle previously returned by `registerInterest` to release. [Experimental(Diagnostics.Experimental)] -internal sealed class UIUnregisterDirectAutoModeSwitchHandlerRequest +internal sealed class ReleaseEventInterestParams { - /// Handle previously returned by `registerDirectAutoModeSwitchHandler`. + /// 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; @@ -10459,177 +17334,206 @@ internal sealed class UIUnregisterDirectAutoModeSwitchHandlerRequest public string SessionId { get; set; } = string.Empty; } -/// Indicates whether the operation succeeded. +/// Request count and cost metrics for this model. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionsConfigureResult +public sealed class UsageMetricsModelMetricRequests { - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// 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; } } -/// Source descriptor for a `session.permissions.configure` content-exclusion rule, with source name and type. +/// Per-model token-detail entry containing the accumulated token count for one token type. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionsConfigureAdditionalContentExclusionPolicyRuleSource +public sealed class UsageMetricsModelMetricTokenDetail { - /// Gets or sets the name value. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; - - /// Gets or sets the type value. - [JsonPropertyName("type")] - public string Type { get; set; } = string.Empty; + /// Accumulated token count for this token type. + [JsonPropertyName("tokenCount")] + public long TokenCount { get; set; } } -/// Single content-exclusion rule supplied to `session.permissions.configure`, with paths, match conditions, and source. +/// Token usage metrics for this model. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionsConfigureAdditionalContentExclusionPolicyRule +public sealed class UsageMetricsModelMetricUsage { - /// Gets or sets the ifAnyMatch value. - [JsonPropertyName("ifAnyMatch")] - public IList? IfAnyMatch { get; set; } + /// Total tokens read from prompt cache. + [JsonPropertyName("cacheReadTokens")] + public long CacheReadTokens { get; set; } - /// Gets or sets the ifNoneMatch value. - [JsonPropertyName("ifNoneMatch")] - public IList? IfNoneMatch { get; set; } + /// Total tokens written to prompt cache. + [JsonPropertyName("cacheWriteTokens")] + public long CacheWriteTokens { get; set; } - /// Gets or sets the paths value. - [JsonPropertyName("paths")] - public IList Paths { get => field ??= []; set; } + /// Total input tokens consumed. + [JsonPropertyName("inputTokens")] + public long InputTokens { 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; } + /// Total output tokens produced. + [JsonPropertyName("outputTokens")] + public long OutputTokens { get; set; } + + /// Total output tokens used for reasoning. + [JsonPropertyName("reasoningTokens")] + public long? ReasoningTokens { get; set; } } -/// Content-exclusion policy supplied to `session.permissions.configure`, with rules, last-updated data, and scope. +/// Per-model usage metrics, including request counts/costs, token usage, nano-AI units, and per-token-type details. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionsConfigureAdditionalContentExclusionPolicy +public sealed class UsageMetricsModelMetric { - /// Gets or sets the last_updated_at value. - [JsonPropertyName("last_updated_at")] - public JsonElement LastUpdatedAt { get; set; } + /// 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; } - /// Gets or sets the rules value. - [JsonPropertyName("rules")] - public IList Rules { get => field ??= []; set; } + /// Request count and cost metrics for this model. + [JsonPropertyName("requests")] + public UsageMetricsModelMetricRequests Requests { get => field ??= new(); set; } - /// Allowed values for the `PermissionsConfigureAdditionalContentExclusionPolicyScope` enumeration. - [JsonPropertyName("scope")] - public PermissionsConfigureAdditionalContentExclusionPolicyScope Scope { get; 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; } } -/// 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. +/// Usage attributed to one agent instance, including its identity, API duration, AI units, and per-model breakdown. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionPathsConfig +public sealed class UsageMetricsAgentMetric { - /// 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; } + /// Human-readable label for this subagent invocation, copied from the originating `subagent.started` event. For task-tool subagents this is the invocation's task description rather than the agent's configured display name, so group by `agentName` for stable per-agent labels. + [JsonPropertyName("agentDisplayName")] + public string? AgentDisplayName { 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; } + /// Configured agent name, when this is a subagent. + [JsonPropertyName("agentName")] + public string? AgentName { 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; } + /// Per-model usage for this agent, keyed by model identifier. + [JsonPropertyName("modelMetrics")] + public IDictionary ModelMetrics { get => field ??= new Dictionary(); 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; } + /// Time spent in model API calls by this agent, in milliseconds. + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] + [JsonPropertyName("totalApiDurationMs")] + public TimeSpan TotalApiDuration { get; set; } + + /// Accumulated nano-AI units cost for this agent. + [JsonPropertyName("totalNanoAiu")] + public double TotalNanoAiu { get; set; } } -/// If specified, replaces the session's approved/denied permission rules. Omit to leave the current rules unchanged. +/// Aggregated code change metrics. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionRulesSet +public sealed class UsageMetricsCodeChanges { - /// Rules that auto-approve matching requests. - [JsonPropertyName("approved")] - public IList Approved { get => field ??= []; set; } + /// Distinct file paths modified during the session. + [JsonPropertyName("filesModified")] + public IList FilesModified { get => field ??= []; set; } - /// Rules that auto-deny matching requests. - [JsonPropertyName("denied")] - public IList Denied { 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; } } -/// 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. +/// Session-wide token-detail entry containing the accumulated token count for one token type. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionUrlsConfig +public sealed class UsageMetricsTokenDetail { - /// 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; } + /// Accumulated token count for this token type. + [JsonPropertyName("tokenCount")] + public long TokenCount { get; set; } } -/// Patch of permission policy fields to apply (omit a field to leave it unchanged). +/// Accumulated session usage metrics, including premium request cost, token counts, model breakdown, and code-change totals. [Experimental(Diagnostics.Experimental)] -internal sealed class PermissionsConfigureParams +public sealed class UsageGetMetricsResult { - /// 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; } + /// Per-agent usage metrics, keyed by agent instance identifier. The main conversation uses the stable key `main`. + [JsonPropertyName("agentMetrics")] + public IDictionary? AgentMetrics { 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; } + /// Aggregated code change metrics. + [JsonPropertyName("codeChanges")] + public UsageMetricsCodeChanges CodeChanges { get => field ??= new(); 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; } + /// Currently active model identifier. + [JsonPropertyName("currentModel")] + public string? CurrentModel { 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; } + /// Input tokens from the most recent main-agent API call. + [JsonPropertyName("lastCallInputTokens")] + public long LastCallInputTokens { 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; } + /// Output tokens from the most recent main-agent API call. + [JsonPropertyName("lastCallOutputTokens")] + public long LastCallOutputTokens { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Per-model token and request metrics, keyed by model identifier. + [JsonPropertyName("modelMetrics")] + public IDictionary ModelMetrics { get => field ??= new Dictionary(); set; } - /// 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; } + /// 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; } } -/// Indicates whether the permission decision was applied; false when the request was already resolved. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionRequestResult +internal sealed class SessionUsageGetMetricsRequest { - /// Whether the permission request was handled successfully. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// The client's response to the pending permission prompt. +/// 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(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 +[JsonDerivedType(typeof(SessionLimitPredictionResultAvailable), "available")] +[JsonDerivedType(typeof(SessionLimitPredictionResultUnavailable), "unavailable")] +public partial class SessionLimitPredictionResult { /// The type discriminator. [JsonPropertyName("kind")] @@ -10637,4543 +17541,4326 @@ public partial class PermissionDecision } -/// Permission-decision request variant to approve only the current permission request. -/// The approve-once variant of . +/// Baseline data provenance for a prediction. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveOnce : PermissionDecision +public sealed class SessionLimitPredictionBaselineData { - /// - [JsonIgnore] - public override string Kind => "approve-once"; + /// End of the baseline data slice. + [JsonPropertyName("windowEnd")] + public string WindowEnd { get; set; } = string.Empty; - /// 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; } + /// Start of the baseline data slice. + [JsonPropertyName("windowStart")] + public string WindowStart { get; set; } = string.Empty; } -/// Session-scoped approval to remember (tool prompts only; omitted for path/url prompts). -/// Polymorphic base type discriminated by kind. +/// Semantic usage tier and its AI-credit cap. [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(PermissionDecisionApproveForSessionApprovalFactory), "factory")] -[JsonDerivedType(typeof(PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess), "extension-permission-access")] -public partial class PermissionDecisionApproveForSessionApproval +public sealed class SessionLimitPredictionTierOption { - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; -} + /// AI-credit cap for this tier. + [JsonPropertyName("cap")] + public double Cap { get; set; } + /// Semantic usage tier. + [JsonPropertyName("tier")] + public SessionLimitPredictionTier Tier { get; set; } +} -/// Session-scoped approval details for specific command identifiers. -/// The commands variant of . +/// Explainable AI-credit session-limit prediction. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForSessionApprovalCommands : PermissionDecisionApproveForSessionApproval +public sealed class SessionLimitPredictionDetails { - /// - [JsonIgnore] - public override string Kind => "commands"; + /// Baseline data provenance. + [JsonPropertyName("baselineData")] + public SessionLimitPredictionBaselineData BaselineData { get => field ??= new(); set; } - /// Command identifiers covered by this approval. - [JsonPropertyName("commandIdentifiers")] - public required IList CommandIdentifiers { get; 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; } } -/// Session-scoped approval details for read-only filesystem operations. -/// The read variant of . +/// The available variant of . [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForSessionApprovalRead : PermissionDecisionApproveForSessionApproval +public partial class SessionLimitPredictionResultAvailable : SessionLimitPredictionResult { /// [JsonIgnore] - public override string Kind => "read"; + public override string Kind => "available"; + + /// Predicted session limit details. + [JsonPropertyName("prediction")] + public required SessionLimitPredictionDetails Prediction { get; set; } } -/// Session-scoped approval details for filesystem write operations. -/// The write variant of . +/// The unavailable variant of . [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForSessionApprovalWrite : PermissionDecisionApproveForSessionApproval +public partial class SessionLimitPredictionResultUnavailable : SessionLimitPredictionResult { /// [JsonIgnore] - public override string Kind => "write"; + public override string Kind => "unavailable"; + + /// Reason no prediction is available. + [JsonPropertyName("reason")] + public required SessionLimitPredictionUnavailableReason Reason { get; set; } } -/// Session-scoped approval details for an MCP server tool, or all tools on the server when `toolName` is null. -/// The mcp variant of . +/// RPC data type for SessionLimitPredictionPredict operations. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForSessionApprovalMcp : PermissionDecisionApproveForSessionApproval +public sealed class SessionLimitPredictionPredictRequest { - /// - [JsonIgnore] - public override string Kind => "mcp"; - - /// MCP server name. - [JsonPropertyName("serverName")] - public required string ServerName { get; set; } + /// Client type to size for. Defaults to `cli-interactive`. + [JsonPropertyName("clientType")] + public SessionLimitPredictionClientType? ClientType { get; set; } - /// MCP tool name, or null to cover every tool on the server. - [JsonPropertyName("toolName")] - public string? ToolName { get; set; } + /// Optional model identifier override. If omitted, the session's current model is used. + [JsonPropertyName("modelId")] + public string? ModelId { get; set; } } -/// Session-scoped approval details for MCP sampling requests from a server. -/// The mcp-sampling variant of . +/// RPC data type for SessionLimitPredictionPredictRequestWithSession operations. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForSessionApprovalMcpSampling : PermissionDecisionApproveForSessionApproval +internal sealed class SessionLimitPredictionPredictRequestWithSession { - /// - [JsonIgnore] - public override string Kind => "mcp-sampling"; + /// Client type to size for. Defaults to `cli-interactive`. + [JsonPropertyName("clientType")] + public SessionLimitPredictionClientType? ClientType { get; set; } - /// MCP server name. - [JsonPropertyName("serverName")] - public required string ServerName { 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; } -/// Session-scoped approval details for writes to long-term memory. -/// The memory variant of . +/// GitHub URL for the session and a flag indicating whether remote steering is enabled. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForSessionApprovalMemory : PermissionDecisionApproveForSessionApproval +public sealed class RemoteEnableResult { - /// - [JsonIgnore] - public override string Kind => "memory"; + /// 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; } } -/// Session-scoped approval details for a custom tool, keyed by tool name. -/// The custom-tool variant of . +/// Optional remote session mode ("off", "export", or "on"); defaults to enabling both export and remote steering. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForSessionApprovalCustomTool : PermissionDecisionApproveForSessionApproval +internal sealed class RemoteEnableRequest { - /// - [JsonIgnore] - public override string Kind => "custom-tool"; + /// 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; } - /// Custom tool name. - [JsonPropertyName("toolName")] - public required string ToolName { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Session-scoped approval details for extension-management operations, optionally narrowed by operation. -/// The extension-management variant of . +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForSessionApprovalExtensionManagement : PermissionDecisionApproveForSessionApproval +internal sealed class SessionRemoteDisableRequest { - /// - [JsonIgnore] - public override string Kind => "extension-management"; - - /// Optional operation identifier; when omitted, the approval covers all extension management operations. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("operation")] - public string? Operation { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Session-scoped factory approval, optionally narrowed by approval key. -/// The factory variant of . +/// 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 partial class PermissionDecisionApproveForSessionApprovalFactory : PermissionDecisionApproveForSessionApproval +public sealed class RemoteNotifySteerableChangedResult { - /// - [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 . +/// New remote-steerability state to persist as a `session.remote_steerable_changed` event. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess : PermissionDecisionApproveForSessionApproval +internal sealed class RemoteNotifySteerableChangedRequest { - /// - [JsonIgnore] - public override string Kind => "extension-permission-access"; + /// 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; } - /// Extension name. - [JsonPropertyName("extensionName")] - public required string ExtensionName { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// 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 . +/// Current sharing status and shareable GitHub URL for a session. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForSession : PermissionDecision +public sealed class VisibilityGetResult { - /// - [JsonIgnore] - public override string Kind => "approve-for-session"; + /// 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; } - /// Session-scoped approval to remember (tool prompts only; omitted for path/url prompts). - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("approval")] - public PermissionDecisionApproveForSessionApproval? Approval { 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; } - /// URL domain to approve for the rest of the session (URL prompts only). - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("domain")] - public string? Domain { 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; } } -/// Approval to persist for this location. -/// Polymorphic base type discriminated by kind. +/// Identifies the target session. [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(PermissionDecisionApproveForLocationApprovalFactory), "factory")] -[JsonDerivedType(typeof(PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess), "extension-permission-access")] -public partial class PermissionDecisionApproveForLocationApproval +internal sealed class SessionVisibilityGetRequest { - /// 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; } - -/// Location-scoped approval details for specific command identifiers. -/// The commands variant of . +/// Effective sharing status and shareable GitHub URL after updating session visibility. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocationApprovalCommands : PermissionDecisionApproveForLocationApproval +public sealed class VisibilitySetResult { - /// - [JsonIgnore] - public override string Kind => "commands"; + /// 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; } - /// Command identifiers covered by this approval. - [JsonPropertyName("commandIdentifiers")] - public required IList CommandIdentifiers { 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; } -/// Location-scoped approval details for read-only filesystem operations. -/// The read variant of . -[Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocationApprovalRead : PermissionDecisionApproveForLocationApproval -{ - /// - [JsonIgnore] - public override string Kind => "read"; + /// 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; } } -/// Location-scoped approval details for filesystem write operations. -/// The write variant of . +/// Desired sharing status for the session. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocationApprovalWrite : PermissionDecisionApproveForLocationApproval +internal sealed class VisibilitySetRequest { - /// - [JsonIgnore] - public override string Kind => "write"; + /// 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; } } -/// Location-scoped approval details for an MCP server tool, or all tools on the server when `toolName` is null. -/// The mcp variant of . +/// Scheduled prompt entry with ID, timing (`intervalMs`, `cron`, or `at`), prompt text, recurrence, and next run time. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocationApprovalMcp : PermissionDecisionApproveForLocationApproval +public sealed class ScheduleEntry { - /// - [JsonIgnore] - public override string Kind => "mcp"; + /// Absolute fire time (epoch milliseconds) for a one-shot calendar schedule. + [JsonPropertyName("at")] + public long? At { get; set; } - /// MCP server name. - [JsonPropertyName("serverName")] - public required string ServerName { get; set; } + /// 5-field cron expression for a recurring calendar schedule, evaluated in `tz`. + [JsonPropertyName("cron")] + public string? Cron { get; set; } - /// MCP tool name, or null to cover every tool on the server. - [JsonPropertyName("toolName")] - public string? ToolName { 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; } } -/// Location-scoped approval details for MCP sampling requests from a server. -/// The mcp-sampling variant of . +/// Snapshot of the currently active recurring prompts for this session. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocationApprovalMcpSampling : PermissionDecisionApproveForLocationApproval +public sealed class ScheduleList { - /// - [JsonIgnore] - public override string Kind => "mcp-sampling"; - - /// MCP server name. - [JsonPropertyName("serverName")] - public required string ServerName { get; set; } + /// Active scheduled prompts, ordered by id. + [JsonPropertyName("entries")] + public IList Entries { get => field ??= []; set; } } -/// Location-scoped approval details for writes to long-term memory. -/// The memory variant of . +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocationApprovalMemory : PermissionDecisionApproveForLocationApproval +internal sealed class SessionScheduleListRequest { - /// - [JsonIgnore] - public override string Kind => "memory"; + /// 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 . +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocationApprovalCustomTool : PermissionDecisionApproveForLocationApproval +internal sealed class SessionScheduleHydrateRequest { - /// - [JsonIgnore] - public override string Kind => "custom-tool"; - - /// Custom tool name. - [JsonPropertyName("toolName")] - public required string ToolName { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Location-scoped approval details for extension-management operations, optionally narrowed by operation. -/// The extension-management variant of . +/// Whether the session currently has an active self-paced schedule. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocationApprovalExtensionManagement : PermissionDecisionApproveForLocationApproval +internal sealed class ScheduleHasSelfPacedResult { - /// - [JsonIgnore] - public override string Kind => "extension-management"; - - /// Optional operation identifier; when omitted, the approval covers all extension management operations. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("operation")] - public string? Operation { get; set; } + /// True when at least one active schedule is self-paced. + [JsonPropertyName("hasSelfPaced")] + public bool HasSelfPaced { get; set; } } -/// Location-scoped factory approval, optionally narrowed by approval key. -/// The factory variant of . +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocationApprovalFactory : PermissionDecisionApproveForLocationApproval +internal sealed class SessionScheduleHasSelfPacedRequest { - /// - [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; } + /// 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 . +/// Result of registering or re-arming a scheduled prompt. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess : PermissionDecisionApproveForLocationApproval +internal sealed class ScheduleAddResult { - /// - [JsonIgnore] - public override string Kind => "extension-permission-access"; + /// The registered or updated schedule entry. + [JsonPropertyName("entry")] + public ScheduleEntry? Entry { get; set; } - /// Extension name. - [JsonPropertyName("extensionName")] - public required string ExtensionName { get; set; } + /// User-facing validation error, when registration failed. + [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 . +/// Register a relative-interval scheduled prompt. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocation : PermissionDecision +internal sealed class ScheduleAddRequest { - /// - [JsonIgnore] - public override string Kind => "approve-for-location"; + /// Optional display-only prompt label. + [JsonPropertyName("displayPrompt")] + public string? DisplayPrompt { get; set; } - /// Approval to persist for this location. - [JsonPropertyName("approval")] - public required PermissionDecisionApproveForLocationApproval Approval { get; set; } + /// Human-readable interval such as `30s`, `5m`, or `2h`. + [JsonPropertyName("interval")] + public string Interval { get; set; } = string.Empty; - /// Location key (git root or cwd) to persist the approval to. - [JsonPropertyName("locationKey")] - public required string LocationKey { get; set; } -} + /// Prompt text to enqueue when the schedule fires. + [JsonPropertyName("prompt")] + public string Prompt { get; set; } = string.Empty; -/// 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 schedule should re-arm after each tick. Defaults to true. + [JsonPropertyName("recurring")] + public bool? Recurring { get; set; } - /// URL domain to approve permanently. - [JsonPropertyName("domain")] - public required string Domain { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Permission-decision request variant to reject a pending permission request, with optional feedback. -/// The reject variant of . +/// Register a cron scheduled prompt. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionReject : PermissionDecision +internal sealed class ScheduleAddCronRequest { - /// - [JsonIgnore] - public override string Kind => "reject"; + /// 5-field cron expression. + [JsonPropertyName("cron")] + public string Cron { get; set; } = string.Empty; - /// Optional feedback explaining the rejection. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("feedback")] - public string? Feedback { 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 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; } } -/// Permission-decision variant indicating no user was available to confirm the request. -/// The user-not-available variant of . +/// Register an absolute-time scheduled prompt. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionUserNotAvailable : PermissionDecision +internal sealed class ScheduleAddAtRequest { - /// - [JsonIgnore] - public override string Kind => "user-not-available"; + /// 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; } -/// Permission-decision variant indicating the request was approved. -/// The approved variant of . +/// Register a self-paced scheduled prompt. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproved : PermissionDecision +internal sealed class ScheduleAddSelfPacedRequest { - /// - [JsonIgnore] - public override string Kind => "approved"; + /// 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; } -/// Permission-decision variant indicating approval was remembered for the session, with approval details. -/// The approved-for-session variant of . +/// Re-arm a self-paced scheduled prompt. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApprovedForSession : PermissionDecision +internal sealed class ScheduleRearmSelfPacedRequest { - /// - [JsonIgnore] - public override string Kind => "approved-for-session"; + /// Epoch milliseconds when the prompt should next fire. + [JsonPropertyName("at")] + public long At { get; set; } - /// The approval to add as a session-scoped rule. - [JsonPropertyName("approval")] - public required UserToolSessionApproval Approval { 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; } -/// Permission-decision variant indicating approval was persisted for a project location, with approval details and location key. -/// The approved-for-location variant of . +/// Remove a scheduled prompt by id. The result entry is omitted if the id was unknown. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApprovedForLocation : PermissionDecision +public sealed class ScheduleStopResult { - /// - [JsonIgnore] - public override string Kind => "approved-for-location"; - - /// The approval to persist for this location. - [JsonPropertyName("approval")] - public required UserToolSessionApproval Approval { get; set; } - - /// The location key (git root or cwd) to persist the approval to. - [JsonPropertyName("locationKey")] - public required string LocationKey { get; set; } + /// The removed entry, or omitted if no entry matched. + [JsonPropertyName("entry")] + public ScheduleEntry? Entry { get; set; } } -/// Permission-decision variant indicating the request was cancelled before use, with an optional reason. -/// The cancelled variant of . +/// Identifier of the scheduled prompt to remove. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionCancelled : PermissionDecision +internal sealed class ScheduleStopRequest { - /// - [JsonIgnore] - public override string Kind => "cancelled"; + /// Id of the scheduled prompt to remove. + [JsonPropertyName("id")] + public long Id { get; set; } - /// Optional explanation of why the request was cancelled. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("reason")] - public string? Reason { 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; } -/// Permission-decision variant indicating explicit denial by permission rules, with the matching rules. -/// The denied-by-rules variant of . +/// 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 partial class PermissionDecisionDeniedByRules : PermissionDecision +public sealed class ProviderTokenAcquireRequest { - /// - [JsonIgnore] - public override string Kind => "denied-by-rules"; + /// 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 the configured provider name. + [JsonPropertyName("providerName")] + public string ProviderName { get; set; } = string.Empty; - /// 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 . +/// Result returned by an extension factory closure. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser : PermissionDecision +public sealed class FactoryExecuteResult { - /// - [JsonIgnore] - public override string Kind => "denied-no-approval-rule-and-could-not-request-from-user"; + /// Factory result value. + [JsonPropertyName("result")] + public JsonElement? Result { get; set; } } -/// Permission-decision variant indicating the user denied an interactive prompt, with optional feedback and force-reject flag. -/// The denied-interactively-by-user variant of . +/// Parameters sent to the owning extension to execute a factory closure. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionDeniedInteractivelyByUser : PermissionDecision +public sealed class FactoryExecuteRequest { - /// - [JsonIgnore] - public override string Kind => "denied-interactively-by-user"; + /// Factory input value. + [JsonPropertyName("args")] + public JsonElement Args { get; set; } - /// Optional feedback from the user explaining the denial. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("feedback")] - public string? Feedback { get; set; } + /// Opaque token identifying this factory execution attempt. + [JsonPropertyName("executionToken")] + public string ExecutionToken { get; set; } = string.Empty; - /// Whether to force-reject the current agent turn. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("forceReject")] - public bool? ForceReject { get; set; } + /// 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; } -/// Permission-decision variant indicating denial by content-exclusion policy, with path and message. -/// The denied-by-content-exclusion-policy variant of . +/// Parameters for cooperatively aborting a factory body. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionDeniedByContentExclusionPolicy : PermissionDecision +public sealed class FactoryAbortRequest { - /// - [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; } + /// Factory run identifier. + [JsonPropertyName("runId")] + public string RunId { get; set; } = string.Empty; - /// File path that triggered the exclusion. - [JsonPropertyName("path")] - public required string Path { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Permission-decision variant indicating denial by a permission request hook, with optional message and interrupt flag. -/// The denied-by-permission-request-hook variant of . +/// Describes a filesystem error. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionDeniedByPermissionRequestHook : PermissionDecision +public sealed class SessionFsError { - /// - [JsonIgnore] - public override string Kind => "denied-by-permission-request-hook"; - - /// Whether to interrupt the current agent turn. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("interrupt")] - public bool? Interrupt { get; set; } + /// Error classification. + [JsonPropertyName("code")] + public SessionFsErrorCode Code { get; set; } - /// Optional message from the hook explaining the denial. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + /// Free-form detail about the error, for logging/diagnostics. [JsonPropertyName("message")] public string? Message { get; set; } } -/// Pending permission request ID and the decision to apply (approve/reject and scope). +/// File content as a UTF-8 string, or a filesystem error if the read failed. [Experimental(Diagnostics.Experimental)] -internal sealed class PermissionDecisionRequest +public sealed class SessionFsReadFileResult { - /// Request ID of the pending permission request. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// File content as UTF-8 string. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; - /// The client's response to the pending permission prompt. - [JsonPropertyName("result")] - public PermissionDecision Result { get => field ??= new(); set; } + /// 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; } -/// Pending permission prompt reconstructed from event history, with request ID and user-facing prompt details. +/// File path, content to write, and optional mode for the client-provided session filesystem. [Experimental(Diagnostics.Experimental)] -public sealed class PendingPermissionRequest +public sealed class SessionFsWriteFileRequest { - /// The user-facing permission prompt details (commands, write, read, mcp, url, memory, custom-tool, path, hook). - [JsonPropertyName("request")] - public PermissionPromptRequest Request { get; set; } = null!; + /// Content to write. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; - /// Unique identifier for the pending permission request. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; -} + /// Optional POSIX-style mode for newly created files. + [JsonPropertyName("mode")] + public long? Mode { get; set; } -/// List of pending permission requests reconstructed from event history. -[Experimental(Diagnostics.Experimental)] -public sealed class 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. - [JsonPropertyName("items")] - public IList Items { get => field ??= []; 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; } -/// No parameters; returns currently-pending permission requests for the session. +/// File path, content to append, and optional mode for the client-provided session filesystem. [Experimental(Diagnostics.Experimental)] -internal sealed class PermissionsPendingRequestsRequest +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 operation succeeded. +/// Indicates whether the requested path exists in the client-provided session filesystem. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionsSetApproveAllResult +public sealed class SessionFsExistsResult { - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// Whether the path exists. + [JsonPropertyName("exists")] + public bool Exists { get; set; } } -/// Allow-all toggle for tool permission requests, with an optional telemetry source. +/// Path to test for existence in the client-provided session filesystem. [Experimental(Diagnostics.Experimental)] -internal sealed class PermissionsSetApproveAllRequest +public sealed class SessionFsExistsRequest { - /// Whether to auto-approve all tool permission requests. - [JsonPropertyName("enabled")] - public bool Enabled { 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; - - /// 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. +/// Filesystem metadata for the requested path, or a filesystem error if the stat failed. [Experimental(Diagnostics.Experimental)] -public sealed class AllowAllPermissionSetResult +public sealed class SessionFsStatResult { - /// Authoritative full allow-all state after the mutation. - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } + /// ISO 8601 timestamp of creation. + [JsonPropertyName("birthtime")] + public DateTimeOffset Birthtime { get; set; } - /// Authoritative allow-all mode after the mutation. - [JsonPropertyName("mode")] - public PermissionsAllowAllMode? Mode { get; set; } + /// Describes a filesystem error. + [JsonPropertyName("error")] + public SessionFsError? Error { get; set; } - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { 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; } } -/// Allow-all mode to apply for the session. +/// Path whose metadata should be returned from the client-provided session filesystem. [Experimental(Diagnostics.Experimental)] -internal sealed class PermissionsSetAllowAllRequest +public sealed class SessionFsStatRequest { - /// 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; } + /// 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; +} - /// Allow-all mode to apply. `on` enables full allow-all; `auto` enables advisory LLM auto-approval; `off` disables both. +/// 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 PermissionsAllowAllMode? Mode { get; set; } + public long? Mode { 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; } + /// 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; - - /// 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. +/// Names of entries in the requested directory, or a filesystem error if the read failed. [Experimental(Diagnostics.Experimental)] -public sealed class AllowAllPermissionState +public sealed class SessionFsReaddirResult { - /// Whether full allow-all permissions are currently active. - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } + /// Entry names in the directory. + [JsonPropertyName("entries")] + public IList Entries { get => field ??= []; set; } - /// Current allow-all mode. - [JsonPropertyName("mode")] - public PermissionsAllowAllMode? Mode { get; set; } + /// Describes a filesystem error. + [JsonPropertyName("error")] + public SessionFsError? Error { get; set; } } -/// No parameters. +/// Directory path whose entries should be listed from the client-provided session filesystem. [Experimental(Diagnostics.Experimental)] -internal sealed class PermissionsGetAllowAllRequest +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; } -/// Indicates whether the operation succeeded. +/// Directory entry returned by session filesystem `readdirWithTypes`, with name and entry type. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionsModifyRulesResult +public sealed class SessionFsReaddirWithTypesEntry { - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// Entry name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Entry type. + [JsonPropertyName("type")] + public SessionFsReaddirWithTypesEntryType Type { get; set; } } -/// Scope and add/remove instructions for modifying session- or location-scoped permission rules. +/// Entries in the requested directory paired with file/directory type information, or a filesystem error if the read failed. [Experimental(Diagnostics.Experimental)] -internal sealed class PermissionsModifyRulesParams +public sealed class SessionFsReaddirWithTypesResult { - /// 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; } + /// Directory entries with type information. + [JsonPropertyName("entries")] + public IList Entries { get => field ??= []; 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; } + /// Describes a filesystem error. + [JsonPropertyName("error")] + public SessionFsError? Error { 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; } +/// 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; } -/// Indicates whether the operation succeeded. +/// Path to remove from the client-provided session filesystem, with options for recursive removal and force. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionsSetRequiredResult +public sealed class SessionFsRmRequest { - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// 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; } -/// Toggles whether permission prompts should be bridged into session events for this client. +/// Source and destination paths for renaming or moving an entry in the client-provided session filesystem. [Experimental(Diagnostics.Experimental)] -internal sealed class PermissionsSetRequiredRequest +public sealed class SessionFsRenameRequest { - /// 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; } + /// 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; } -/// Indicates whether the operation succeeded. +/// Query results including rows, columns, and rows affected, or a filesystem error if execution failed. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionsResetSessionApprovalsResult +public sealed class SessionFsSqliteQueryResult { - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// 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; } } -/// Clears session-scoped tool permission approvals, and optionally the location-scoped ones. +/// 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)] -internal sealed class PermissionsResetSessionApprovalsRequest +public sealed class SessionFsSqliteQueryRequest { - /// Whether location-scoped approvals are cleared too. Defaults to `true`. - [JsonPropertyName("includeLocation")] - public bool? IncludeLocation { get; set; } + /// 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 operation succeeded. +/// Classified SQLite transaction failure. busyOrLocked guarantees rollback; postCommitAmbiguous must never be retried. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionsNotifyPromptShownResult +public sealed class SessionFsSqliteTransactionError { - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// Machine-readable classification of the transaction failure. + [JsonPropertyName("errorClass")] + public SessionFsSqliteTransactionErrorClass ErrorClass { get; set; } + + /// Human-readable transaction failure message. + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; } -/// Notification payload describing the permission prompt that the client just rendered. +/// Per-statement results, or a classified transaction error. [Experimental(Diagnostics.Experimental)] -internal sealed class PermissionPromptShownNotification +public sealed class SessionFsSqliteTransactionResult { - /// 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; + /// Classified transaction failure, when execution did not succeed. + [JsonPropertyName("error")] + public SessionFsSqliteTransactionError? Error { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Per-statement query results in input order. + [JsonPropertyName("results")] + public IList Results { get => field ??= []; set; } } -/// Snapshot of the session's allow-listed directories and primary working directory. +/// One statement in an atomic SQLite transaction. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionPathsList +public sealed class SessionFsSqliteTransactionStatement { - /// All directories currently allowed for tool access on this session. - [JsonPropertyName("directories")] - public IList Directories { get => field ??= []; set; } + /// Optional named bind parameters. + [JsonPropertyName("params")] + public IDictionary? Params { get; set; } - /// The primary working directory for this session. - [JsonPropertyName("primary")] - public string Primary { get; set; } = string.Empty; + /// 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; } } -/// No parameters; returns the session's allow-listed directories. +/// Statements to execute atomically. Providers apply busy handling for every call. [Experimental(Diagnostics.Experimental)] -internal sealed class PermissionsPathsListRequest +public sealed class SessionFsSqliteTransactionRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; + + /// Ordered SQL statements to execute in one transaction. + [JsonPropertyName("statements")] + public IList Statements { get => field ??= []; set; } } -/// Indicates whether the operation succeeded. +/// Indicates whether the per-session SQLite database already exists. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionsPathsAddResult +public sealed class SessionFsSqliteExistsResult { - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// Whether the session database already exists. + [JsonPropertyName("exists")] + public bool Exists { get; set; } } -/// Directory path to add to the session's allowed directories. -[Experimental(Diagnostics.Experimental)] -internal sealed class PermissionPathsAddParams +/// Identifies the target session. +public sealed class SessionFsSqliteExistsRequest { - /// 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; - /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Indicates whether the operation succeeded. +/// Canvas open result returned by the provider. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionsPathsUpdatePrimaryResult +public sealed class CanvasProviderOpenResult { - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } -} + /// Provider-supplied status text. + [JsonPropertyName("status")] + public string? Status { get; set; } -/// Directory path to set as the session's new primary working directory. -[Experimental(Diagnostics.Experimental)] -internal sealed class PermissionPathsUpdatePrimaryParams -{ - /// Directory to set as the new primary working directory for the session's permission policy. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// Provider-supplied title. + [JsonPropertyName("title")] + public string? Title { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// URL for web-rendered canvases. + [JsonPropertyName("url")] + public string? Url { get; set; } } -/// Indicates whether the supplied path is within the session's allowed directories. +/// Host capabilities. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionPathsAllowedCheckResult +public sealed class CanvasHostContextCapabilities { - /// Whether the path is within the session's allowed directories. - [JsonPropertyName("allowed")] - public bool Allowed { get; set; } + /// Whether canvas rendering is supported. + [JsonPropertyName("canvases")] + public bool? Canvases { get; set; } } -/// Path to evaluate against the session's allowed directories. +/// Host context supplied by the runtime. [Experimental(Diagnostics.Experimental)] -internal sealed class PermissionPathsAllowedCheckParams +public sealed class CanvasHostContext { - /// Path to check against the session's allowed directories. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; - - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Host capabilities. + [JsonPropertyName("capabilities")] + public CanvasHostContextCapabilities? Capabilities { get; set; } } -/// Indicates whether the supplied path is within the session's workspace directory. +/// Session context supplied by the runtime. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionPathsWorkspaceCheckResult +public sealed class CanvasSessionContext { - /// Whether the path is within the session workspace directory. - [JsonPropertyName("allowed")] - public bool Allowed { get; set; } + /// Active session working directory, when known. + [JsonPropertyName("workingDirectory")] + public string? WorkingDirectory { get; set; } } -/// Path to evaluate against the session's workspace (primary) directory. +/// Canvas open parameters sent to the provider. [Experimental(Diagnostics.Experimental)] -internal sealed class PermissionPathsWorkspaceCheckParams +public sealed class CanvasProviderOpenRequest { - /// Path to check against the session workspace directory. - [JsonPropertyName("path")] - public string Path { 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; } + + /// 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; } -/// Resolved location-permissions key and type. +/// Canvas close parameters sent to the provider. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionLocationResolveResult +public sealed class CanvasProviderCloseRequest { - /// Location key used in the location-permissions store. - [JsonPropertyName("locationKey")] - public string LocationKey { get; set; } = string.Empty; + /// Provider-local canvas identifier. + [JsonPropertyName("canvasId")] + public string CanvasId { get; set; } = string.Empty; - /// Whether the location is a git repo or directory. - [JsonPropertyName("locationType")] - public PermissionLocationType LocationType { get; set; } -} + /// 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; } -/// Working directory to resolve into a location-permissions key. -[Experimental(Diagnostics.Experimental)] -internal sealed class PermissionLocationResolveParams -{ /// 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; } -/// Summary of persisted location permissions applied to the session. +/// Canvas action invocation parameters sent to the provider. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionLocationApplyResult +public sealed class CanvasProviderInvokeActionRequest { - /// Number of persisted allowed directories added to the live path manager. - [JsonPropertyName("appliedDirectoryCount")] - public long AppliedDirectoryCount { get; set; } + /// Action name to invoke. + [JsonPropertyName("actionName")] + public string ActionName { get; set; } = string.Empty; - /// Number of location-scoped rules added to the live permission service. - [JsonPropertyName("appliedRuleCount")] - public long AppliedRuleCount { get; set; } + /// Provider-local canvas identifier. + [JsonPropertyName("canvasId")] + public string CanvasId { get; set; } = string.Empty; - /// Location-scoped rules applied to the live permission service. - [JsonPropertyName("appliedRules")] - public IList AppliedRules { get => field ??= []; set; } + /// Owning provider identifier. + [JsonPropertyName("extensionId")] + public string ExtensionId { get; set; } = string.Empty; - /// Whether a different location was applied since the previous apply call. - [JsonPropertyName("changed")] - public bool Changed { get; set; } + /// Host context supplied by the runtime. + [JsonPropertyName("host")] + public CanvasHostContext? Host { get; set; } - /// Location key used in the location-permissions store. - [JsonPropertyName("locationKey")] - public string LocationKey { get; set; } = string.Empty; + /// Action input. + [JsonPropertyName("input")] + public JsonElement? Input { get; set; } - /// Whether the location is a git repo or directory. - [JsonPropertyName("locationType")] - public PermissionLocationType LocationType { 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; } -/// 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; - - /// Working directory whose persisted location permissions should be applied. - [JsonPropertyName("workingDirectory")] - public string WorkingDirectory { get; set; } = string.Empty; } -/// Indicates whether the operation succeeded. +/// Opaque integrator-owned process launch profile for one extension entrypoint. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionsLocationsAddToolApprovalResult +public sealed class ExtensionLaunchProfile { - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// 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; } -/// Tool approval to persist and apply. -/// Polymorphic base type discriminated by kind. +/// The launch profile for a supported entrypoint. Omit launch when the provider does not support the entrypoint. [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(PermissionsLocationsAddToolApprovalDetailsFactory), "factory")] -[JsonDerivedType(typeof(PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess), "extension-permission-access")] -public partial class PermissionsLocationsAddToolApprovalDetails +public sealed class ExtensionLaunchProviderResolveResult { - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; + /// Opaque launch profile, omitted when this provider does not support the entrypoint. + [JsonPropertyName("launch")] + public ExtensionLaunchProfile? Launch { get; set; } } - -/// Location-persisted tool approval details for specific command identifiers. -/// The commands variant of . +/// A discovered extension entrypoint that the registered integrator may classify and resolve to an opaque launch profile. [Experimental(Diagnostics.Experimental)] -public partial class PermissionsLocationsAddToolApprovalDetailsCommands : PermissionsLocationsAddToolApprovalDetails +public sealed class ExtensionLaunchProviderResolveRequest { - /// - [JsonIgnore] - public override string Kind => "commands"; + /// Source-qualified extension identifier. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; - /// Command identifiers covered by this approval. - [JsonPropertyName("commandIdentifiers")] - public required IList CommandIdentifiers { get; set; } -} + /// Absolute path to the discovered extension entrypoint. + [JsonPropertyName("modulePath")] + public string ModulePath { get; set; } = string.Empty; -/// 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"; + /// 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; } } -/// Location-persisted tool approval details for filesystem write operations. -/// The write variant of . +/// Acknowledgement. Returning successfully simply means the SDK accepted the start frame; it does not imply the request will succeed. [Experimental(Diagnostics.Experimental)] -public partial class PermissionsLocationsAddToolApprovalDetailsWrite : PermissionsLocationsAddToolApprovalDetails +public sealed class LlmInferenceHttpRequestStartResult { - /// - [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 . +/// The head of an outbound model-layer HTTP request. [Experimental(Diagnostics.Experimental)] -public partial class PermissionsLocationsAddToolApprovalDetailsMcp : PermissionsLocationsAddToolApprovalDetails +public sealed class LlmInferenceHttpRequestStartRequest { - /// - [JsonIgnore] - public override string Kind => "mcp"; + /// 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; } - /// MCP server name. - [JsonPropertyName("serverName")] - public required string ServerName { 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; } - /// MCP tool name, or null to cover every tool on the server. - [JsonPropertyName("toolName")] - public string? ToolName { get; set; } -} + /// HTTP request headers, preserving multiple values per name. + [JsonPropertyName("headers")] + public IDictionary> Headers { get => field ??= new Dictionary>(); set; } -/// Location-persisted tool approval details for MCP sampling requests from a server. -/// The mcp-sampling variant of . -[Experimental(Diagnostics.Experimental)] -public partial class PermissionsLocationsAddToolApprovalDetailsMcpSampling : PermissionsLocationsAddToolApprovalDetails -{ - /// - [JsonIgnore] - public override string Kind => "mcp-sampling"; + /// 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; } - /// MCP server name. - [JsonPropertyName("serverName")] - public required string ServerName { 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; } -/// Location-persisted tool approval details for writes to long-term memory. -/// The memory variant of . +/// Acknowledgement. The SDK is free to ignore the ack and treat chunk delivery as fire-and-forget. [Experimental(Diagnostics.Experimental)] -public partial class PermissionsLocationsAddToolApprovalDetailsMemory : PermissionsLocationsAddToolApprovalDetails +public sealed class LlmInferenceHttpRequestChunkResult { - /// - [JsonIgnore] - public override string Kind => "memory"; } -/// Location-persisted tool approval details for a custom tool, keyed by tool name. -/// The custom-tool variant of . +/// A request body chunk or cancellation signal. [Experimental(Diagnostics.Experimental)] -public partial class PermissionsLocationsAddToolApprovalDetailsCustomTool : PermissionsLocationsAddToolApprovalDetails +public sealed class LlmInferenceHttpRequestChunkRequest { - /// - [JsonIgnore] - public override string Kind => "custom-tool"; + /// 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; } - /// Custom tool name. - [JsonPropertyName("toolName")] - public required string ToolName { 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; } -/// 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"; + /// 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 operation identifier; when omitted, the approval covers all extension management operations. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("operation")] - public string? Operation { get; set; } -} + /// Optional human-readable reason for the cancellation, propagated for logging. + [JsonPropertyName("cancelReason")] + public string? CancelReason { get; set; } -/// Location-persisted factory approval, optionally narrowed by approval key. -/// The factory variant of . -[Experimental(Diagnostics.Experimental)] -public partial class PermissionsLocationsAddToolApprovalDetailsFactory : PermissionsLocationsAddToolApprovalDetails -{ - /// - [JsonIgnore] - public override string Kind => "factory"; + /// 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; - /// 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; } + /// 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; } -/// Location-persisted tool approval details for an extension's permission-gated capability access, keyed by extension name. -/// The extension-permission-access variant of . +/// Client environment metadata describing the process that produced a telemetry event. [Experimental(Diagnostics.Experimental)] -public partial class PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess : PermissionsLocationsAddToolApprovalDetails +public sealed class GitHubTelemetryClientInfo { - /// - [JsonIgnore] - public override string Kind => "extension-permission-access"; + /// Copilot CLI version string. + [JsonPropertyName("cli_version")] + public string CliVersion { get; set; } = string.Empty; - /// Extension name. - [JsonPropertyName("extensionName")] - public required string ExtensionName { get; set; } -} + /// Name of the client application. + [JsonPropertyName("client_name")] + public string? ClientName { get; set; } -/// Location-scoped tool approval to persist. -[Experimental(Diagnostics.Experimental)] -internal sealed class PermissionLocationAddToolApprovalParams -{ - /// Tool approval to persist and apply. - [JsonPropertyName("approval")] - public PermissionsLocationsAddToolApprovalDetails Approval { get => field ??= new(); set; } + /// Type of client. + [JsonPropertyName("client_type")] + public string? ClientType { get; set; } - /// Location key (git root or cwd) to persist the approval to. - [JsonPropertyName("locationKey")] - public string LocationKey { get; set; } = string.Empty; + /// Copilot subscription plan, when known. + [JsonPropertyName("copilot_plan")] + public string? CopilotPlan { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Number of logical CPU cores on the host. + [JsonPropertyName("cpu_count")] + public long? CpuCount { get; set; } + + /// Distinct CPU model names for the host, comma-separated. + [JsonPropertyName("cpu_model")] + public string? CpuModel { 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; } -/// Folder trust check result. +/// 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 FolderTrustCheckResult +public sealed class GitHubTelemetryEvent { - /// Whether the folder is trusted. - [JsonPropertyName("trusted")] - public bool Trusted { get; set; } + /// 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; } } -/// Folder path to check for trust. +/// 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)] -internal sealed class FolderTrustCheckParams +public sealed class GitHubTelemetryNotification { - /// Folder path to check. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// The telemetry event, in the runtime's native GitHub-shaped telemetry format. + [JsonPropertyName("event")] + public GitHubTelemetryEvent Event { get => field ??= new(); set; } - /// Target session identifier. + /// 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; } = string.Empty; + public string? SessionId { get; set; } } -/// Indicates whether the operation succeeded. +/// SDK host response to a GitHub credential request. +/// Polymorphic base type discriminated by kind. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionsFolderTrustAddTrustedResult +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(GitHubTokenAcquireResultToken), "token")] +[JsonDerivedType(typeof(GitHubTokenAcquireResultCancelled), "cancelled")] +public partial class GitHubTokenAcquireResult { - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; } -/// Folder path to add to trusted folders. + +/// The token variant of . [Experimental(Diagnostics.Experimental)] -internal sealed class FolderTrustAddParams +public partial class GitHubTokenAcquireResultToken : GitHubTokenAcquireResult { - /// Folder path to mark as trusted. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "token"; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// GitHub access token acquired by the SDK host. + [JsonPropertyName("accessToken")] + public required string AccessToken { get; set; } -/// Indicates whether the operation succeeded. -[Experimental(Diagnostics.Experimental)] -public sealed class PermissionsUrlsSetUnrestrictedModeResult -{ - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// Remaining token lifetime in seconds when callback execution completes. It must exceed the one-hour preflight refresh threshold. + [JsonPropertyName("expiresIn")] + public required long ExpiresIn { get; set; } + + /// OAuth token type. Defaults to bearer when omitted. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("tokenType")] + public string? TokenType { get; set; } } -/// Whether the URL-permission policy should run in unrestricted mode. +/// The cancelled variant of . [Experimental(Diagnostics.Experimental)] -internal sealed class PermissionUrlsSetUnrestrictedModeParams +public partial class GitHubTokenAcquireResultCancelled : GitHubTokenAcquireResult { - /// 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; + /// + [JsonIgnore] + public override string Kind => "cancelled"; } -/// The repository the remote session targets. +/// Asks the SDK client to acquire a GitHub access token from an opaque callback registration. [Experimental(Diagnostics.Experimental)] -public sealed class MetadataSnapshotRemoteMetadataRepository +public sealed class GitHubTokenAcquireRequest { - /// The branch the remote session is operating on. - [JsonPropertyName("branch")] - public string Branch { get; set; } = string.Empty; + /// Effective GitHub host for which the callback must return a token. + [JsonPropertyName("host")] + public string Host { get; set; } = string.Empty; - /// The GitHub repository name (without owner). - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Why the runtime is requesting a GitHub credential. + [JsonPropertyName("reason")] + public GitHubTokenAcquireReason Reason { get; set; } - /// The GitHub owner (user or organization) of the target repository. - [JsonPropertyName("owner")] - public string Owner { get; set; } = string.Empty; + /// Opaque identifier generated by the SDK for this callback registration. + [JsonPropertyName("registrationId")] + public string RegistrationId { get; set; } = string.Empty; + + /// Session receiving the token. Absent only before a cloud session has been assigned its id. + [JsonPropertyName("sessionId")] + public string? SessionId { get; set; } } -/// Remote-session-specific metadata. Populated only when `isRemote` is true. Fields are immutable for the lifetime of the session. +/// Hook event name. Discovery emits the file-configurable subset; SDK callbacks additionally support callback-only events. [Experimental(Diagnostics.Experimental)] -public sealed class MetadataSnapshotRemoteMetadata +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct HookType : IEquatable { - /// The pull request number the remote session is associated with, if any. - [JsonPropertyName("pullRequestNumber")] - public long? PullRequestNumber { get; set; } - - /// The repository the remote session targets. - [JsonPropertyName("repository")] - public MetadataSnapshotRemoteMetadataRepository Repository { get => field ??= new(); set; } + private readonly string? _value; - /// 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; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public HookType(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Whether the remote task originated from Copilot Coding Agent (cca) or a CLI `--remote` invocation. - [JsonPropertyName("taskType")] - public MetadataSnapshotRemoteMetadataTaskType? TaskType { get; set; } -} + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; -/// Public-facing projection of workspace metadata for SDK / TUI consumers. -public sealed class SessionMetadataSnapshotWorkspace -{ - /// Branch checked out at session start, if any. - [JsonPropertyName("branch")] - public string? Branch { get; set; } + /// Runs before a tool is invoked. + public static HookType PreToolUse { get; } = new("preToolUse"); - /// ISO 8601 timestamp when the workspace was created. - [JsonPropertyName("created_at")] - public DateTimeOffset? CreatedAt { get; set; } + /// Runs before an MCP tool is invoked. + public static HookType PreMcpToolCall { get; } = new("preMcpToolCall"); - /// Current working directory at session start. - [JsonPropertyName("cwd")] - public string? Cwd { get; set; } + /// Runs after a tool completes successfully. + public static HookType PostToolUse { get; } = new("postToolUse"); - /// Resolved git root for cwd, if any. - [JsonPropertyName("git_root")] - public string? GitRoot { get; set; } + /// Runs after a tool fails. + public static HookType PostToolUseFailure { get; } = new("postToolUseFailure"); - /// Repository host type, if known. - [JsonPropertyName("host_type")] - public WorkspaceSummaryHostType? HostType { get; set; } + /// Runs after the user submits a prompt. + public static HookType UserPromptSubmitted { get; } = new("userPromptSubmitted"); - /// 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; + /// Runs after the runtime transforms the submitted prompt for the model, before it is added to session history. + public static HookType UserPromptTransformed { get; } = new("userPromptTransformed"); - /// Display name for the session, if set. - [JsonPropertyName("name")] - public string? Name { get; set; } + /// Runs when a session starts. + public static HookType SessionStart { get; } = new("sessionStart"); - /// Repository identifier in 'owner/repo' or 'org/project/repo' format, if any. - [JsonPropertyName("repository")] - public string? Repository { get; set; } + /// Runs when a session ends. + public static HookType SessionEnd { get; } = new("sessionEnd"); - /// ISO 8601 timestamp when the workspace was last updated. - [JsonPropertyName("updated_at")] - public DateTimeOffset? UpdatedAt { get; set; } + /// Runs after an agent result is produced. + public static HookType PostResult { get; } = new("postResult"); - /// Whether the display name was explicitly set by the user. - [JsonPropertyName("user_named")] - public bool? UserNamed { get; set; } -} + /// Runs before a pull request description is generated. + public static HookType PrePRDescription { get; } = new("prePRDescription"); -/// Point-in-time snapshot of slow-changing session identifier and state fields. -[Experimental(Diagnostics.Experimental)] -public sealed class 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. - [JsonPropertyName("alreadyInUse")] - public bool AlreadyInUse { get; set; } + /// Runs when the agent encounters an error. + public static HookType ErrorOccurred { get; } = new("errorOccurred"); - /// Runtime client name associated with the session (telemetry identifier). - [JsonPropertyName("clientName")] - public string? ClientName { get; set; } + /// Runs when the agent stops. + public static HookType AgentStop { get; } = new("agentStop"); - /// The current agent mode for this session (e.g., 'interactive', 'plan', 'autopilot'). - [JsonPropertyName("currentMode")] - public MetadataSnapshotCurrentMode CurrentMode { get; set; } + /// Runs when a subagent starts. + public static HookType SubagentStart { get; } = new("subagentStart"); - /// User-provided name supplied at session construction (via `--name`), if any. Immutable after construction. - [JsonPropertyName("initialName")] - public string? InitialName { get; set; } + /// Runs when a subagent stops. + public static HookType SubagentStop { get; } = new("subagentStop"); - /// 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; } + /// Runs before conversation context is compacted. + public static HookType PreCompact { get; } = new("preCompact"); - /// 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; } + /// Runs when the agent requests permission. + public static HookType PermissionRequest { get; } = new("permissionRequest"); - /// 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; } + /// Runs when the agent emits a notification. + public static HookType Notification { get; } = new("notification"); - /// Currently selected model identifier, if any. - [JsonPropertyName("selectedModel")] - public string? SelectedModel { get; set; } + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(HookType left, HookType right) => left.Equals(right); - /// The unique identifier of the session. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(HookType left, HookType right) => !(left == right); - /// Current session limits, or null when no limits are active. - [JsonPropertyName("sessionLimits")] - public SessionLimitsConfig? SessionLimits { get; set; } + /// + public override bool Equals(object? obj) => obj is HookType other && Equals(other); - /// ISO 8601 timestamp of when the session started. - [JsonPropertyName("startTime")] - public DateTimeOffset StartTime { get; set; } + /// + public bool Equals(HookType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// Short human-readable summary of the session, if known. Omitted when no summary has been generated. - [JsonPropertyName("summary")] - public string? Summary { get; set; } + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - /// Absolute path to the session's current working directory. - [JsonPropertyName("workingDirectory")] - public string WorkingDirectory { get; set; } = string.Empty; + /// + public override string ToString() => Value; - /// 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; } + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override HookType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } - /// 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; } + /// + public override void Write(Utf8JsonWriter writer, HookType value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(HookType)); + } + } } -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionMetadataSnapshotRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} -/// Indicates whether the local session is currently processing a turn or background continuation. +/// Configuration tier that contributed a discovered hook action. [Experimental(Diagnostics.Experimental)] -public sealed class MetadataIsProcessingResult +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct HookOrigin : IEquatable { - /// 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; } -} + private readonly string? _value; -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionMetadataIsProcessingRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public HookOrigin(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } -/// Current activity flags for the session. -[Experimental(Diagnostics.Experimental)] -public sealed class SessionActivity -{ - /// Whether an in-flight operation can currently be aborted. - [JsonPropertyName("abortable")] - public bool Abortable { get; set; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Whether the session currently has active work, including running turns or tasks. - [JsonPropertyName("hasActiveWork")] - public bool HasActiveWork { get; set; } + /// Hook loaded from user settings or the user's hook directory. + public static HookOrigin User { get; } = new("user"); + + /// Hook loaded from repository settings or the repository hook directory. + public static HookOrigin Repository { get; } = new("repository"); + + /// Hook provided by an enabled installed or explicit plugin. Projectless rows omit projectPath and do not expand a project directory. + public static HookOrigin Plugin { get; } = new("plugin"); + + /// Hook enforced by centrally managed policy. + public static HookOrigin Policy { get; } = new("policy"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(HookOrigin left, HookOrigin right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(HookOrigin left, HookOrigin right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is HookOrigin other && Equals(other); + + /// + public bool Equals(HookOrigin 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 HookOrigin Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, HookOrigin value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(HookOrigin)); + } + } } -/// Identifies the target session. + +/// Resolved Anthropic adaptive-thinking capability for a model. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionMetadataActivityRequest +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct AdaptiveThinkingSupport : IEquatable { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + private readonly string? _value; -/// Token-usage breakdown for the session's current context window. -public sealed class MetadataContextInfoResultContextInfo -{ - /// Output reserve plus tokens after the buffer-exhaustion blocking threshold (default 95%). - [JsonPropertyName("bufferTokens")] - public long BufferTokens { get; set; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public AdaptiveThinkingSupport(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Token count at which background compaction starts (configurable percentage of promptTokenLimit). - [JsonPropertyName("compactionThreshold")] - public long CompactionThreshold { get; set; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Tokens consumed by user/assistant/tool messages. - [JsonPropertyName("conversationTokens")] - public long ConversationTokens { get; set; } + /// 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); - /// Prompt token limit plus the model's full output token limit. - [JsonPropertyName("limit")] - public long Limit { get; set; } + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AdaptiveThinkingSupport left, AdaptiveThinkingSupport right) => !(left == right); - /// Tokens consumed by MCP tool definitions (subset of toolDefinitionsTokens, excludes deferred tools). - [JsonPropertyName("mcpToolsTokens")] - public long McpToolsTokens { get; set; } + /// + public override bool Equals(object? obj) => obj is AdaptiveThinkingSupport other && Equals(other); - /// The model used for token counting. - [JsonPropertyName("modelName")] - public string ModelName { get; set; } = string.Empty; + /// + public bool Equals(AdaptiveThinkingSupport other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// Maximum prompt tokens allowed by the model (or DEFAULT_TOKEN_LIMIT if unspecified). - [JsonPropertyName("promptTokenLimit")] - public long PromptTokenLimit { get; set; } + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - /// Tokens consumed by the system prompt. - [JsonPropertyName("systemTokens")] - public long SystemTokens { get; set; } + /// + public override string ToString() => Value; - /// Tokens consumed by tool definitions sent to the model (excludes deferred tools). - [JsonPropertyName("toolDefinitionsTokens")] - public long ToolDefinitionsTokens { get; set; } + /// 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)); + } - /// Sum of system, conversation and tool-definition tokens. - [JsonPropertyName("totalTokens")] - public long TotalTokens { get; set; } + /// + public override void Write(Utf8JsonWriter writer, AdaptiveThinkingSupport value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AdaptiveThinkingSupport)); + } + } } -/// Token breakdown for the session's current context window, or null if uninitialized. -[Experimental(Diagnostics.Experimental)] -public sealed class 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). - [JsonPropertyName("contextInfo")] - public MetadataContextInfoResultContextInfo? ContextInfo { get; set; } -} -/// Model identifier and token limits used to compute the context-info breakdown. +/// Model capability category for grouping in the model picker. [Experimental(Diagnostics.Experimental)] -internal sealed class MetadataContextInfoRequest +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct ModelPickerCategory : IEquatable { - /// Maximum output tokens allowed by the target model. Pass 0 if unknown. - [JsonPropertyName("outputTokenLimit")] - public long OutputTokenLimit { get; set; } + private readonly string? _value; - /// Maximum prompt tokens allowed by the target model. Pass 0 to use the runtime default. - [JsonPropertyName("promptTokenLimit")] - public long PromptTokenLimit { get; set; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public ModelPickerCategory(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// 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; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Lightweight model category optimized for faster, lower-cost interactions. + public static ModelPickerCategory Lightweight { get; } = new("lightweight"); -/// Successful compaction history for the session. -public sealed class MetadataContextAttributionResultContextAttributionCompactions -{ - /// Number of successful compactions in this session. - [JsonPropertyName("count")] - public long Count { get; set; } -} + /// Versatile model category suitable for a broad range of tasks. + public static ModelPickerCategory Versatile { get; } = new("versatile"); -/// RPC data type for MetadataContextAttributionResultContextAttributionEntry operations. -public sealed class MetadataContextAttributionResultContextAttributionEntry -{ - /// 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; } + /// Powerful model category optimized for complex tasks. + public static ModelPickerCategory Powerful { get; } = new("powerful"); - /// 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; + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ModelPickerCategory left, ModelPickerCategory right) => left.Equals(right); - /// 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; + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ModelPickerCategory left, ModelPickerCategory right) => !(left == right); - /// 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; + /// + public override bool Equals(object? obj) => obj is ModelPickerCategory other && Equals(other); - /// 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; } + /// + public bool Equals(ModelPickerCategory other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// Token count currently in context attributable to this entry. - [JsonPropertyName("tokens")] - public long Tokens { get; set; } -} + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); -/// Per-source token attribution snapshot for the current context window. The heaviest individual messages are available separately via `metadata.getContextHeaviestMessages`. -public sealed class MetadataContextAttributionResultContextAttribution -{ - /// Successful compaction history for the session. - [JsonPropertyName("compactions")] - public MetadataContextAttributionResultContextAttributionCompactions Compactions { get => field ??= new(); set; } + /// + public override string ToString() => Value; - /// 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; } + /// 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)); + } - /// 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; } + /// + public override void Write(Utf8JsonWriter writer, ModelPickerCategory value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ModelPickerCategory)); + } + } } -/// Per-source attribution breakdown for the session's current context window, or null if uninitialized. -[Experimental(Diagnostics.Experimental)] -public sealed class MetadataContextAttributionResult -{ - /// 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. +/// Relative cost tier for token-based billing users. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionMetadataGetContextAttributionRequest +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct ModelPickerPriceCategory : IEquatable { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + private readonly string? _value; -/// A single large message currently in context. -[Experimental(Diagnostics.Experimental)] -public sealed class ContextHeaviestMessage -{ - /// Stable identifier for this message within the snapshot. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public ModelPickerPriceCategory(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Human-readable source label, e.g. `tool: bash` or `skill: tmux`. Presentation-only. - [JsonPropertyName("label")] - public string Label { get; set; } = string.Empty; + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Role of the chat message (`user`, `assistant`, or `tool`). - [JsonPropertyName("role")] - public string Role { get; set; } = string.Empty; + /// Lowest relative token cost tier. + public static ModelPickerPriceCategory Low { get; } = new("low"); - /// Token count currently in context for this individual message. - [JsonPropertyName("tokens")] - public long Tokens { get; set; } -} + /// Medium relative token cost tier. + public static ModelPickerPriceCategory Medium { get; } = new("medium"); -/// The heaviest individual messages in the session's context window, most-expensive first. -[Experimental(Diagnostics.Experimental)] -public sealed class MetadataContextHeaviestMessagesResult -{ - /// Heaviest messages, most-expensive first. - [JsonPropertyName("messages")] - public IList Messages { get => field ??= []; set; } + /// High relative token cost tier. + public static ModelPickerPriceCategory High { get; } = new("high"); - /// 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; } -} + /// Highest relative token cost tier. + public static ModelPickerPriceCategory VeryHigh { get; } = new("very_high"); -/// Parameters for the heaviest-messages query. -[Experimental(Diagnostics.Experimental)] -internal sealed class MetadataContextHeaviestMessagesRequest -{ - /// Maximum number of messages to return, most-expensive first. Omit for the server default. - [JsonPropertyName("limit")] - public long? Limit { get; set; } + /// 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)); + } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// + public override void Write(Utf8JsonWriter writer, ModelPickerPriceCategory value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ModelPickerPriceCategory)); + } + } } -/// 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 MetadataRecordContextChangeResult -{ -} -/// Updated working directory and git context. Emitted as the new payload of `session.context_changed`. +/// Current policy state for this model. [Experimental(Diagnostics.Experimental)] -public sealed class SessionWorkingDirectoryContext +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct ModelPolicyState : IEquatable { - /// 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; } - - /// Current working directory path. - [JsonPropertyName("cwd")] - public string Cwd { get; set; } = string.Empty; + private readonly string? _value; - /// Root directory of the git repository, resolved via git rev-parse. - [JsonPropertyName("gitRoot")] - public string? GitRoot { get; set; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public ModelPolicyState(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Head commit of the current git branch. - [JsonPropertyName("headCommit")] - public string? HeadCommit { get; set; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Hosting platform type of the repository. - [JsonPropertyName("hostType")] - public SessionWorkingDirectoryContextHostType? HostType { get; set; } + /// The model is enabled by policy. + public static ModelPolicyState Enabled { get; } = new("enabled"); - /// 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; } + /// The model is disabled by policy. + public static ModelPolicyState Disabled { get; } = new("disabled"); - /// Raw host string from the git remote URL (e.g. "github.com", "dev.azure.com"). - [JsonPropertyName("repositoryHost")] - public string? RepositoryHost { get; set; } -} + /// No explicit policy is configured for the model. + public static ModelPolicyState Unconfigured { get; } = new("unconfigured"); -/// Updated working-directory/git context to record on the session. -[Experimental(Diagnostics.Experimental)] -internal sealed class MetadataRecordContextChangeRequest -{ - /// Updated working directory and git context. Emitted as the new payload of `session.context_changed`. - [JsonPropertyName("context")] - public SessionWorkingDirectoryContext Context { get => field ??= new(); set; } + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ModelPolicyState left, ModelPolicyState right) => left.Equals(right); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ModelPolicyState left, ModelPolicyState right) => !(left == right); -/// 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 MetadataSetWorkingDirectoryResult -{ - /// Working directory after the update. - [JsonPropertyName("workingDirectory")] - public string WorkingDirectory { get; set; } = string.Empty; -} + /// + public override bool Equals(object? obj) => obj is ModelPolicyState other && Equals(other); -/// 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)] -internal sealed class MetadataSetWorkingDirectoryRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// + public bool Equals(ModelPolicyState other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// 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; -} + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); -/// 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; } + /// + public override string ToString() => Value; - /// Tokens contributed by system/developer prompt snapshots. - [JsonPropertyName("systemTokenCount")] - public long SystemTokenCount { get; set; } + /// 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)); + } - /// Sum of tokens across chat-context and system-context messages currently held by the session. - [JsonPropertyName("totalTokens")] - public long TotalTokens { get; set; } + /// + public override void Write(Utf8JsonWriter writer, ModelPolicyState value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ModelPolicyState)); + } + } } -/// Model identifier to use when re-tokenizing the session's existing messages. -[Experimental(Diagnostics.Experimental)] -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; -} -/// Availability of built-in job tools surfaced to boundary consumers. +/// Server transport type: stdio, http, sse (deprecated), or memory. [Experimental(Diagnostics.Experimental)] -public sealed class SessionSettingsBuiltInToolAvailabilitySnapshot +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct DiscoveredMcpServerType : IEquatable { - /// Gets or sets the createPullRequest value. - [JsonPropertyName("createPullRequest")] - public bool? CreatePullRequest { get; set; } + private readonly string? _value; - /// Gets or sets the reportProgress value. - [JsonPropertyName("reportProgress")] - public bool? ReportProgress { get; set; } -} + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public DiscoveredMcpServerType(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } -/// Redacted job settings for a session. The job nonce is excluded. -[Experimental(Diagnostics.Experimental)] -public sealed class SessionSettingsJobSnapshot -{ - /// Gets or sets the builtInToolAvailability value. - [JsonPropertyName("builtInToolAvailability")] - public SessionSettingsBuiltInToolAvailabilitySnapshot? BuiltInToolAvailability { get; set; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Gets or sets the eventType value. - [JsonPropertyName("eventType")] - public string? EventType { get; set; } + /// Server communicates over stdio with a local child process. + public static DiscoveredMcpServerType Stdio { get; } = new("stdio"); - /// Gets or sets the isTriggerJob value. - [JsonPropertyName("isTriggerJob")] - public bool? IsTriggerJob { get; set; } -} + /// Server communicates over streamable HTTP. + public static DiscoveredMcpServerType Http { get; } = new("http"); -/// Redacted model routing settings for a session. -[Experimental(Diagnostics.Experimental)] -public sealed class SessionSettingsModelSnapshot -{ - /// Gets or sets the callbackUrl value. - [JsonPropertyName("callbackUrl")] - public string? CallbackUrl { get; set; } + /// Server communicates over Server-Sent Events (deprecated). + public static DiscoveredMcpServerType Sse { get; } = new("sse"); - /// Gets or sets the defaultReasoningEffort value. - [JsonPropertyName("defaultReasoningEffort")] - public string? DefaultReasoningEffort { get; set; } + /// Server is backed by an in-memory runtime implementation. + public static DiscoveredMcpServerType Memory { get; } = new("memory"); - /// Gets or sets the instanceId value. - [JsonPropertyName("instanceId")] - public string? InstanceId { get; set; } + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(DiscoveredMcpServerType left, DiscoveredMcpServerType right) => left.Equals(right); - /// Gets or sets the model value. - [JsonPropertyName("model")] - public string? Model { get; set; } -} + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(DiscoveredMcpServerType left, DiscoveredMcpServerType right) => !(left == right); -/// Online-evaluation settings safe to expose across the SDK boundary. -[Experimental(Diagnostics.Experimental)] -public sealed class SessionSettingsOnlineEvaluationSnapshot -{ - /// Gets or sets the disableOnlineEvaluation value. - [JsonPropertyName("disableOnlineEvaluation")] - public bool? DisableOnlineEvaluation { get; set; } + /// + public override bool Equals(object? obj) => obj is DiscoveredMcpServerType other && Equals(other); - /// Gets or sets the enableOnlineEvaluationOutputFile value. - [JsonPropertyName("enableOnlineEvaluationOutputFile")] - public bool? EnableOnlineEvaluationOutputFile { get; set; } -} + /// + public bool Equals(DiscoveredMcpServerType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); -/// Redacted repository and GitHub host settings for a session. -[Experimental(Diagnostics.Experimental)] -public sealed class SessionSettingsRepoSnapshot -{ - /// Gets or sets the branch value. - [JsonPropertyName("branch")] - public string? Branch { get; set; } + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - /// Gets or sets the commit value. - [JsonPropertyName("commit")] - public string? Commit { get; set; } + /// + public override string ToString() => Value; - /// Gets or sets the host value. - [JsonPropertyName("host")] - public string? Host { get; set; } + /// 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)); + } - /// Gets or sets the hostProtocol value. - [JsonPropertyName("hostProtocol")] - public string? HostProtocol { get; set; } + /// + public override void Write(Utf8JsonWriter writer, DiscoveredMcpServerType value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(DiscoveredMcpServerType)); + } + } +} - /// 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; } +/// A wire feature a caller can require of the catalog surface, negotiated per request. A grant means the runtime understands the feature's contract, not that the deployment has enabled the operation; typed unavailable results report availability separately. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct CatalogCapability : IEquatable +{ + private readonly string? _value; - /// Gets or sets the ownerId value. - [JsonPropertyName("ownerId")] - public double? OwnerId { get; set; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public CatalogCapability(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Gets or sets the ownerName value. - [JsonPropertyName("ownerName")] - public string? OwnerName { get; set; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Gets or sets the prCommitCount value. - [JsonPropertyName("prCommitCount")] - public double? PrCommitCount { get; set; } + /// Understands the current `application/mcp-server-card+json` media type. + public static CatalogCapability McpServerCard { get; } = new("mcp-server-card"); - /// Gets or sets the readWrite value. - [JsonPropertyName("readWrite")] - public bool? ReadWrite { get; set; } + /// Understands the legacy `application/mcp-server+json` media type. + public static CatalogCapability LegacyMcpServerCard { get; } = new("legacy-mcp-server-card"); - /// Gets or sets the secretScanningUrl value. - [JsonPropertyName("secretScanningUrl")] - public string? SecretScanningUrl { get; set; } + /// Understands `application/ai-skill` candidates as discovery-only and typed non-installable. + public static CatalogCapability AiSkillDiscovery { get; } = new("ai-skill-discovery"); - /// Gets or sets the serverUrl value. - [JsonPropertyName("serverUrl")] - public string? ServerUrl { get; set; } -} + /// Understands side-effect-free MCP install-plan requests, results, and plan handles; `planning-unavailable` separately reports that planning is not enabled. + public static CatalogCapability McpInstallPlanning { get; } = new("mcp-install-planning"); -/// Redacted validation and memory-tool settings for a session. -[Experimental(Diagnostics.Experimental)] -public sealed class SessionSettingsValidationSnapshot -{ - /// Gets or sets the advisoryEnabled value. - [JsonPropertyName("advisoryEnabled")] - public bool? AdvisoryEnabled { get; set; } + /// Understands plans that enumerate every eligible transport rather than a single preferred one. + public static CatalogCapability MultipleTransportChoice { get; } = new("multiple-transport-choice"); - /// Gets or sets the codeqlEnabled value. - [JsonPropertyName("codeqlEnabled")] - public bool? CodeqlEnabled { get; set; } + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(CatalogCapability left, CatalogCapability right) => left.Equals(right); - /// Gets or sets the codeReviewEnabled value. - [JsonPropertyName("codeReviewEnabled")] - public bool? CodeReviewEnabled { get; set; } + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(CatalogCapability left, CatalogCapability right) => !(left == right); - /// Gets or sets the codeReviewModel value. - [JsonPropertyName("codeReviewModel")] - public string? CodeReviewModel { get; set; } + /// + public override bool Equals(object? obj) => obj is CatalogCapability other && Equals(other); - /// Gets or sets the dependabotTimeout value. - [JsonPropertyName("dependabotTimeout")] - public double? DependabotTimeout { get; set; } + /// + public bool Equals(CatalogCapability other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// Gets or sets the memoryStoreEnabled value. - [JsonPropertyName("memoryStoreEnabled")] - public bool? MemoryStoreEnabled { get; set; } + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - /// Gets or sets the memoryVoteEnabled value. - [JsonPropertyName("memoryVoteEnabled")] - public bool? MemoryVoteEnabled { get; set; } + /// + public override string ToString() => Value; - /// Gets or sets the secretScanningEnabled value. - [JsonPropertyName("secretScanningEnabled")] - public bool? SecretScanningEnabled { get; set; } + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override CatalogCapability Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } - /// Gets or sets the timeout value. - [JsonPropertyName("timeout")] - public double? Timeout { get; set; } + /// + public override void Write(Utf8JsonWriter writer, CatalogCapability value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(CatalogCapability)); + } + } } -/// Redacted, serializable view of session runtime settings for SDK boundary consumers. Secrets and raw feature flags are intentionally excluded. + +/// Whether a planned configuration change would create or modify an entry. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionSettingsSnapshot +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct McpPlanConfigurationOperation : IEquatable { - /// 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; } + private readonly string? _value; - /// Gets or sets the model value. - [JsonPropertyName("model")] - public SessionSettingsModelSnapshot Model { get => field ??= new(); set; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public McpPlanConfigurationOperation(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Gets or sets the onlineEvaluation value. - [JsonPropertyName("onlineEvaluation")] - public SessionSettingsOnlineEvaluationSnapshot OnlineEvaluation { get => field ??= new(); set; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Gets or sets the repo value. - [JsonPropertyName("repo")] - public SessionSettingsRepoSnapshot Repo { get => field ??= new(); set; } + /// Creates a configuration entry that does not exist yet. + public static McpPlanConfigurationOperation Add { get; } = new("add"); - /// Gets or sets the startTimeMs value. - [JsonPropertyName("startTimeMs")] - public double? StartTimeMs { get; set; } + /// Modifies a configuration entry that already exists. + public static McpPlanConfigurationOperation Update { get; } = new("update"); - /// Gets or sets the timeoutMs value. - [JsonPropertyName("timeoutMs")] - public double? TimeoutMs { get; set; } + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(McpPlanConfigurationOperation left, McpPlanConfigurationOperation right) => left.Equals(right); - /// Gets or sets the validation value. - [JsonPropertyName("validation")] - public SessionSettingsValidationSnapshot Validation { get => field ??= new(); set; } + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(McpPlanConfigurationOperation left, McpPlanConfigurationOperation right) => !(left == right); - /// Gets or sets the version value. - [JsonPropertyName("version")] - public string? Version { get; set; } -} + /// + public override bool Equals(object? obj) => obj is McpPlanConfigurationOperation other && Equals(other); -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionSettingsSnapshotRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// + public bool Equals(McpPlanConfigurationOperation other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); -/// Result of evaluating a Rust-owned settings predicate. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionSettingsEvaluatePredicateResult -{ - /// Gets or sets the enabled value. - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } -} + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); -/// Named Rust-owned settings predicate to evaluate for this session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionSettingsEvaluatePredicateRequest -{ - /// Predicate name. The runtime owns the raw feature-flag names and composition logic. - [JsonPropertyName("name")] - public SessionSettingsPredicateName Name { get; set; } + /// + public override string ToString() => Value; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override McpPlanConfigurationOperation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } - /// Tool name for tool-scoped predicates such as trivial-change handling. - [JsonPropertyName("toolName")] - public string? ToolName { get; set; } + /// + public override void Write(Utf8JsonWriter writer, McpPlanConfigurationOperation value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpPlanConfigurationOperation)); + } + } } -/// Content-exclusion decision for one requested path. + +/// Configuration scope an MCP install plan targets. [Experimental(Diagnostics.Experimental)] -public sealed class ContentExclusionPathCheck +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct McpPlanScope : IEquatable { - /// Whether the session's complete content-exclusion policy excludes the path. - [JsonPropertyName("excluded")] - public bool Excluded { get; set; } + private readonly string? _value; - /// The path supplied by the caller. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; -} + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public McpPlanScope(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } -/// Batch content-exclusion result. Callers must fail closed when policy evaluation is unavailable. -[Experimental(Diagnostics.Experimental)] -public sealed class 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. - [JsonPropertyName("available")] - public bool Available { get; set; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Per-path decisions in request order. Empty when available is false. - [JsonPropertyName("checks")] - public IList Checks { get => field ??= []; set; } -} + /// The user's own MCP configuration. + public static McpPlanScope User { get; } = new("user"); -/// Local file system absolute paths within the session working directory to check against its content-exclusion policy. -[Experimental(Diagnostics.Experimental)] -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; } + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(McpPlanScope left, McpPlanScope right) => left.Equals(right); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(McpPlanScope left, McpPlanScope right) => !(left == right); -/// Identifier of the spawned process, used to correlate streamed output and exit notifications. -[Experimental(Diagnostics.Experimental)] -public sealed class ShellExecResult -{ - /// Unique identifier for tracking streamed output. - [JsonPropertyName("processId")] - public string ProcessId { get; set; } = string.Empty; -} + /// + public override bool Equals(object? obj) => obj is McpPlanScope other && Equals(other); -/// Shell command to run, with optional working directory and timeout in milliseconds. -[Experimental(Diagnostics.Experimental)] -internal sealed class ShellExecRequest -{ - /// Shell command to execute. - [JsonPropertyName("command")] - public string Command { get; set; } = string.Empty; + /// + public bool Equals(McpPlanScope other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// Working directory (defaults to session working directory). - [JsonPropertyName("cwd")] - public string? Cwd { get; set; } + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// + public override string ToString() => Value; - /// Timeout in milliseconds (default: 30000). - [JsonConverter(typeof(MillisecondsTimeSpanConverter))] - [JsonPropertyName("timeout")] - public TimeSpan? Timeout { get; set; } -} + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override McpPlanScope Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } -/// Indicates whether the signal was delivered; false if the process was unknown or already exited. -[Experimental(Diagnostics.Experimental)] -public sealed class ShellKillResult -{ - /// Whether the signal was sent successfully. - [JsonPropertyName("killed")] - public bool Killed { get; set; } + /// + public override void Write(Utf8JsonWriter writer, McpPlanScope value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpPlanScope)); + } + } } -/// Identifier of a process previously returned by "shell.exec" and the signal to send. + +/// What policy decided for a planned server. [Experimental(Diagnostics.Experimental)] -internal sealed class ShellKillRequest +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct McpPlanPolicyDecision : IEquatable { - /// 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; + private readonly string? _value; - /// Signal to send (default: SIGTERM). - [JsonPropertyName("signal")] - public ShellKillSignal? Signal { get; set; } -} + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public McpPlanPolicyDecision(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } -/// Result of a user-requested shell command. -[Experimental(Diagnostics.Experimental)] -public sealed class UserRequestedShellCommandResult -{ - /// Error output when the execution failed. - [JsonPropertyName("error")] - public string? Error { get; set; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Process exit code, when available. - [JsonPropertyName("exitCode")] - public long? ExitCode { get; set; } + /// Policy permits the server. + public static McpPlanPolicyDecision Allowed { get; } = new("allowed"); - /// Captured command output. - [JsonPropertyName("output")] - public string Output { get; set; } = string.Empty; + /// Policy forbids the server, so the plan cannot be applied. + public static McpPlanPolicyDecision Blocked { get; } = new("blocked"); - /// Whether the command completed successfully. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// Policy permits the server only after an explicit approval. + public static McpPlanPolicyDecision RequiresApproval { get; } = new("requires-approval"); - /// Tool call id emitted for the shell execution. - [JsonPropertyName("toolCallId")] - public string ToolCallId { get; set; } = string.Empty; -} + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(McpPlanPolicyDecision left, McpPlanPolicyDecision right) => left.Equals(right); -/// User-requested shell command and cancellation handle. -[Experimental(Diagnostics.Experimental)] -internal sealed class ShellExecuteUserRequestedRequest -{ - /// Shell command to execute. - [JsonPropertyName("command")] - public string Command { get; set; } = string.Empty; + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(McpPlanPolicyDecision left, McpPlanPolicyDecision right) => !(left == right); - /// Caller-provided cancellation handle for this execution. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// + public override bool Equals(object? obj) => obj is McpPlanPolicyDecision other && Equals(other); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// + public bool Equals(McpPlanPolicyDecision other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); -/// Cancellation result for a user-requested shell command. -[Experimental(Diagnostics.Experimental)] -public sealed class CancelUserRequestedShellCommandResult -{ - /// Whether an in-flight execution was found and signalled to cancel. - [JsonPropertyName("cancelled")] - public bool Cancelled { get; set; } -} + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); -/// 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; + /// + public override string ToString() => Value; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override McpPlanPolicyDecision Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, McpPlanPolicyDecision value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpPlanPolicyDecision)); + } + } } -/// Post-compaction context window usage breakdown. + +/// Which authority produced a policy decision. [Experimental(Diagnostics.Experimental)] -public sealed class HistoryCompactContextWindow +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct McpPlanPolicySource : IEquatable { - /// Token count from non-system messages (user, assistant, tool). - [JsonPropertyName("conversationTokens")] - public long? ConversationTokens { get; set; } + private readonly string? _value; - /// Current total tokens in the context window (system + conversation + tool definitions). - [JsonPropertyName("currentTokens")] - public long CurrentTokens { get; set; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public McpPlanPolicySource(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Current number of messages in the conversation. - [JsonPropertyName("messagesLength")] - public long MessagesLength { get; set; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Token count from system message(s). - [JsonPropertyName("systemTokens")] - public long? SystemTokens { get; set; } + /// No policy applied, so the server is permitted by default. + public static McpPlanPolicySource None { get; } = new("none"); - /// Maximum token count for the model's context window. - [JsonPropertyName("tokenLimit")] - public long TokenLimit { get; set; } + /// An enterprise allowlist evaluated the server. + public static McpPlanPolicySource EnterpriseAllowlist { get; } = new("enterprise-allowlist"); - /// Token count from tool definitions. - [JsonPropertyName("toolDefinitionsTokens")] - public long? ToolDefinitionsTokens { get; set; } -} + /// The registry the card came from evaluated the server. + public static McpPlanPolicySource RegistryPolicy { get; } = new("registry-policy"); -/// Compaction outcome with the number of tokens and messages removed, summary text, and the resulting context window breakdown. -[Experimental(Diagnostics.Experimental)] -public sealed class HistoryCompactResult -{ - /// Post-compaction context window usage breakdown. - [JsonPropertyName("contextWindow")] - public HistoryCompactContextWindow? ContextWindow { get; set; } + /// Local trust settings evaluated the server. + public static McpPlanPolicySource LocalTrust { get; } = new("local-trust"); - /// Number of messages removed during compaction. - [JsonPropertyName("messagesRemoved")] - public long MessagesRemoved { get; set; } + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(McpPlanPolicySource left, McpPlanPolicySource right) => left.Equals(right); - /// Whether compaction completed successfully. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(McpPlanPolicySource left, McpPlanPolicySource right) => !(left == right); - /// Summary text produced by compaction. Omitted when compaction did not produce a summary (e.g. failure path). - [JsonPropertyName("summaryContent")] - public string? SummaryContent { get; set; } + /// + public override bool Equals(object? obj) => obj is McpPlanPolicySource other && Equals(other); - /// Number of tokens freed by compaction. - [JsonPropertyName("tokensRemoved")] - public long TokensRemoved { get; set; } -} + /// + public bool Equals(McpPlanPolicySource other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); -/// RPC data type for SessionHistoryCompact operations. -[Experimental(Diagnostics.Experimental)] -public sealed class SessionHistoryCompactRequest -{ - /// 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; } + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - /// 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; } + /// + public override string ToString() => Value; - /// 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; } + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override McpPlanPolicySource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, McpPlanPolicySource value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpPlanPolicySource)); + } + } } -/// RPC data type for SessionHistoryCompactRequestWithSession operations. + +/// Canonical digest algorithm for a validated MCP card. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionHistoryCompactRequestWithSession +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct CardDigestAlgorithm : IEquatable { - /// 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; } + private readonly string? _value; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public CardDigestAlgorithm(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// 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; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// 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; } -} + /// SHA-256 over RFC 8785 canonical JSON encoded as UTF-8. + public static CardDigestAlgorithm Sha256Rfc8785 { get; } = new("sha256-rfc8785"); -/// Number of events that were removed by the truncation. -[Experimental(Diagnostics.Experimental)] -public sealed class HistoryTruncateResult -{ - /// Failure detail when checkpointCleanupFailed is true. - [JsonPropertyName("checkpointCleanupError")] - public string? CheckpointCleanupError { get; set; } + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(CardDigestAlgorithm left, CardDigestAlgorithm right) => left.Equals(right); - /// 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; } + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(CardDigestAlgorithm left, CardDigestAlgorithm right) => !(left == right); - /// Number of events that were removed. - [JsonPropertyName("eventsRemoved")] - public long EventsRemoved { get; set; } -} + /// + public override bool Equals(object? obj) => obj is CardDigestAlgorithm other && Equals(other); -/// Identifier of the event to truncate to; this event and all later events are removed. -[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; + /// + public bool Equals(CardDigestAlgorithm other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { 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 CardDigestAlgorithm Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, CardDigestAlgorithm value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(CardDigestAlgorithm)); + } + } } -/// A root user turn that the session can rewind to. + +/// JSON MCP card media type accepted for install planning. [Experimental(Diagnostics.Experimental)] -public sealed class HistoryRewindPoint +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct McpServerCardMediaType : IEquatable { - /// Whether at least one file in this turn or a later turn can be restored. - [JsonPropertyName("canRestoreFiles")] - public bool CanRestoreFiles { get; set; } + private readonly string? _value; - /// ID of the user.message event that begins the discarded suffix. - [JsonPropertyName("eventId")] - public string EventId { get; set; } = string.Empty; + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public McpServerCardMediaType(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Number of unique files in this turn and all later turns that have captured changes. - [JsonPropertyName("fileCount")] - public long FileCount { get; set; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Whether this turn was an automatically injected autopilot continuation. - [JsonPropertyName("isAutopilotContinuation")] - public bool IsAutopilotContinuation { get; set; } + /// The current MCP server card media type. + public static McpServerCardMediaType ApplicationMcpServerCardJson { get; } = new("application/mcp-server-card+json"); - /// Lines added by this turn's captured file changes. - [JsonPropertyName("linesAdded")] - public long LinesAdded { get; set; } + /// The legacy MCP server card media type, accepted for compatibility. + public static McpServerCardMediaType ApplicationMcpServerJson { get; } = new("application/mcp-server+json"); - /// Lines removed by this turn's captured file changes. - [JsonPropertyName("linesRemoved")] - public long LinesRemoved { get; set; } + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(McpServerCardMediaType left, McpServerCardMediaType right) => left.Equals(right); - /// ISO timestamp of the user turn. - [JsonPropertyName("timestamp")] - public string Timestamp { get; set; } = string.Empty; + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(McpServerCardMediaType left, McpServerCardMediaType right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is McpServerCardMediaType other && Equals(other); + + /// + public bool Equals(McpServerCardMediaType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - /// Whether this turn itself captured any file changes. - [JsonPropertyName("turnChangedFiles")] - public bool TurnChangedFiles { get; set; } + /// + public override string ToString() => Value; - /// User-visible message text for the turn. - [JsonPropertyName("userMessage")] - public string UserMessage { get; set; } = string.Empty; + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override McpServerCardMediaType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, McpServerCardMediaType value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpServerCardMediaType)); + } + } } -/// Rewind points and file-change-tracking availability for the session. + +/// Where a required value is applied when the planned server is launched. [Experimental(Diagnostics.Experimental)] -public sealed class HistoryListRewindPointsResult +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct McpPlanValueCategory : IEquatable { - /// Whether this session captured file changes from its first turn. - [JsonPropertyName("fileChangeTrackingEnabled")] - public bool FileChangeTrackingEnabled { get; set; } + private readonly string? _value; - /// Root user turns in chronological order. Empty when `unavailableReason` is set. - [JsonPropertyName("points")] - public IList Points { get => field ??= []; set; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public McpPlanValueCategory(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// 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; } -} + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionHistoryListRewindPointsRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Set as an environment variable on the launched process. + public static McpPlanValueCategory EnvironmentVariable { get; } = new("environment-variable"); -/// A file that a conversation-and-files rewind would restore. -[Experimental(Diagnostics.Experimental)] -public sealed class HistoryRewindFilePreview -{ - /// Aggregate change made across the discarded turns. - [JsonPropertyName("changeType")] - public HistoryRewindChangeType ChangeType { get; set; } + /// Passed to the runtime that launches the package. + public static McpPlanValueCategory RuntimeArgument { get; } = new("runtime-argument"); - /// Lines added across the discarded turns. - [JsonPropertyName("linesAdded")] - public long LinesAdded { get; set; } + /// Passed to the packaged server itself. + public static McpPlanValueCategory PackageArgument { get; } = new("package-argument"); - /// Lines removed across the discarded turns. - [JsonPropertyName("linesRemoved")] - public long LinesRemoved { get; set; } + /// Sent as a request header to a remote endpoint. + public static McpPlanValueCategory Header { get; } = new("header"); - /// Absolute path of the captured file. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; -} + /// Substituted into the remote endpoint URL. + public static McpPlanValueCategory UrlVariable { get; } = new("url-variable"); -/// Files and aggregate changes for a prospective rewind. -[Experimental(Diagnostics.Experimental)] -public sealed class HistoryPreviewRewindResult -{ - /// 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; } + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(McpPlanValueCategory left, McpPlanValueCategory right) => left.Equals(right); - /// Number of unique files in the preview. - [JsonPropertyName("fileCount")] - public long FileCount { get; set; } + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(McpPlanValueCategory left, McpPlanValueCategory right) => !(left == right); - /// Files ordered by path. - [JsonPropertyName("files")] - public IList Files { get => field ??= []; set; } + /// + public override bool Equals(object? obj) => obj is McpPlanValueCategory other && Equals(other); - /// 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; } -} + /// + public bool Equals(McpPlanValueCategory other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); -/// Event boundary to preview for conversation-and-files rewind. -[Experimental(Diagnostics.Experimental)] -internal sealed class HistoryPreviewRewindRequest -{ - /// ID of the user.message event that begins the discarded suffix. - [JsonPropertyName("eventId")] - public string EventId { get; set; } = string.Empty; + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// + public override string ToString() => Value; -/// A captured file that rewind intentionally left unchanged. -[Experimental(Diagnostics.Experimental)] -public sealed class HistorySkippedFileRestore -{ - /// Absolute path of the skipped file. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override McpPlanValueCategory Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } - /// Reason the file was not restored. - [JsonPropertyName("reason")] - public HistoryFileRestoreSkipReason Reason { get; set; } + /// + public override void Write(Utf8JsonWriter writer, McpPlanValueCategory value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpPlanValueCategory)); + } + } } -/// Structured outcome of a rewind request. + +/// Scalar type a required value must conform to. [Experimental(Diagnostics.Experimental)] -public sealed class HistoryRewindResult +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct McpPlanScalarValueType : IEquatable { - /// 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 string? Error { get; set; } + private readonly string? _value; - /// 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; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public McpPlanScalarValueType(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// 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; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// 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; } + /// Free text. + public static McpPlanScalarValueType String { get; } = new("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. - [JsonPropertyName("skippedFiles")] - public IList SkippedFiles { get => field ??= []; set; } -} + /// A number. + public static McpPlanScalarValueType Number { get; } = new("number"); -/// Boundary and mode for rewinding session history. -[Experimental(Diagnostics.Experimental)] -internal sealed class HistoryRewindRequest -{ - /// ID of the user.message event that begins the discarded suffix. - [JsonPropertyName("eventId")] - public string EventId { get; set; } = string.Empty; + /// A boolean. + public static McpPlanScalarValueType Boolean { get; } = new("boolean"); - /// Whether to rewind only conversation history or also restore captured files. - [JsonPropertyName("mode")] - public HistoryRewindMode Mode { get; set; } + /// A filesystem path. + public static McpPlanScalarValueType Path { get; } = new("path"); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(McpPlanScalarValueType left, McpPlanScalarValueType right) => left.Equals(right); -/// Indicates whether an in-progress background compaction was cancelled. -[Experimental(Diagnostics.Experimental)] -public sealed class 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. - [JsonPropertyName("cancelled")] - public bool Cancelled { get; set; } -} + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(McpPlanScalarValueType left, McpPlanScalarValueType right) => !(left == right); -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionHistoryCancelBackgroundCompactionRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// + public override bool Equals(object? obj) => obj is McpPlanScalarValueType other && Equals(other); -/// Indicates whether an in-progress manual compaction was aborted. -[Experimental(Diagnostics.Experimental)] -public sealed class 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. - [JsonPropertyName("aborted")] - public bool Aborted { get; set; } -} + /// + public bool Equals(McpPlanScalarValueType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionHistoryAbortManualCompactionRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); -/// Markdown summary of the conversation context (empty when not available). -[Experimental(Diagnostics.Experimental)] -public sealed class 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. - [JsonPropertyName("summary")] - public string Summary { get; set; } = string.Empty; -} + /// + public override string ToString() => Value; -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionHistorySummarizeForHandoffRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override McpPlanScalarValueType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } -/// What a successful clear removed. A clear that could not be applied rejects instead of reporting a count. -[Experimental(Diagnostics.Experimental)] -public sealed class HistoryClearContextResult -{ - /// 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; } + /// + public override void Write(Utf8JsonWriter writer, McpPlanScalarValueType value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpPlanScalarValueType)); + } + } } -/// Parameters for clearing the conversation and seeding the window that replaces it. + +/// Discriminator for an enumerated required value. [Experimental(Diagnostics.Experimental)] -internal sealed class HistoryClearContextRequest +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct McpPlanEnumValueType : IEquatable { - /// 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; + private readonly string? _value; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public McpPlanEnumValueType(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// One of a fixed, non-empty set of permitted values. + public static McpPlanEnumValueType Enum { get; } = new("enum"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(McpPlanEnumValueType left, McpPlanEnumValueType right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(McpPlanEnumValueType left, McpPlanEnumValueType right) => !(left == right); -/// 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 -{ - /// 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; } + /// + public override bool Equals(object? obj) => obj is McpPlanEnumValueType other && Equals(other); - /// Human-readable text to display for this queue entry in the UI. - [JsonPropertyName("displayText")] - public string DisplayText { get; set; } = string.Empty; + /// + public bool Equals(McpPlanEnumValueType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// Stable opaque id for the canonical queued item. Batch rows share one id. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - /// Whether this item is a queued user message or a queued slash command / model change. - [JsonPropertyName("kind")] - public QueuePendingItemsKind Kind { get; set; } -} + /// + public override string ToString() => Value; -/// Snapshot of the session's pending queued items and immediate-steering messages. -[Experimental(Diagnostics.Experimental)] -public sealed class QueuePendingItemsResult -{ - /// 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; } + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override McpPlanEnumValueType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } - /// Display text for messages currently in the immediate steering queue (interjections sent during a running turn). - [JsonPropertyName("steeringMessages")] - public IList SteeringMessages { get => field ??= []; set; } + /// + public override void Write(Utf8JsonWriter writer, McpPlanEnumValueType value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpPlanEnumValueType)); + } + } } -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionQueuePendingItemsRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} -/// Internal snapshot of native queue state for local session orchestration. +/// Transport exposed by a locally launched package. [Experimental(Diagnostics.Experimental)] -internal sealed class QueueSnapshotResult +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct McpPlanPackageTransport : IEquatable { - /// Insertion orders for queued items, aligned with `items`. - [JsonPropertyName("itemOrders")] - public IList? ItemOrders { get; set; } + private readonly string? _value; - /// User-facing pending items in FIFO order. - [JsonPropertyName("items")] - public IList Items { get => field ??= []; set; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public McpPlanPackageTransport(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Insertion orders for immediate steering messages, aligned with `steeringMessages`. - [JsonPropertyName("steeringMessageOrders")] - public IList? SteeringMessageOrders { get; set; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Immediate steering messages waiting for an active turn. - [JsonPropertyName("steeringMessages")] - public IList SteeringMessages { get => field ??= []; set; } -} + /// A locally launched process spoken to over standard input and output. + public static McpPlanPackageTransport Stdio { get; } = new("stdio"); -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionQueueSnapshotRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(McpPlanPackageTransport left, McpPlanPackageTransport right) => left.Equals(right); -/// 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; } -} + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(McpPlanPackageTransport left, McpPlanPackageTransport right) => !(left == right); -/// 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; + /// + public override bool Equals(object? obj) => obj is McpPlanPackageTransport other && Equals(other); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// + public bool Equals(McpPlanPackageTransport other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// Zero-based target position in the public visible queue. Values outside the queue clamp to an end. - [JsonPropertyName("toPosition")] - public long ToPosition { get; set; } -} + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); -/// 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; + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override McpPlanPackageTransport Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, McpPlanPackageTransport value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpPlanPackageTransport)); + } + } } -/// Serializable message fields accepted by queue.insertAt. + +/// Transport exposed by a remote endpoint. [Experimental(Diagnostics.Experimental)] -public sealed class QueueInsertMessage +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct McpPlanRemoteTransport : IEquatable { - /// Optional explicit agent mode. When omitted, the session's current mode is assigned. - [JsonPropertyName("agentMode")] - public SendAgentMode? AgentMode { get; set; } + private readonly string? _value; - /// Optional attachments for the message. - [JsonPropertyName("attachments")] - public IList? Attachments { get; set; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public McpPlanRemoteTransport(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Whether the message is billable. - [JsonPropertyName("billable")] - public bool? Billable { get; set; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Accepted for internal SendOptions compatibility but ignored; delivery is derived from current session activity. - [JsonPropertyName("delivery")] - public string? Delivery { get; set; } + /// An HTTP endpoint. + public static McpPlanRemoteTransport Http { get; } = new("http"); - /// Optional user-facing display text. - [JsonPropertyName("displayPrompt")] - public string? DisplayPrompt { get; set; } + /// A streamable HTTP endpoint. + public static McpPlanRemoteTransport StreamableHttp { get; } = new("streamable-http"); - /// Accepted for SendOptions compatibility but ignored; inserted items always use queued delivery semantics. - [JsonPropertyName("mode")] - public SendMode? Mode { get; set; } + /// A server-sent events endpoint. + public static McpPlanRemoteTransport Sse { get; } = new("sse"); - /// Accepted for SendOptions compatibility but ignored; the requested public position controls placement. - [JsonPropertyName("prepend")] - public bool? Prepend { get; set; } + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(McpPlanRemoteTransport left, McpPlanRemoteTransport right) => left.Equals(right); - /// The user message text. - [JsonPropertyName("prompt")] - public string Prompt { get; set; } = string.Empty; + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(McpPlanRemoteTransport left, McpPlanRemoteTransport right) => !(left == right); - /// Per-turn request headers. - [JsonPropertyName("requestHeaders")] - public IDictionary? RequestHeaders { get; set; } + /// + public override bool Equals(object? obj) => obj is McpPlanRemoteTransport other && Equals(other); - /// Required tool name for the turn, when any. - [JsonPropertyName("requiredTool")] - public string? RequiredTool { get; set; } + /// + public bool Equals(McpPlanRemoteTransport other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// 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; } + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - /// Accepted for SendOptions compatibility but ignored; insertion scheduling is controlled by the queue drain state. - [JsonPropertyName("wait")] - public bool? Wait { get; set; } + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override McpPlanRemoteTransport Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, McpPlanRemoteTransport value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpPlanRemoteTransport)); + } + } } -/// Parameters for inserting a queued message at a public visible position. + +/// Why capability and protocol-version negotiation refused a caller. [Experimental(Diagnostics.Experimental)] -internal sealed class QueueInsertAtRequest +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct CatalogNegotiationRefusedReason : IEquatable { - /// Gets or sets the message value. - [JsonPropertyName("message")] - public QueueInsertMessage Message { get => field ??= new(); set; } + private readonly string? _value; - /// Zero-based position in the public visible queue. Values outside the queue clamp to an end. - [JsonPropertyName("position")] - public long Position { get; set; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public CatalogNegotiationRefusedReason(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Gets the value associated with this . + public string Value => _value ?? 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; } -} + /// The caller's protocol version is below the lowest this runtime serves. + public static CatalogNegotiationRefusedReason UnsupportedProtocolVersion { get; } = new("unsupported-protocol-version"); -/// 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; + /// The caller requires at least one capability this runtime cannot honour. + public static CatalogNegotiationRefusedReason UnsupportedCapability { get; } = new("unsupported-capability"); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(CatalogNegotiationRefusedReason left, CatalogNegotiationRefusedReason right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(CatalogNegotiationRefusedReason left, CatalogNegotiationRefusedReason right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is CatalogNegotiationRefusedReason other && Equals(other); + + /// + public bool Equals(CatalogNegotiationRefusedReason 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 CatalogNegotiationRefusedReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, CatalogNegotiationRefusedReason value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(CatalogNegotiationRefusedReason)); + } + } } -/// 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. +/// Which kind of opaque handle was presented. [Experimental(Diagnostics.Experimental)] -internal sealed class QueueUpdateTextRequest +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct CatalogHandleType : IEquatable { - /// Gets or sets the displayPrompt value. - [JsonPropertyName("displayPrompt")] - public string? DisplayPrompt { get; set; } + private readonly string? _value; - /// Gets or sets the id value. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public CatalogHandleType(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Gets or sets the prompt value. - [JsonPropertyName("prompt")] - public string Prompt { get; set; } = string.Empty; + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// A search candidate handle. + public static CatalogHandleType Candidate { get; } = new("candidate"); -/// 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; -} + /// An install plan handle. + public static CatalogHandleType Plan { get; } = new("plan"); -/// 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; + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(CatalogHandleType left, CatalogHandleType right) => left.Equals(right); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(CatalogHandleType left, CatalogHandleType right) => !(left == right); -/// 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; } + /// + public override bool Equals(object? obj) => obj is CatalogHandleType other && Equals(other); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// + public bool Equals(CatalogHandleType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); -/// 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; } -} + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); -/// 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; + /// + public override string ToString() => Value; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override CatalogHandleType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } -/// 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; } + /// + public override void Write(Utf8JsonWriter writer, CatalogHandleType value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(CatalogHandleType)); + } + } } -/// 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. +/// Why a presented handle was rejected. [Experimental(Diagnostics.Experimental)] -internal sealed class QueueBeginDeferredIdleDrainResult +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct CatalogHandleRejectionReason : IEquatable { - /// True when the host should run finishDeferredIdleDrain asynchronously. - [JsonPropertyName("shouldDrain")] - public bool ShouldDrain { get; set; } -} + private readonly string? _value; -/// 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; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public CatalogHandleRejectionReason(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Gets the value associated with this . + public string Value => _value ?? 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; } + /// The handle is unparseable, unknown, or was issued for a different operation. + public static CatalogHandleRejectionReason Invalid { get; } = new("invalid"); - /// One of none, processQueue, or emitSessionIdle. - [JsonPropertyName("action")] - public string Action { get; set; } = string.Empty; -} + /// The handle's time to live has elapsed. + public static CatalogHandleRejectionReason Stale { get; } = new("stale"); -/// 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; } + /// The handle has already been used, and handles are single-use. + public static CatalogHandleRejectionReason Replayed { get; } = new("replayed"); - /// Whether native queued work remains. - [JsonPropertyName("hasPending")] - public bool HasPending { get; set; } + /// The handle was issued by a different runtime instance. + public static CatalogHandleRejectionReason Foreign { get; } = new("foreign"); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(CatalogHandleRejectionReason left, CatalogHandleRejectionReason right) => left.Equals(right); -/// 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; } + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(CatalogHandleRejectionReason left, CatalogHandleRejectionReason right) => !(left == right); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// + public override bool Equals(object? obj) => obj is CatalogHandleRejectionReason other && Equals(other); -/// 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; } -} + /// + public bool Equals(CatalogHandleRejectionReason other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionQueueRemoveMostRecentRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionQueueClearRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// + public override string ToString() => Value; -/// 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; } + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override CatalogHandleRejectionReason 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, CatalogHandleRejectionReason value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(CatalogHandleRejectionReason)); + } + } } -/// 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. +/// Which request field was rejected before any work was done. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionQueueEnqueueResumePendingRequest +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct CatalogInvalidRequestField : IEquatable { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + private readonly string? _value; -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionQueueProcessRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public CatalogInvalidRequestField(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The search query was empty or longer than permitted. + public static CatalogInvalidRequestField Query { get; } = new("query"); + + /// The requested result count fell outside its permitted range. + public static CatalogInvalidRequestField Limit { get; } = new("limit"); + + /// The requested candidate kinds were empty or contained a duplicate. + public static CatalogInvalidRequestField Kinds { get; } = new("kinds"); + + /// The negotiation block was missing or malformed. + public static CatalogInvalidRequestField Contract { get; } = new("contract"); + + /// The plan source was missing or malformed. + public static CatalogInvalidRequestField Source { get; } = new("source"); -/// 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; + /// The supplied card was missing its media type, URL, or data. + public static CatalogInvalidRequestField Card { get; } = new("card"); - /// 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; } + /// The requested configuration scope is not one this runtime writes. + public static CatalogInvalidRequestField Scope { get; } = new("scope"); - /// 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; } + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(CatalogInvalidRequestField left, CatalogInvalidRequestField right) => left.Equals(right); - /// 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; } + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(CatalogInvalidRequestField left, CatalogInvalidRequestField right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is CatalogInvalidRequestField other && Equals(other); + + /// + public bool Equals(CatalogInvalidRequestField 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 CatalogInvalidRequestField Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, CatalogInvalidRequestField value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(CatalogInvalidRequestField)); + } + } } -/// Cursor, batch size, and optional long-poll/filter parameters for reading session events. + +/// Why the catalog authority did not accept the caller's identity. [Experimental(Diagnostics.Experimental)] -internal sealed class EventLogReadRequest +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct CatalogAuthenticationRequiredReason : IEquatable { - /// 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; } + private readonly string? _value; - /// 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; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public CatalogAuthenticationRequiredReason(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// 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; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// 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; } + /// No credential was presented, so there is nothing to refresh and the caller must sign in. + public static CatalogAuthenticationRequiredReason NoCredential { get; } = new("no-credential"); - /// 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; } + /// A credential was presented and its lifetime has elapsed. A silent refresh is worth attempting before prompting anyone. + public static CatalogAuthenticationRequiredReason CredentialExpired { get; } = new("credential-expired"); - /// Maximum number of events to return in this batch (1–1000, default 200). - [JsonPropertyName("max")] - public long? Max { get; set; } + /// A credential was presented and the authority refused it, for example because it was revoked, malformed, or issued for another audience. Refreshing the same rejected credential is not useful; the caller must sign in again. + public static CatalogAuthenticationRequiredReason CredentialRejected { get; } = new("credential-rejected"); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(CatalogAuthenticationRequiredReason left, CatalogAuthenticationRequiredReason right) => left.Equals(right); - /// Either '*' to receive all event types, or a non-empty list of event types to receive. - [JsonPropertyName("types")] - public JsonElement? Types { get; set; } + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(CatalogAuthenticationRequiredReason left, CatalogAuthenticationRequiredReason right) => !(left == right); - /// 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; } -} + /// + public override bool Equals(object? obj) => obj is CatalogAuthenticationRequiredReason other && Equals(other); -/// 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; -} + /// + public bool Equals(CatalogAuthenticationRequiredReason other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionEventLogTailRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); -/// 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; -} + /// + public override string ToString() => Value; -/// 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; + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override CatalogAuthenticationRequiredReason 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, CatalogAuthenticationRequiredReason value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(CatalogAuthenticationRequiredReason)); + } + } } -/// 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. +/// Categorised network failure, low cardinality so it can be aggregated without carrying a URL. [Experimental(Diagnostics.Experimental)] -internal sealed class ReleaseEventInterestParams +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct CatalogNetworkFailureReason : IEquatable { - /// 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; + private readonly string? _value; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public CatalogNetworkFailureReason(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } -/// 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; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Number of distinct files modified. - [JsonPropertyName("filesModifiedCount")] - public long FilesModifiedCount { get; set; } + /// No network is available, so nothing was attempted. + public static CatalogNetworkFailureReason Offline { get; } = new("offline"); - /// Total lines of code added. - [JsonPropertyName("linesAdded")] - public long LinesAdded { get; set; } + /// The authority's name could not be resolved. + public static CatalogNetworkFailureReason Dns { get; } = new("dns"); - /// Total lines of code removed. - [JsonPropertyName("linesRemoved")] - public long LinesRemoved { get; set; } -} + /// The request exceeded its time budget. + public static CatalogNetworkFailureReason Timeout { get; } = new("timeout"); -/// 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; } + /// The TLS handshake or certificate validation failed. + public static CatalogNetworkFailureReason Tls { get; } = new("tls"); - /// Number of API requests made with this model. - [JsonPropertyName("count")] - public long Count { get; set; } -} + /// The connection was refused or reset. + public static CatalogNetworkFailureReason ConnectionRefused { get; } = new("connection-refused"); -/// 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; } -} + /// The configured proxy returned 407 and requires authentication. + public static CatalogNetworkFailureReason ProxyAuthenticationRequired { get; } = new("proxy-authentication-required"); -/// 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; } + /// The authority rate-limited requests and supplied or implied a bounded cooldown. + public static CatalogNetworkFailureReason RateLimited { get; } = new("rate-limited"); - /// Total tokens written to prompt cache. - [JsonPropertyName("cacheWriteTokens")] - public long CacheWriteTokens { get; set; } + /// The authority returned a transient 5xx response. + public static CatalogNetworkFailureReason ServiceUnavailable { get; } = new("service-unavailable"); - /// Total input tokens consumed. - [JsonPropertyName("inputTokens")] - public long InputTokens { get; set; } + /// The authority returned another status the runtime treats as a failure. + public static CatalogNetworkFailureReason HttpStatus { get; } = new("http-status"); - /// Total output tokens produced. - [JsonPropertyName("outputTokens")] - public long OutputTokens { get; set; } + /// The response exceeded the permitted size. + public static CatalogNetworkFailureReason ResponseTooLarge { get; } = new("response-too-large"); - /// Total output tokens used for reasoning. - [JsonPropertyName("reasoningTokens")] - public long? ReasoningTokens { get; set; } -} + /// A redirect was refused by the runtime's redirect policy. + public static CatalogNetworkFailureReason RedirectRejected { get; } = new("redirect-rejected"); -/// 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; } + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(CatalogNetworkFailureReason left, CatalogNetworkFailureReason right) => left.Equals(right); - /// Request count and cost metrics for this model. - [JsonPropertyName("requests")] - public UsageMetricsModelMetricRequests Requests { get => field ??= new(); set; } + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(CatalogNetworkFailureReason left, CatalogNetworkFailureReason right) => !(left == right); - /// Token count details per type. - [JsonPropertyName("tokenDetails")] - public IDictionary? TokenDetails { get; set; } + /// + public override bool Equals(object? obj) => obj is CatalogNetworkFailureReason other && Equals(other); - /// Accumulated nano-AI units cost for this model. - [JsonPropertyName("totalNanoAiu")] - public double? TotalNanoAiu { get; set; } + /// + public bool Equals(CatalogNetworkFailureReason other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// Token usage metrics for this model. - [JsonPropertyName("usage")] - public UsageMetricsModelMetricUsage Usage { get => field ??= new(); set; } -} + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); -/// 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; } + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override CatalogNetworkFailureReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, CatalogNetworkFailureReason value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(CatalogNetworkFailureReason)); + } + } } -/// Accumulated session usage metrics, including premium request cost, token counts, model breakdown, and code-change totals. + +/// Which hardened-fetch control refused a retrieval. [Experimental(Diagnostics.Experimental)] -public sealed class UsageGetMetricsResult +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct CatalogUnsafeRetrievalReason : IEquatable { - /// Aggregated code change metrics. - [JsonPropertyName("codeChanges")] - public UsageMetricsCodeChanges CodeChanges { get => field ??= new(); set; } - - /// Currently active model identifier. - [JsonPropertyName("currentModel")] - public string? CurrentModel { get; set; } + private readonly string? _value; - /// Input tokens from the most recent main-agent API call. - [JsonPropertyName("lastCallInputTokens")] - public long LastCallInputTokens { get; set; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public CatalogUnsafeRetrievalReason(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Output tokens from the most recent main-agent API call. - [JsonPropertyName("lastCallOutputTokens")] - public long LastCallOutputTokens { get; set; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Per-model token and request metrics, keyed by model identifier. - [JsonPropertyName("modelMetrics")] - public IDictionary ModelMetrics { get => field ??= new Dictionary(); set; } + /// The URL used a scheme the runtime refuses to fetch. + public static CatalogUnsafeRetrievalReason BlockedScheme { get; } = new("blocked-scheme"); - /// ISO 8601 timestamp when the session started. - [JsonPropertyName("sessionStartTime")] - public DateTimeOffset SessionStartTime { get; set; } + /// The URL embedded credentials. + public static CatalogUnsafeRetrievalReason CredentialsInUrl { get; } = new("credentials-in-url"); - /// Session-wide per-token-type accumulated token counts. - [JsonPropertyName("tokenDetails")] - public IDictionary? TokenDetails { get; set; } + /// The URL resolved to a loopback, private, link-local, or cloud metadata address. + public static CatalogUnsafeRetrievalReason BlockedAddress { get; } = new("blocked-address"); - /// Total time spent in model API calls (milliseconds). - [JsonConverter(typeof(MillisecondsTimeSpanConverter))] - [JsonPropertyName("totalApiDurationMs")] - public TimeSpan TotalApiDuration { get; set; } + /// A redirect target resolved to a blocked address. + public static CatalogUnsafeRetrievalReason RedirectToBlockedAddress { get; } = new("redirect-to-blocked-address"); - /// Session-wide accumulated nano-AI units cost. - [JsonPropertyName("totalNanoAiu")] - public double? TotalNanoAiu { get; set; } + /// The configured proxy policy refused the request. + public static CatalogUnsafeRetrievalReason ProxyRejected { get; } = new("proxy-rejected"); - /// Total user-initiated premium request cost across all models (may be fractional due to multipliers). - [JsonPropertyName("totalPremiumRequestCost")] - public double TotalPremiumRequestCost { get; set; } + /// The authority is not permitted for card retrieval. + public static CatalogUnsafeRetrievalReason HostNotPermitted { get; } = new("host-not-permitted"); - /// Raw count of user-initiated API requests. - [JsonPropertyName("totalUserRequests")] - public long TotalUserRequests { get; set; } -} + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(CatalogUnsafeRetrievalReason left, CatalogUnsafeRetrievalReason right) => left.Equals(right); -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionUsageGetMetricsRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(CatalogUnsafeRetrievalReason left, CatalogUnsafeRetrievalReason right) => !(left == right); -/// 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; -} + /// + public override bool Equals(object? obj) => obj is CatalogUnsafeRetrievalReason other && Equals(other); + /// + public bool Equals(CatalogUnsafeRetrievalReason other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); -/// 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; + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - /// Start of the baseline data slice. - [JsonPropertyName("windowStart")] - public string WindowStart { get; set; } = string.Empty; -} + /// + public override string ToString() => Value; -/// 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; } + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override CatalogUnsafeRetrievalReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } - /// Gets or sets the tier value. - [JsonPropertyName("tier")] - public SessionLimitPredictionTier Tier { get; set; } + /// + public override void Write(Utf8JsonWriter writer, CatalogUnsafeRetrievalReason value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(CatalogUnsafeRetrievalReason)); + } + } } -/// Explainable AI-credit session-limit prediction. + +/// Media type a catalog card is interpreted as. [Experimental(Diagnostics.Experimental)] -public sealed class SessionLimitPredictionDetails +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct CatalogMediaType : IEquatable { - /// Baseline data provenance. - [JsonPropertyName("baselineData")] - public SessionLimitPredictionBaselineData BaselineData { get => field ??= new(); set; } + private readonly string? _value; - /// Client population used for the prediction. - [JsonPropertyName("clientType")] - public SessionLimitPredictionClientType ClientType { get; set; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public CatalogMediaType(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Resolved model family when known. - [JsonPropertyName("family")] - public string? Family { get; set; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Model identifier used for lookup. - [JsonPropertyName("modelId")] - public string ModelId { get; set; } = string.Empty; + /// The current MCP server card media type. + public static CatalogMediaType ApplicationMcpServerCardJson { get; } = new("application/mcp-server-card+json"); - /// Recommended maximum AI credits for this session. - [JsonPropertyName("recommendedCap")] - public double RecommendedCap { get; set; } + /// The legacy MCP server card media type, accepted for compatibility. + public static CatalogMediaType ApplicationMcpServerJson { get; } = new("application/mcp-server+json"); - /// Tier chosen as the recommended cap. - [JsonPropertyName("recommendedTier")] - public SessionLimitPredictionTier RecommendedTier { get; set; } + /// An AI skill card. Representable and searchable, but typed non-installable. + public static CatalogMediaType ApplicationAiSkill { get; } = new("application/ai-skill"); - /// Baseline fallback level used to create the prediction. - [JsonPropertyName("source")] - public SessionLimitPredictionSource Source { get; set; } + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(CatalogMediaType left, CatalogMediaType right) => left.Equals(right); - /// Key matched at the source level, such as a model id, family id, or `global`. - [JsonPropertyName("sourceKey")] - public string SourceKey { get; set; } = string.Empty; + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(CatalogMediaType left, CatalogMediaType right) => !(left == right); - /// Ordered usage tiers and their AI-credit caps. - [JsonPropertyName("tiers")] - public IList Tiers { get => field ??= []; set; } -} + /// + public override bool Equals(object? obj) => obj is CatalogMediaType other && Equals(other); -/// The available variant of . -[Experimental(Diagnostics.Experimental)] -public partial class SessionLimitPredictionResultAvailable : SessionLimitPredictionResult -{ /// - [JsonIgnore] - public override string Kind => "available"; + public bool Equals(CatalogMediaType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// Predicted session limit details. - [JsonPropertyName("prediction")] - public required SessionLimitPredictionDetails Prediction { get; set; } -} + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); -/// The unavailable variant of . -[Experimental(Diagnostics.Experimental)] -public partial class SessionLimitPredictionResultUnavailable : SessionLimitPredictionResult -{ /// - [JsonIgnore] - public override string Kind => "unavailable"; + public override string ToString() => Value; - /// Reason no prediction is available. - [JsonPropertyName("reason")] - public required SessionLimitPredictionUnavailableReason Reason { get; set; } + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override CatalogMediaType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, CatalogMediaType value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(CatalogMediaType)); + } + } } -/// RPC data type for SessionLimitPredictionPredict operations. + +/// How a card failed validation. [Experimental(Diagnostics.Experimental)] -public sealed class SessionLimitPredictionPredictRequest +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct CatalogMalformedCardReason : IEquatable { - /// Client type to size for. Defaults to `cli-interactive`. - [JsonPropertyName("clientType")] - public SessionLimitPredictionClientType? ClientType { get; set; } + private readonly string? _value; - /// Optional model identifier override. If omitted, the session's current model is used. - [JsonPropertyName("modelId")] - public string? ModelId { get; set; } -} + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public CatalogMalformedCardReason(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } -/// 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; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Optional model identifier override. If omitted, the session's current model is used. - [JsonPropertyName("modelId")] - public string? ModelId { get; set; } + /// The document is not well-formed JSON. + public static CatalogMalformedCardReason InvalidJson { get; } = new("invalid-json"); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// The document does not satisfy its media type's schema. + public static CatalogMalformedCardReason SchemaViolation { get; } = new("schema-violation"); -/// 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; } + /// The declared media type is not one this runtime understands. + public static CatalogMalformedCardReason UnsupportedMediaType { get; } = new("unsupported-media-type"); - /// GitHub frontend URL for this session. - [Url] - [StringSyntax(StringSyntaxAttribute.Uri)] - [JsonPropertyName("url")] - public string? Url { get; set; } -} + /// A field the media type requires is absent. + public static CatalogMalformedCardReason MissingRequiredField { get; } = new("missing-required-field"); -/// 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; } + /// The document exceeded the permitted size. + public static CatalogMalformedCardReason SizeLimitExceeded { get; } = new("size-limit-exceeded"); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(CatalogMalformedCardReason left, CatalogMalformedCardReason right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(CatalogMalformedCardReason left, CatalogMalformedCardReason right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is CatalogMalformedCardReason other && Equals(other); + + /// + public bool Equals(CatalogMalformedCardReason 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 CatalogMalformedCardReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, CatalogMalformedCardReason value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(CatalogMalformedCardReason)); + } + } } -/// 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. +/// Which wire-contract rule an upstream response broke. [Experimental(Diagnostics.Experimental)] -public sealed class RemoteNotifySteerableChangedResult +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct CatalogContractViolationReason : IEquatable { -} + private readonly string? _value; -/// 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; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public CatalogContractViolationReason(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Gets the value associated with this . + public string Value => _value ?? 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; } + /// A result carried both a URL and embedded data, when exactly one is permitted. + public static CatalogContractViolationReason BothUrlAndData { get; } = new("both-url-and-data"); - /// 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; } + /// A result carried neither a URL nor embedded data, when exactly one is required. + public static CatalogContractViolationReason NeitherUrlNorData { get; } = new("neither-url-nor-data"); - /// 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; } -} + /// Two results claimed the same normalised identity. + public static CatalogContractViolationReason DuplicateIdentity { get; } = new("duplicate-identity"); -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionVisibilityGetRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// A result declared no media type, or one this contract does not model. + public static CatalogContractViolationReason UnknownMediaType { get; } = new("unknown-media-type"); -/// 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; } + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(CatalogContractViolationReason left, CatalogContractViolationReason right) => left.Equals(right); - /// 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; } + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(CatalogContractViolationReason left, CatalogContractViolationReason right) => !(left == right); - /// 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; } -} + /// + public override bool Equals(object? obj) => obj is CatalogContractViolationReason other && Equals(other); -/// 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; + /// + public bool Equals(CatalogContractViolationReason other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// 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; } + /// + 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 CatalogContractViolationReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, CatalogContractViolationReason value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(CatalogContractViolationReason)); + } + } } -/// Scheduled prompt entry with ID, timing (`intervalMs`, `cron`, or `at`), prompt text, recurrence, and next run time. + +/// Why no usable transport could be offered. [Experimental(Diagnostics.Experimental)] -public sealed class ScheduleEntry +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct CatalogUnavailableTransportReason : IEquatable { - /// Absolute fire time (epoch milliseconds) for a one-shot calendar schedule. - [JsonPropertyName("at")] - public long? At { get; set; } + private readonly string? _value; - /// 5-field cron expression for a recurring calendar schedule, evaluated in `tz`. - [JsonPropertyName("cron")] - public string? Cron { get; set; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public CatalogUnavailableTransportReason(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// 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; } + /// Gets the value associated with this . + public string Value => _value ?? 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; } + /// The card advertises no transport this runtime can use. + public static CatalogUnavailableTransportReason NoEligibleTransport { get; } = new("no-eligible-transport"); - /// Interval between scheduled ticks, in milliseconds (relative-interval schedules). - [JsonConverter(typeof(MillisecondsTimeSpanConverter))] - [JsonPropertyName("intervalMs")] - public TimeSpan? Interval { get; set; } + /// Every advertised transport is of a kind this runtime does not implement. + public static CatalogUnavailableTransportReason TransportNotSupported { get; } = new("transport-not-supported"); - /// ISO 8601 timestamp when the next tick is scheduled to fire. - [JsonPropertyName("nextRunAt")] - public DateTimeOffset NextRunAt { get; set; } + /// Eligible remotes could not be enumerated, so no explicit choice can be offered. + public static CatalogUnavailableTransportReason RemoteEnumerationUnavailable { get; } = new("remote-enumeration-unavailable"); - /// Prompt text that gets enqueued on every tick. - [JsonPropertyName("prompt")] - public string Prompt { get; set; } = string.Empty; + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(CatalogUnavailableTransportReason left, CatalogUnavailableTransportReason right) => left.Equals(right); - /// Whether the schedule re-arms after each tick (`/every`) or fires once (`/after`). - [JsonPropertyName("recurring")] - public bool Recurring { get; set; } + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(CatalogUnavailableTransportReason left, CatalogUnavailableTransportReason right) => !(left == right); - /// 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; } + /// + public override bool Equals(object? obj) => obj is CatalogUnavailableTransportReason other && Equals(other); - /// IANA timezone the `cron` expression is evaluated in. - [JsonPropertyName("tz")] - public string? Tz { get; set; } -} + /// + public bool Equals(CatalogUnavailableTransportReason other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); -/// 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; } -} + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionScheduleListRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// + public override string ToString() => Value; -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionScheduleHydrateRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override CatalogUnavailableTransportReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } -/// 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; } + /// + public override void Write(Utf8JsonWriter writer, CatalogUnavailableTransportReason value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(CatalogUnavailableTransportReason)); + } + } } -/// 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. +/// Why a discoverable candidate cannot be installed. [Experimental(Diagnostics.Experimental)] -internal sealed class ScheduleAddResult +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct CatalogNotInstallableReason : IEquatable { - /// The registered or updated schedule entry. - [JsonPropertyName("entry")] - public ScheduleEntry? Entry { get; set; } + private readonly string? _value; - /// User-facing validation error, when registration failed. - [JsonPropertyName("error")] - public string? Error { get; set; } -} + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public CatalogNotInstallableReason(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } -/// 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; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Human-readable interval such as `30s`, `5m`, or `2h`. - [JsonPropertyName("interval")] - public string Interval { get; set; } = string.Empty; + /// This kind of resource is not installable through this surface. + public static CatalogNotInstallableReason KindNotInstallable { get; } = new("kind-not-installable"); - /// Prompt text to enqueue when the schedule fires. - [JsonPropertyName("prompt")] - public string Prompt { get; set; } = string.Empty; + /// AI skills are discoverable but have no typed importer in this phase. + public static CatalogNotInstallableReason AiSkillNotInstallable { get; } = new("ai-skill-not-installable"); - /// Whether the schedule should re-arm after each tick. Defaults to true. - [JsonPropertyName("recurring")] - public bool? Recurring { get; set; } + /// Policy forbids installing this candidate. + public static CatalogNotInstallableReason PolicyForbids { get; } = new("policy-forbids"); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(CatalogNotInstallableReason left, CatalogNotInstallableReason right) => left.Equals(right); -/// 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; + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(CatalogNotInstallableReason left, CatalogNotInstallableReason right) => !(left == right); - /// Optional display-only prompt label. - [JsonPropertyName("displayPrompt")] - public string? DisplayPrompt { get; set; } + /// + public override bool Equals(object? obj) => obj is CatalogNotInstallableReason other && Equals(other); - /// Prompt text to enqueue when the schedule fires. - [JsonPropertyName("prompt")] - public string Prompt { get; set; } = string.Empty; + /// + public bool Equals(CatalogNotInstallableReason other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - /// Whether the schedule should re-arm after each tick. Defaults to true. - [JsonPropertyName("recurring")] - public bool? Recurring { get; set; } + /// + public override string ToString() => Value; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override CatalogNotInstallableReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } - /// IANA timezone for evaluating the cron expression. - [JsonPropertyName("tz")] - public string? Tz { get; set; } + /// + public override void Write(Utf8JsonWriter writer, CatalogNotInstallableReason value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(CatalogNotInstallableReason)); + } + } } -/// Register an absolute-time scheduled prompt. + +/// Why a catalog operation is not available on this runtime. [Experimental(Diagnostics.Experimental)] -internal sealed class ScheduleAddAtRequest +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct CatalogUnavailableReason : IEquatable { - /// Epoch milliseconds when the prompt should fire. - [JsonPropertyName("at")] - public long At { get; set; } + private readonly string? _value; - /// Optional display-only prompt label. - [JsonPropertyName("displayPrompt")] - public string? DisplayPrompt { get; set; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public CatalogUnavailableReason(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Prompt text to enqueue when the schedule fires. - [JsonPropertyName("prompt")] - public string Prompt { get; set; } = string.Empty; + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Whether the schedule should re-arm after each tick. Defaults to false. - [JsonPropertyName("recurring")] - public bool? Recurring { get; set; } + /// Bounded search is not wired up on this runtime build. + public static CatalogUnavailableReason SearchUnavailable { get; } = new("search-unavailable"); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Install planning is not wired up on this runtime build. + public static CatalogUnavailableReason PlanningUnavailable { get; } = new("planning-unavailable"); -/// 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; } + /// No catalog authority is configured for this runtime. + public static CatalogUnavailableReason AuthorityNotConfigured { get; } = new("authority-not-configured"); - /// Prompt text to enqueue when the schedule fires. - [JsonPropertyName("prompt")] - public string Prompt { get; set; } = string.Empty; + /// The surface is disabled by policy on this runtime. + public static CatalogUnavailableReason DisabledByPolicy { get; } = new("disabled-by-policy"); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(CatalogUnavailableReason left, CatalogUnavailableReason right) => left.Equals(right); -/// 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; } + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(CatalogUnavailableReason left, CatalogUnavailableReason right) => !(left == right); - /// Id of the self-paced scheduled prompt. - [JsonPropertyName("id")] - public long Id { get; set; } + /// + public override bool Equals(object? obj) => obj is CatalogUnavailableReason other && Equals(other); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// + public bool Equals(CatalogUnavailableReason other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); -/// 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; } -} + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); -/// 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; } + /// + public override string ToString() => Value; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override CatalogUnavailableReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } -/// 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; + /// + public override void Write(Utf8JsonWriter writer, CatalogUnavailableReason value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(CatalogUnavailableReason)); + } + } } -/// 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. +/// Persisted extension discovery source. [Experimental(Diagnostics.Experimental)] -public sealed class FactoryExecuteResult +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct DiscoveredExtensionSource : IEquatable { - /// Factory result value. - [JsonPropertyName("result")] - public JsonElement? Result { get; set; } -} + private readonly string? _value; -/// 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; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public DiscoveredExtensionSource(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Opaque token identifying this factory execution attempt. - [JsonPropertyName("executionToken")] - public string ExecutionToken { get; set; } = string.Empty; + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Registered factory name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Extension discovered from the user's extensions directory. + public static DiscoveredExtensionSource User { get; } = new("user"); - /// Factory run identifier. - [JsonPropertyName("runId")] - public string RunId { get; set; } = string.Empty; + /// Extension contributed by an installed plugin. + public static DiscoveredExtensionSource Plugin { get; } = new("plugin"); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(DiscoveredExtensionSource left, DiscoveredExtensionSource right) => left.Equals(right); -/// 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; + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(DiscoveredExtensionSource left, DiscoveredExtensionSource right) => !(left == right); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// + public override bool Equals(object? obj) => obj is DiscoveredExtensionSource other && Equals(other); -/// Describes a filesystem error. -[Experimental(Diagnostics.Experimental)] -public sealed class SessionFsError -{ - /// Error classification. - [JsonPropertyName("code")] - public SessionFsErrorCode Code { get; set; } + /// + public bool Equals(DiscoveredExtensionSource other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// Free-form detail about the error, for logging/diagnostics. - [JsonPropertyName("message")] - public string? Message { get; set; } -} + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); -/// 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; + /// + public override string ToString() => Value; - /// Describes a filesystem error. - [JsonPropertyName("error")] - public SessionFsError? Error { get; set; } + /// 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)); + } + } } -/// Path of the file to read from the client-provided session filesystem. + +/// Effective extension loading and agent-management mode. [Experimental(Diagnostics.Experimental)] -public sealed class SessionFsReadFileRequest +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct DiscoveredExtensionMode : IEquatable { - /// Path using SessionFs conventions. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + 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; + } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Gets the value associated with this . + public string Value => _value ?? 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; + /// Extensions are not loaded. + public static DiscoveredExtensionMode Disabled { get; } = new("disabled"); - /// Optional POSIX-style mode for newly created files. - [JsonPropertyName("mode")] - public long? Mode { get; set; } + /// Extensions are loaded, but the agent cannot create, reload, or manage them. + public static DiscoveredExtensionMode LoadOnly { get; } = new("load_only"); - /// Path using SessionFs conventions. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// Extensions are loaded and the agent can create, reload, and manage them. + public static DiscoveredExtensionMode LoadAndAugment { get; } = new("load_and_augment"); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(DiscoveredExtensionMode left, DiscoveredExtensionMode right) => left.Equals(right); -/// 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; + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(DiscoveredExtensionMode left, DiscoveredExtensionMode right) => !(left == right); - /// Optional POSIX-style mode for newly created files. - [JsonPropertyName("mode")] - public long? Mode { get; set; } + /// + public override bool Equals(object? obj) => obj is DiscoveredExtensionMode other && Equals(other); - /// Path using SessionFs conventions. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// + public bool Equals(DiscoveredExtensionMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); -/// 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; } -} + /// + public override string ToString() => Value; -/// 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; + /// 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)); + } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// + public override void Write(Utf8JsonWriter writer, DiscoveredExtensionMode value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(DiscoveredExtensionMode)); + } + } } -/// Filesystem metadata for the requested path, or a filesystem error if the stat failed. + +/// Whether an MCP server candidate can be planned for installation. [Experimental(Diagnostics.Experimental)] -public sealed class SessionFsStatResult +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct CatalogMcpServerInstallability : IEquatable { - /// ISO 8601 timestamp of creation. - [JsonPropertyName("birthtime")] - public DateTimeOffset Birthtime { get; set; } - - /// Describes a filesystem error. - [JsonPropertyName("error")] - public SessionFsError? Error { get; set; } + private readonly string? _value; - /// Whether the path is a directory. - [JsonPropertyName("isDirectory")] - public bool IsDirectory { get; set; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public CatalogMcpServerInstallability(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Whether the path is a file. - [JsonPropertyName("isFile")] - public bool IsFile { get; set; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// ISO 8601 timestamp of last modification. - [JsonPropertyName("mtime")] - public DateTimeOffset Mtime { get; set; } + /// An install plan can be computed for this MCP server candidate. + public static CatalogMcpServerInstallability Installable { get; } = new("installable"); - /// File size in bytes. - [JsonPropertyName("size")] - public long Size { get; set; } -} + /// Policy forbids installing this MCP server candidate. + public static CatalogMcpServerInstallability NotInstallablePolicy { get; } = new("not-installable-policy"); -/// 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; + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(CatalogMcpServerInstallability left, CatalogMcpServerInstallability right) => left.Equals(right); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(CatalogMcpServerInstallability left, CatalogMcpServerInstallability right) => !(left == right); -/// 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; } + /// + public override bool Equals(object? obj) => obj is CatalogMcpServerInstallability other && Equals(other); - /// Path using SessionFs conventions. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// + public bool Equals(CatalogMcpServerInstallability other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// Create parent directories as needed. - [JsonPropertyName("recursive")] - public bool? Recursive { get; set; } + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// + public override string ToString() => Value; -/// 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; } + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override CatalogMcpServerInstallability Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } - /// Describes a filesystem error. - [JsonPropertyName("error")] - public SessionFsError? Error { get; set; } + /// + public override void Write(Utf8JsonWriter writer, CatalogMcpServerInstallability value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(CatalogMcpServerInstallability)); + } + } } -/// Directory path whose entries should be listed from the client-provided session filesystem. + +/// What kind of resource a catalog candidate describes. [Experimental(Diagnostics.Experimental)] -public sealed class SessionFsReaddirRequest +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct CatalogCandidateKind : IEquatable { - /// Path using SessionFs conventions. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + private readonly string? _value; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public CatalogCandidateKind(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } -/// 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; + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Entry type. - [JsonPropertyName("type")] - public SessionFsReaddirWithTypesEntryType Type { get; set; } -} + /// An MCP server, which can be planned for installation. + public static CatalogCandidateKind McpServer { get; } = new("mcp-server"); -/// 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; } + /// An AI skill, which is discoverable but not installable through this surface. + public static CatalogCandidateKind AiSkill { get; } = new("ai-skill"); - /// Describes a filesystem error. - [JsonPropertyName("error")] - public SessionFsError? Error { get; set; } -} + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(CatalogCandidateKind left, CatalogCandidateKind right) => left.Equals(right); -/// 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; + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(CatalogCandidateKind left, CatalogCandidateKind right) => !(left == right); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// + public override bool Equals(object? obj) => obj is CatalogCandidateKind other && Equals(other); -/// 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; } + /// + public bool Equals(CatalogCandidateKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// Path using SessionFs conventions. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - /// Remove directories and their contents recursively. - [JsonPropertyName("recursive")] - public bool? Recursive { get; set; } + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override CatalogCandidateKind 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, CatalogCandidateKind value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(CatalogCandidateKind)); + } + } } -/// Source and destination paths for renaming or moving an entry in the client-provided session filesystem. + +/// Which tier this directory belongs to. [Experimental(Diagnostics.Experimental)] -public sealed class SessionFsRenameRequest +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct SkillDiscoveryScope : IEquatable { - /// Destination path using SessionFs conventions. - [JsonPropertyName("dest")] - public string Dest { get; set; } = string.Empty; + private readonly string? _value; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public SkillDiscoveryScope(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Source path using SessionFs conventions. - [JsonPropertyName("src")] - public string Src { get; set; } = string.Empty; -} + /// Gets the value associated with this . + public string Value => _value ?? 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; } + /// A project's repository skill directory. + public static SkillDiscoveryScope Project { get; } = new("project"); - /// Describes a filesystem error. - [JsonPropertyName("error")] - public SessionFsError? Error { get; set; } + /// The user's personal Copilot skill directory. + public static SkillDiscoveryScope PersonalCopilot { get; } = new("personal-copilot"); - /// SQLite last_insert_rowid() value for INSERT. - [JsonPropertyName("lastInsertRowid")] - public long? LastInsertRowid { get; set; } + /// The user's personal agents skill directory. + public static SkillDiscoveryScope PersonalAgents { get; } = new("personal-agents"); - /// For SELECT: array of row objects. For others: empty array. - [JsonPropertyName("rows")] - public IList> Rows { get => field ??= []; set; } + /// A configured custom skill directory. + public static SkillDiscoveryScope Custom { get; } = new("custom"); - /// Number of rows affected (for INSERT/UPDATE/DELETE). - [JsonPropertyName("rowsAffected")] - public long RowsAffected { get; set; } -} + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SkillDiscoveryScope left, SkillDiscoveryScope right) => left.Equals(right); -/// 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; } + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SkillDiscoveryScope left, SkillDiscoveryScope right) => !(left == right); - /// SQL query to execute. - [JsonPropertyName("query")] - public string Query { get; set; } = string.Empty; + /// + public override bool Equals(object? obj) => obj is SkillDiscoveryScope other && Equals(other); - /// 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; } + /// + public bool Equals(SkillDiscoveryScope other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); -/// 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; } + /// + public override string ToString() => Value; - /// Gets or sets the message value. - [JsonPropertyName("message")] - public string Message { get; set; } = string.Empty; + /// 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)); + } + } } -/// Per-statement results, or a classified transaction error. + +/// Where the agent definition was loaded from. [Experimental(Diagnostics.Experimental)] -public sealed class SessionFsSqliteTransactionResult +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct AgentInfoSource : IEquatable { - /// Gets or sets the error value. - [JsonPropertyName("error")] - public SessionFsSqliteTransactionError? Error { get; set; } + private readonly string? _value; - /// Gets or sets the results value. - [JsonPropertyName("results")] - public IList Results { get => field ??= []; set; } -} + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public AgentInfoSource(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } -/// 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; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// SQL statement to execute. - [JsonPropertyName("query")] - public string Query { get; set; } = string.Empty; + /// Agent loaded from the user's personal agent configuration. + public static AgentInfoSource User { get; } = new("user"); - /// How to execute the statement. - [JsonPropertyName("queryType")] - public SessionFsSqliteQueryType QueryType { get; set; } -} + /// Agent loaded from the current project's repository configuration. + public static AgentInfoSource Project { get; } = new("project"); -/// 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; + /// Agent inherited from a parent project or workspace. + public static AgentInfoSource Inherited { get; } = new("inherited"); - /// Gets or sets the statements value. - [JsonPropertyName("statements")] - public IList Statements { get => field ??= []; set; } -} + /// Agent provided by a remote runtime or service. + public static AgentInfoSource Remote { get; } = new("remote"); -/// 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; } -} + /// Agent contributed by an installed plugin. + public static AgentInfoSource Plugin { get; } = new("plugin"); -/// Identifies the target session. -public sealed class SessionFsSqliteExistsRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Agent built into the Copilot runtime. + public static AgentInfoSource Builtin { get; } = new("builtin"); -/// 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; } + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(AgentInfoSource left, AgentInfoSource right) => left.Equals(right); - /// Provider-supplied title. - [JsonPropertyName("title")] - public string? Title { get; set; } + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AgentInfoSource left, AgentInfoSource right) => !(left == right); - /// URL for web-rendered canvases. - [JsonPropertyName("url")] - public string? Url { get; set; } -} + /// + public override bool Equals(object? obj) => obj is AgentInfoSource other && Equals(other); -/// Host capabilities. -[Experimental(Diagnostics.Experimental)] -public sealed class CanvasHostContextCapabilities -{ - /// Whether canvas rendering is supported. - [JsonPropertyName("canvases")] - public bool? Canvases { get; set; } -} + /// + public bool Equals(AgentInfoSource other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); -/// Host context supplied by the runtime. -[Experimental(Diagnostics.Experimental)] -public sealed class CanvasHostContext -{ - /// Host capabilities. - [JsonPropertyName("capabilities")] - public CanvasHostContextCapabilities? Capabilities { get; set; } -} + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); -/// 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; } + /// + 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)); + } + } } -/// Canvas open parameters sent to the provider. + +/// Which tier this directory belongs to. [Experimental(Diagnostics.Experimental)] -public sealed class CanvasProviderOpenRequest +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct AgentDiscoveryPathScope : IEquatable { - /// Provider-local canvas identifier. - [JsonPropertyName("canvasId")] - public string CanvasId { get; set; } = string.Empty; + private readonly string? _value; - /// Owning provider identifier. - [JsonPropertyName("extensionId")] - public string ExtensionId { get; set; } = string.Empty; + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public AgentDiscoveryPathScope(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Host context supplied by the runtime. - [JsonPropertyName("host")] - public CanvasHostContext? Host { get; set; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Canvas open input. - [JsonPropertyName("input")] - public JsonElement? Input { get; set; } + /// The user's personal agent configuration directory. + public static AgentDiscoveryPathScope User { get; } = new("user"); - /// Stable caller-supplied canvas instance identifier. - [JsonPropertyName("instanceId")] - public string InstanceId { get; set; } = string.Empty; + /// A project's repository agent directory. + public static AgentDiscoveryPathScope Project { get; } = new("project"); - /// Session context supplied by the runtime. - [JsonPropertyName("session")] - public CanvasSessionContext? Session { get; set; } + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(AgentDiscoveryPathScope left, AgentDiscoveryPathScope right) => left.Equals(right); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AgentDiscoveryPathScope left, AgentDiscoveryPathScope right) => !(left == right); -/// 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; + /// + public override bool Equals(object? obj) => obj is AgentDiscoveryPathScope other && Equals(other); - /// Owning provider identifier. - [JsonPropertyName("extensionId")] - public string ExtensionId { get; set; } = string.Empty; + /// + public bool Equals(AgentDiscoveryPathScope other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// Host context supplied by the runtime. - [JsonPropertyName("host")] - public CanvasHostContext? Host { 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 AgentDiscoveryPathScope 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, AgentDiscoveryPathScope value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentDiscoveryPathScope)); + } + } } -/// Canvas action invocation parameters sent to the provider. + +/// Where this source lives — used for UI grouping. [Experimental(Diagnostics.Experimental)] -public sealed class CanvasProviderInvokeActionRequest +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct InstructionSourceLocation : IEquatable { - /// 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; } + private readonly string? _value; - /// Canvas instance identifier. - [JsonPropertyName("instanceId")] - public string InstanceId { get; set; } = string.Empty; + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public InstructionSourceLocation(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Session context supplied by the runtime. - [JsonPropertyName("session")] - public CanvasSessionContext? Session { get; set; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Instructions live in user-level configuration. + public static InstructionSourceLocation User { get; } = new("user"); -/// 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 -{ -} + /// Instructions live in repository-level configuration. + public static InstructionSourceLocation Repository { get; } = new("repository"); -/// 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; } + /// Instructions live under the current working directory. + public static InstructionSourceLocation WorkingDirectory { get; } = new("working-directory"); - /// 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; } + /// Instructions live in plugin-provided configuration. + public static InstructionSourceLocation Plugin { get; } = new("plugin"); - /// Gets or sets the headers value. - [JsonPropertyName("headers")] - public IDictionary> Headers { get => field ??= new Dictionary>(); set; } + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(InstructionSourceLocation left, InstructionSourceLocation right) => left.Equals(right); - /// 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; } + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(InstructionSourceLocation left, InstructionSourceLocation right) => !(left == right); - /// HTTP method, e.g. GET, POST. - [JsonPropertyName("method")] - public string Method { get; set; } = string.Empty; + /// + public override bool Equals(object? obj) => obj is InstructionSourceLocation other && Equals(other); - /// 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; } + /// + public bool Equals(InstructionSourceLocation other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// 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; + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - /// 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 string ToString() => Value; - /// 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; } + /// 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)); + } - /// Absolute request URL. - [JsonPropertyName("url")] - public string Url { get; set; } = string.Empty; + /// + public override void Write(Utf8JsonWriter writer, InstructionSourceLocation value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(InstructionSourceLocation)); + } + } } -/// 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. +/// Category of instruction source — used for merge logic. [Experimental(Diagnostics.Experimental)] -public sealed class LlmInferenceHttpRequestChunkRequest +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct InstructionSourceType : 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 InstructionSourceType(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; } + /// Instructions loaded from the user's home configuration. + public static InstructionSourceType Home { get; } = new("home"); - /// 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; + /// 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"); - /// 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; } + /// Instructions loaded from VS Code instruction files. + public static InstructionSourceType Vscode { get; } = new("vscode"); - /// Matches the requestId from the originating httpRequestStart frame. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; -} + /// Instructions discovered from nested agent files. + public static InstructionSourceType NestedAgents { get; } = new("nested-agents"); -/// 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; + /// Instructions inherited from child instruction files. + public static InstructionSourceType ChildInstructions { get; } = new("child-instructions"); - /// Name of the client application. - [JsonPropertyName("client_name")] - public string? ClientName { get; set; } + /// Instructions supplied by an installed plugin. + public static InstructionSourceType Plugin { get; } = new("plugin"); - /// Type of client. - [JsonPropertyName("client_type")] - public string? ClientType { get; set; } + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(InstructionSourceType left, InstructionSourceType right) => left.Equals(right); - /// Copilot subscription plan, when known. - [JsonPropertyName("copilot_plan")] - public string? CopilotPlan { get; set; } + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(InstructionSourceType left, InstructionSourceType right) => !(left == right); - /// Stable machine identifier for the device. - [JsonPropertyName("dev_device_id")] - public string? DevDeviceId { get; set; } + /// + public override bool Equals(object? obj) => obj is InstructionSourceType other && Equals(other); - /// Whether the user is a GitHub/Microsoft staff member. - [JsonPropertyName("is_staff")] - public bool? IsStaff { get; set; } + /// + public bool Equals(InstructionSourceType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// Node.js runtime version string. - [JsonPropertyName("node_version")] - public string NodeVersion { get; set; } = string.Empty; + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - /// Operating system architecture (e.g. arm64, x64). - [JsonPropertyName("os_arch")] - public string OsArch { get; set; } = string.Empty; + /// + public override string ToString() => Value; - /// Operating system platform (e.g. darwin, linux, win32). - [JsonPropertyName("os_platform")] - public string OsPlatform { get; set; } = string.Empty; + /// 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)); + } - /// Operating system version string. - [JsonPropertyName("os_version")] - public string OsVersion { get; set; } = string.Empty; + /// + public override void Write(Utf8JsonWriter writer, InstructionSourceType value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(InstructionSourceType)); + } + } } -/// 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. + +/// Whether the target is a single file or a directory of instruction files. [Experimental(Diagnostics.Experimental)] -public sealed class GitHubTelemetryEvent +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct InstructionDiscoveryPathKind : 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 InstructionDiscoveryPathKind(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; } + /// The target is a single instruction file. + public static InstructionDiscoveryPathKind File { get; } = new("file"); - /// Feature flags enabled for this session, as a map from flag to value. - [JsonPropertyName("features")] - public IDictionary? Features { get; set; } + /// The target is a directory that holds instruction files. + public static InstructionDiscoveryPathKind Directory { get; } = new("directory"); - /// Event type/kind (e.g. get_completion_with_tools_turn, tool_call_executed). - [JsonPropertyName("kind")] - public string Kind { get; set; } = string.Empty; + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(InstructionDiscoveryPathKind left, InstructionDiscoveryPathKind right) => left.Equals(right); - /// Numeric metrics as a map from key to value. - [JsonPropertyName("metrics")] - public IDictionary Metrics { get => field ??= new Dictionary(); set; } + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(InstructionDiscoveryPathKind left, InstructionDiscoveryPathKind right) => !(left == right); - /// Reference to the model call that produced this event. - [JsonPropertyName("model_call_id")] - public string? ModelCallId { get; set; } + /// + public override bool Equals(object? obj) => obj is InstructionDiscoveryPathKind other && Equals(other); - /// String-valued properties as a map from key to value. - [JsonPropertyName("properties")] - public IDictionary Properties { get => field ??= new Dictionary(); set; } + /// + public bool Equals(InstructionDiscoveryPathKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// Session identifier the event belongs to. - [JsonPropertyName("session_id")] - public string? SessionId { get; set; } -} + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); -/// 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; } + /// + public override string ToString() => Value; - /// 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; } + /// 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)); + } - /// 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 override void Write(Utf8JsonWriter writer, InstructionDiscoveryPathKind value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(InstructionDiscoveryPathKind)); + } + } } -/// Resolved Anthropic adaptive-thinking capability for a model. + +/// Which tier this target belongs to. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct AdaptiveThinkingSupport : IEquatable +public readonly struct InstructionDiscoveryPathLocation : 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 InstructionDiscoveryPathLocation(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"); + /// Instructions live in user-level configuration. + public static InstructionDiscoveryPathLocation User { get; } = new("user"); - /// The model accepts adaptive thinking but also accepts thinking.type='enabled'. - public static AdaptiveThinkingSupport Optional { get; } = new("optional"); + /// Instructions live in repository-level configuration. + public static InstructionDiscoveryPathLocation Repository { get; } = new("repository"); - /// 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"); + /// Instructions live under the current working directory. + public static InstructionDiscoveryPathLocation WorkingDirectory { get; } = new("working-directory"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(AdaptiveThinkingSupport left, AdaptiveThinkingSupport right) => left.Equals(right); + /// Instructions live in plugin-provided configuration. + public static InstructionDiscoveryPathLocation Plugin { get; } = new("plugin"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(AdaptiveThinkingSupport left, AdaptiveThinkingSupport right) => !(left == right); + /// 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 AdaptiveThinkingSupport other && Equals(other); + public override bool Equals(object? obj) => obj is InstructionDiscoveryPathLocation other && Equals(other); /// - public bool Equals(AdaptiveThinkingSupport other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(InstructionDiscoveryPathLocation other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -15181,65 +21868,59 @@ 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 InstructionDiscoveryPathLocation 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, InstructionDiscoveryPathLocation value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AdaptiveThinkingSupport)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(InstructionDiscoveryPathLocation)); } } } -/// Model capability category for grouping in the model picker. +/// 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 ModelPickerCategory : IEquatable +public readonly struct SlashCommandInputCompletion : 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 SlashCommandInputCompletion(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"); - - /// Powerful model category optimized for complex tasks. - public static ModelPickerCategory Powerful { get; } = new("powerful"); + /// 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 ==(ModelPickerCategory left, ModelPickerCategory right) => left.Equals(right); + /// 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 !=(ModelPickerCategory left, ModelPickerCategory right) => !(left == 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 ModelPickerCategory other && Equals(other); + public override bool Equals(object? obj) => obj is SlashCommandInputCompletion other && Equals(other); /// - public bool Equals(ModelPickerCategory other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(SlashCommandInputCompletion other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -15247,68 +21928,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 SlashCommandInputCompletion 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, SlashCommandInputCompletion value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ModelPickerCategory)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SlashCommandInputCompletion)); } } } -/// Relative cost tier for token-based billing users. +/// 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 ModelPickerPriceCategory : IEquatable +public readonly struct SlashCommandKind : 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 SlashCommandKind(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"); + /// Command implemented by the runtime. + public static SlashCommandKind Builtin { get; } = new("builtin"); - /// High relative token cost tier. - public static ModelPickerPriceCategory High { get; } = new("high"); + /// Command backed by a skill. + public static SlashCommandKind Skill { get; } = new("skill"); - /// Highest relative token cost tier. - public static ModelPickerPriceCategory VeryHigh { get; } = new("very_high"); + /// 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 ==(ModelPickerPriceCategory left, ModelPickerPriceCategory right) => left.Equals(right); + /// 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 !=(ModelPickerPriceCategory left, ModelPickerPriceCategory right) => !(left == 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 ModelPickerPriceCategory other && Equals(other); + public override bool Equals(object? obj) => obj is SlashCommandKind other && Equals(other); /// - public bool Equals(ModelPickerPriceCategory other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(SlashCommandKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -15316,65 +21994,62 @@ 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 SlashCommandKind 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, SlashCommandKind value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ModelPickerPriceCategory)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SlashCommandKind)); } } } -/// Current policy state for this model. +/// Path conventions used by this filesystem. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ModelPolicyState : IEquatable +public readonly struct SessionFsSetProviderConventions : 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 SessionFsSetProviderConventions(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"); - - /// The model is disabled by policy. - public static ModelPolicyState Disabled { get; } = new("disabled"); + /// Paths use Windows path conventions. + public static SessionFsSetProviderConventions Windows { get; } = new("windows"); - /// No explicit policy is configured for the model. - public static ModelPolicyState Unconfigured { get; } = new("unconfigured"); + /// Paths use POSIX path conventions. + public static SessionFsSetProviderConventions Posix { get; } = new("posix"); - /// 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 equivalent. + public static bool operator ==(SessionFsSetProviderConventions left, SessionFsSetProviderConventions right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(ModelPolicyState left, ModelPolicyState right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SessionFsSetProviderConventions left, SessionFsSetProviderConventions right) => !(left == right); /// - public override bool Equals(object? obj) => obj is ModelPolicyState other && Equals(other); + public override bool Equals(object? obj) => obj is SessionFsSetProviderConventions other && Equals(other); /// - public bool Equals(ModelPolicyState other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(SessionFsSetProviderConventions other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -15382,68 +22057,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 SessionFsSetProviderConventions 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, SessionFsSetProviderConventions value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ModelPolicyState)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionFsSetProviderConventions)); } } } -/// Server transport type: stdio, http, sse (deprecated), or memory. +/// Repository host type. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct DiscoveredMcpServerType : IEquatable +public readonly struct SessionContextHostType : 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 SessionContextHostType(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"); + /// Session repository is hosted on GitHub. + public static SessionContextHostType GitHub { get; } = new("github"); - /// Server is backed by an in-memory runtime implementation. - public static DiscoveredMcpServerType Memory { get; } = new("memory"); + /// Session repository is hosted on Azure DevOps. + public static SessionContextHostType Ado { get; } = new("ado"); - /// 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 ==(SessionContextHostType left, SessionContextHostType 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 !=(SessionContextHostType left, SessionContextHostType right) => !(left == right); /// - public override bool Equals(object? obj) => obj is DiscoveredMcpServerType other && Equals(other); + public override bool Equals(object? obj) => obj is SessionContextHostType other && Equals(other); /// - public bool Equals(DiscoveredMcpServerType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(SessionContextHostType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -15451,62 +22120,68 @@ 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 SessionContextHostType 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, SessionContextHostType value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(DiscoveredMcpServerType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionContextHostType)); } } } -/// Persisted extension discovery source. +/// What a remote host says one of its sessions is doing right now. Deliberately coarse: this is what a host can report for EVERY session in a catalogue listing, without a client subscribing to each one. AHP's `SessionSummary.status` is the source today; `input-needed` covers both a permission prompt and an `ask_user` question, since the summary does not say which. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct DiscoveredExtensionSource : IEquatable +public readonly struct RemoteSessionHostStatus : 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 DiscoveredExtensionSource(string value) + public RemoteSessionHostStatus(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 user's extensions directory. - public static DiscoveredExtensionSource User { get; } = new("user"); + /// No turn is running. + public static RemoteSessionHostStatus Idle { get; } = new("idle"); - /// Extension contributed by an installed plugin. - public static DiscoveredExtensionSource Plugin { get; } = new("plugin"); + /// A turn is running. + public static RemoteSessionHostStatus Working { get; } = new("working"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(DiscoveredExtensionSource left, DiscoveredExtensionSource right) => left.Equals(right); + /// The session is blocked on the user: a permission prompt or an `ask_user` question. + public static RemoteSessionHostStatus InputNeeded { get; } = new("input-needed"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(DiscoveredExtensionSource left, DiscoveredExtensionSource right) => !(left == right); + /// The session ended its last turn in an error. + public static RemoteSessionHostStatus Error { get; } = new("error"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(RemoteSessionHostStatus left, RemoteSessionHostStatus right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(RemoteSessionHostStatus left, RemoteSessionHostStatus right) => !(left == right); /// - public override bool Equals(object? obj) => obj is DiscoveredExtensionSource other && Equals(other); + public override bool Equals(object? obj) => obj is RemoteSessionHostStatus other && Equals(other); /// - public bool Equals(DiscoveredExtensionSource other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(RemoteSessionHostStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -15514,65 +22189,62 @@ public DiscoveredExtensionSource(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 DiscoveredExtensionSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override RemoteSessionHostStatus 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) + public override void Write(Utf8JsonWriter writer, RemoteSessionHostStatus value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(DiscoveredExtensionSource)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(RemoteSessionHostStatus)); } } } -/// Effective extension loading and agent-management mode. +/// Whether the remote task originated from CCA or CLI `--remote`. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct DiscoveredExtensionMode : IEquatable +public readonly struct RemoteSessionMetadataTaskType : 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 DiscoveredExtensionMode(string value) + public RemoteSessionMetadataTaskType(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; - /// 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"); + /// GitHub Copilot coding agent task. + public static RemoteSessionMetadataTaskType Cca { get; } = new("cca"); - /// Extensions are loaded and the agent can create, reload, and manage them. - public static DiscoveredExtensionMode LoadAndAugment { get; } = new("load_and_augment"); + /// CLI remote task. + public static RemoteSessionMetadataTaskType Cli { get; } = new("cli"); - /// 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 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 !=(DiscoveredExtensionMode left, DiscoveredExtensionMode right) => !(left == 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 DiscoveredExtensionMode other && Equals(other); + public override bool Equals(object? obj) => obj is RemoteSessionMetadataTaskType other && Equals(other); /// - public bool Equals(DiscoveredExtensionMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(RemoteSessionMetadataTaskType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -15580,68 +22252,62 @@ public DiscoveredExtensionMode(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 DiscoveredExtensionMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override RemoteSessionMetadataTaskType 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) + public override void Write(Utf8JsonWriter writer, RemoteSessionMetadataTaskType value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(DiscoveredExtensionMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(RemoteSessionMetadataTaskType)); } } } -/// Which tier this directory belongs to. +/// Step status. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct SkillDiscoveryScope : IEquatable +public readonly struct SessionsOpenProgressStatus : 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 SessionsOpenProgressStatus(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"); + /// The step has started and has not yet finished. + public static SessionsOpenProgressStatus InProgress { get; } = new("in-progress"); - /// A configured custom skill directory. - public static SkillDiscoveryScope Custom { get; } = new("custom"); + /// The step has completed successfully. + public static SessionsOpenProgressStatus Complete { get; } = new("complete"); - /// 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 ==(SessionsOpenProgressStatus left, SessionsOpenProgressStatus 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 !=(SessionsOpenProgressStatus left, SessionsOpenProgressStatus right) => !(left == right); /// - public override bool Equals(object? obj) => obj is SkillDiscoveryScope other && Equals(other); + public override bool Equals(object? obj) => obj is SessionsOpenProgressStatus other && Equals(other); /// - public bool Equals(SkillDiscoveryScope other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(SessionsOpenProgressStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -15649,74 +22315,74 @@ 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 SessionsOpenProgressStatus 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, SessionsOpenProgressStatus value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SkillDiscoveryScope)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionsOpenProgressStatus)); } } } -/// Where the agent definition was loaded from. +/// Handoff step. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct AgentInfoSource : IEquatable +public readonly struct SessionsOpenProgressStep : 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 SessionsOpenProgressStep(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"); + /// Loading the source session's events from the remote service. + public static SessionsOpenProgressStep LoadSession { get; } = new("load-session"); - /// Agent loaded from the current project's repository configuration. - public static AgentInfoSource Project { get; } = new("project"); + /// Validating that the local repository matches the remote session's repository. + public static SessionsOpenProgressStep ValidateRepo { get; } = new("validate-repo"); - /// Agent inherited from a parent project or workspace. - public static AgentInfoSource Inherited { get; } = new("inherited"); + /// Checking the local working tree for uncommitted changes that would block the handoff. + public static SessionsOpenProgressStep CheckChanges { get; } = new("check-changes"); - /// Agent provided by a remote runtime or service. - public static AgentInfoSource Remote { get; } = new("remote"); + /// Checking out the branch associated with the remote session in the local working tree. + public static SessionsOpenProgressStep CheckoutBranch { get; } = new("checkout-branch"); - /// Agent contributed by an installed plugin. - public static AgentInfoSource Plugin { get; } = new("plugin"); + /// Creating the new local session and seeding it with the source session's events. + public static SessionsOpenProgressStep CreateSession { get; } = new("create-session"); - /// Agent built into the Copilot runtime. - public static AgentInfoSource Builtin { get; } = new("builtin"); + /// Persisting the newly-created local session to disk. + public static SessionsOpenProgressStep SaveSession { get; } = new("save-session"); - /// 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 ==(SessionsOpenProgressStep left, SessionsOpenProgressStep 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 !=(SessionsOpenProgressStep left, SessionsOpenProgressStep right) => !(left == right); /// - public override bool Equals(object? obj) => obj is AgentInfoSource other && Equals(other); + public override bool Equals(object? obj) => obj is SessionsOpenProgressStep other && Equals(other); /// - public bool Equals(AgentInfoSource other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(SessionsOpenProgressStep other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -15724,62 +22390,71 @@ 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 SessionsOpenProgressStep 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, SessionsOpenProgressStep value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentInfoSource)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionsOpenProgressStep)); } } } -/// Which tier this directory belongs to. +/// Outcome of the open request. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct AgentDiscoveryPathScope : 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 AgentDiscoveryPathScope(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 user's personal agent configuration directory. - public static AgentDiscoveryPathScope User { get; } = new("user"); + /// A new session was created. + public static SessionsOpenStatus Created { get; } = new("created"); - /// A project's repository agent directory. - public static AgentDiscoveryPathScope Project { get; } = new("project"); + /// An existing session was loaded or reattached. + public static SessionsOpenStatus Resumed { get; } = new("resumed"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(AgentDiscoveryPathScope left, AgentDiscoveryPathScope right) => left.Equals(right); + /// No matching persisted session was found. + public static SessionsOpenStatus NotFound { get; } = new("not_found"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(AgentDiscoveryPathScope left, AgentDiscoveryPathScope right) => !(left == right); + /// Connected to an existing remote session. + public static SessionsOpenStatus Connected { get; } = new("connected"); + + /// 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 AgentDiscoveryPathScope other && Equals(other); + public override bool Equals(object? obj) => obj is SessionsOpenStatus other && Equals(other); /// - public bool Equals(AgentDiscoveryPathScope 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); @@ -15787,68 +22462,62 @@ 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 SessionsOpenStatus 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, SessionsOpenStatus value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentDiscoveryPathScope)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionsOpenStatus)); } } } -/// Where this source lives — used for UI grouping. +/// Neutral SDK discriminator for the connected remote session kind. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct InstructionSourceLocation : 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 InstructionSourceLocation(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; - /// 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"); + /// Remote CLI session. + public static ConnectedRemoteSessionMetadataKind RemoteSession { get; } = new("remote-session"); - /// Instructions live in plugin-provided configuration. - public static InstructionSourceLocation Plugin { get; } = new("plugin"); + /// 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 ==(InstructionSourceLocation left, InstructionSourceLocation 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 !=(InstructionSourceLocation left, InstructionSourceLocation 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 InstructionSourceLocation other && Equals(other); + public override bool Equals(object? obj) => obj is ConnectedRemoteSessionMetadataKind other && Equals(other); /// - public bool Equals(InstructionSourceLocation 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); @@ -15856,77 +22525,65 @@ 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 ConnectedRemoteSessionMetadataKind 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, ConnectedRemoteSessionMetadataKind value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(InstructionSourceLocation)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ConnectedRemoteSessionMetadataKind)); } } } -/// Category of instruction source — used for merge logic. +/// Which session sources to include. Defaults to `local` for backward compatibility. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct InstructionSourceType : 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 InstructionSourceType(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; - /// 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"); + /// Return only local sessions. + public static SessionSource Local { get; } = new("local"); - /// Instructions inherited from child instruction files. - public static InstructionSourceType ChildInstructions { get; } = new("child-instructions"); + /// Return only remote sessions. + public static SessionSource Remote { get; } = new("remote"); - /// Instructions supplied by an installed plugin. - public static InstructionSourceType Plugin { get; } = new("plugin"); + /// 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 ==(InstructionSourceType left, InstructionSourceType 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 !=(InstructionSourceType left, InstructionSourceType 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 InstructionSourceType other && Equals(other); + public override bool Equals(object? obj) => obj is SessionSource other && Equals(other); /// - public bool Equals(InstructionSourceType 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); @@ -15934,62 +22591,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 SessionSource 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, SessionSource value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(InstructionSourceType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionSource)); } } } -/// Whether the target is a single file or a directory of instruction files. +/// 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 InstructionDiscoveryPathKind : 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 InstructionDiscoveryPathKind(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; - /// The target is a single instruction file. - public static InstructionDiscoveryPathKind File { get; } = new("file"); + /// The cursor was applied successfully. + public static EventsCursorStatus Ok { get; } = new("ok"); - /// The target is a directory that holds instruction files. - public static InstructionDiscoveryPathKind Directory { get; } = new("directory"); + /// 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 ==(InstructionDiscoveryPathKind left, InstructionDiscoveryPathKind 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 !=(InstructionDiscoveryPathKind left, InstructionDiscoveryPathKind 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 InstructionDiscoveryPathKind other && Equals(other); + public override bool Equals(object? obj) => obj is EventsCursorStatus other && Equals(other); /// - public bool Equals(InstructionDiscoveryPathKind 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); @@ -15997,68 +22654,62 @@ 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 EventsCursorStatus 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, EventsCursorStatus value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(InstructionDiscoveryPathKind)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(EventsCursorStatus)); } } } -/// Which tier this target belongs to. +/// 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 InstructionDiscoveryPathLocation : 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 InstructionDiscoveryPathLocation(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; - /// 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"); + /// Page from the cursor toward newer events (default). + public static EventsReadDirection Forward { get; } = new("forward"); - /// Instructions live in plugin-provided configuration. - public static InstructionDiscoveryPathLocation Plugin { get; } = new("plugin"); + /// Tail-first: return the newest events and page toward older events. + public static EventsReadDirection Backward { get; } = new("backward"); - /// 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 ==(EventsReadDirection left, EventsReadDirection 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 !=(EventsReadDirection left, EventsReadDirection right) => !(left == right); /// - public override bool Equals(object? obj) => obj is InstructionDiscoveryPathLocation other && Equals(other); + public override bool Equals(object? obj) => obj is EventsReadDirection other && Equals(other); /// - public bool Equals(InstructionDiscoveryPathLocation 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); @@ -16066,59 +22717,71 @@ 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 EventsReadDirection 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, EventsReadDirection value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(InstructionDiscoveryPathLocation)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(EventsReadDirection)); } } } -/// Optional completion hint for the input (e.g. 'directory' for filesystem path completion). +/// Kind of attention required when status === "attention". Meaningful only when status === "attention". [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct SlashCommandInputCompletion : 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 SlashCommandInputCompletion(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; - /// Input should complete filesystem directories. - public static SlashCommandInputCompletion Directory { get; } = new("directory"); + /// Session is blocked on an unrecoverable error. + public static AgentRegistryLiveTargetEntryAttentionKind Error { get; } = new("error"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SlashCommandInputCompletion left, SlashCommandInputCompletion right) => left.Equals(right); + /// Session is waiting for a tool-permission decision. + public static AgentRegistryLiveTargetEntryAttentionKind Permission { get; } = new("permission"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SlashCommandInputCompletion left, SlashCommandInputCompletion right) => !(left == right); + /// Session is waiting for the user to approve or reject a plan. + public static AgentRegistryLiveTargetEntryAttentionKind ExitPlan { get; } = new("exit_plan"); + + /// Session is waiting on an elicitation prompt. + public static AgentRegistryLiveTargetEntryAttentionKind Elicitation { get; } = new("elicitation"); + + /// 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 SlashCommandInputCompletion other && Equals(other); + public override bool Equals(object? obj) => obj is AgentRegistryLiveTargetEntryAttentionKind other && Equals(other); /// - public bool Equals(SlashCommandInputCompletion 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); @@ -16126,65 +22789,62 @@ 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 AgentRegistryLiveTargetEntryAttentionKind 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, AgentRegistryLiveTargetEntryAttentionKind value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SlashCommandInputCompletion)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentRegistryLiveTargetEntryAttentionKind)); } } } -/// Coarse command category for grouping and behavior: runtime built-in, skill-backed command, or SDK/client-owned command. +/// Process kind tag for the registry entry. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct SlashCommandKind : 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 SlashCommandKind(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; - /// Command implemented by the runtime. - public static SlashCommandKind Builtin { get; } = new("builtin"); - - /// Command backed by a skill. - public static SlashCommandKind Skill { get; } = new("skill"); + /// Interactive Copilot CLI exposing a UI server (legacy/normal CLI process). + public static AgentRegistryLiveTargetEntryKind UiServer { get; } = new("ui-server"); - /// Command registered by an SDK client or extension. - public static SlashCommandKind Client { get; } = new("client"); + /// 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 ==(SlashCommandKind left, SlashCommandKind 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 !=(SlashCommandKind left, SlashCommandKind 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 SlashCommandKind other && Equals(other); + public override bool Equals(object? obj) => obj is AgentRegistryLiveTargetEntryKind other && Equals(other); /// - public bool Equals(SlashCommandKind 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); @@ -16192,62 +22852,62 @@ 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 AgentRegistryLiveTargetEntryKind 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, AgentRegistryLiveTargetEntryKind value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SlashCommandKind)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentRegistryLiveTargetEntryKind)); } } } -/// Path conventions used by this filesystem. +/// 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 SessionFsSetProviderConventions : 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 SessionFsSetProviderConventions(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; - /// Paths use Windows path conventions. - public static SessionFsSetProviderConventions Windows { get; } = new("windows"); + /// Last turn ended cleanly (model returned a final assistant message). + public static AgentRegistryLiveTargetEntryLastTerminalEvent TurnEnd { get; } = new("turn_end"); - /// Paths use POSIX path conventions. - public static SessionFsSetProviderConventions Posix { get; } = new("posix"); + /// 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 ==(SessionFsSetProviderConventions left, SessionFsSetProviderConventions 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 !=(SessionFsSetProviderConventions left, SessionFsSetProviderConventions 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 SessionFsSetProviderConventions other && Equals(other); + public override bool Equals(object? obj) => obj is AgentRegistryLiveTargetEntryLastTerminalEvent other && Equals(other); /// - public bool Equals(SessionFsSetProviderConventions 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); @@ -16255,62 +22915,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 AgentRegistryLiveTargetEntryLastTerminalEvent 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, AgentRegistryLiveTargetEntryLastTerminalEvent value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionFsSetProviderConventions)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentRegistryLiveTargetEntryLastTerminalEvent)); } } } -/// Repository host type. +/// Coarse lifecycle status of the foreground session. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct SessionContextHostType : 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 SessionContextHostType(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; - /// Session repository is hosted on GitHub. - public static SessionContextHostType GitHub { get; } = new("github"); + /// Session is actively processing a turn. + public static AgentRegistryLiveTargetEntryStatus Working { get; } = new("working"); - /// Session repository is hosted on Azure DevOps. - public static SessionContextHostType Ado { get; } = new("ado"); + /// Session is idle, waiting for input. + public static AgentRegistryLiveTargetEntryStatus Waiting { get; } = new("waiting"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SessionContextHostType left, SessionContextHostType right) => left.Equals(right); + /// Last turn completed successfully. + public static AgentRegistryLiveTargetEntryStatus Done { get; } = new("done"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SessionContextHostType left, SessionContextHostType right) => !(left == right); + /// 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 ==(AgentRegistryLiveTargetEntryStatus left, AgentRegistryLiveTargetEntryStatus right) => left.Equals(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 SessionContextHostType other && Equals(other); + public override bool Equals(object? obj) => obj is AgentRegistryLiveTargetEntryStatus other && Equals(other); /// - public bool Equals(SessionContextHostType 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); @@ -16318,62 +22984,65 @@ 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 AgentRegistryLiveTargetEntryStatus 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, AgentRegistryLiveTargetEntryStatus value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionContextHostType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentRegistryLiveTargetEntryStatus)); } } } -/// Whether the remote task originated from CCA or CLI `--remote`. +/// Categorized reason for log-open failure. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct RemoteSessionMetadataTaskType : 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 RemoteSessionMetadataTaskType(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; - /// GitHub Copilot coding agent task. - public static RemoteSessionMetadataTaskType Cca { get; } = new("cca"); + /// Filesystem permission denied opening the log file. + public static AgentRegistryLogCaptureOpenErrorReason Permission { get; } = new("permission"); - /// CLI remote task. - public static RemoteSessionMetadataTaskType Cli { get; } = new("cli"); + /// 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 ==(RemoteSessionMetadataTaskType left, RemoteSessionMetadataTaskType 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 !=(RemoteSessionMetadataTaskType left, RemoteSessionMetadataTaskType 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 RemoteSessionMetadataTaskType other && Equals(other); + public override bool Equals(object? obj) => obj is AgentRegistryLogCaptureOpenErrorReason other && Equals(other); /// - public bool Equals(RemoteSessionMetadataTaskType 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); @@ -16381,62 +23050,71 @@ 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 AgentRegistryLogCaptureOpenErrorReason 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, AgentRegistryLogCaptureOpenErrorReason value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(RemoteSessionMetadataTaskType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentRegistryLogCaptureOpenErrorReason)); } } } -/// Step status. +/// 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 SessionsOpenProgressStatus : 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 SessionsOpenProgressStatus(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; - /// The step has started and has not yet finished. - public static SessionsOpenProgressStatus InProgress { get; } = new("in-progress"); + /// The cwd parameter. + public static AgentRegistrySpawnValidationErrorField Cwd { get; } = new("cwd"); - /// The step has completed successfully. - public static SessionsOpenProgressStatus Complete { get; } = new("complete"); + /// The session name parameter. + public static AgentRegistrySpawnValidationErrorField Name { get; } = new("name"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SessionsOpenProgressStatus left, SessionsOpenProgressStatus right) => left.Equals(right); + /// The agentName parameter. + public static AgentRegistrySpawnValidationErrorField AgentName { get; } = new("agentName"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SessionsOpenProgressStatus left, SessionsOpenProgressStatus right) => !(left == right); + /// The model parameter. + public static AgentRegistrySpawnValidationErrorField Model { get; } = new("model"); + + /// The permissionMode parameter. + public static AgentRegistrySpawnValidationErrorField PermissionMode { get; } = new("permissionMode"); + + /// 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 SessionsOpenProgressStatus other && Equals(other); + public override bool Equals(object? obj) => obj is AgentRegistrySpawnValidationErrorField other && Equals(other); /// - public bool Equals(SessionsOpenProgressStatus 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); @@ -16444,74 +23122,74 @@ 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 AgentRegistrySpawnValidationErrorField 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, AgentRegistrySpawnValidationErrorField value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionsOpenProgressStatus)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentRegistrySpawnValidationErrorField)); } } } -/// Handoff step. +/// 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 SessionsOpenProgressStep : 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 SessionsOpenProgressStep(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; - /// Loading the source session's events from the remote service. - public static SessionsOpenProgressStep LoadSession { get; } = new("load-session"); + /// Provided cwd does not exist on disk. + public static AgentRegistrySpawnValidationErrorReason CwdNotFound { get; } = new("cwd-not-found"); - /// Validating that the local repository matches the remote session's repository. - public static SessionsOpenProgressStep ValidateRepo { get; } = new("validate-repo"); + /// Provided cwd exists but is not a directory. + public static AgentRegistrySpawnValidationErrorReason CwdNotDirectory { get; } = new("cwd-not-directory"); - /// Checking the local working tree for uncommitted changes that would block the handoff. - public static SessionsOpenProgressStep CheckChanges { get; } = new("check-changes"); + /// Session name failed validateSessionName. + public static AgentRegistrySpawnValidationErrorReason InvalidName { get; } = new("invalid-name"); - /// Checking out the branch associated with the remote session in the local working tree. - public static SessionsOpenProgressStep CheckoutBranch { get; } = new("checkout-branch"); + /// Requested agent name was not found in builtin or custom agents. + public static AgentRegistrySpawnValidationErrorReason UnknownAgent { get; } = new("unknown-agent"); - /// Creating the new local session and seeding it with the source session's events. - public static SessionsOpenProgressStep CreateSession { get; } = new("create-session"); + /// Requested model is not available to this session. + public static AgentRegistrySpawnValidationErrorReason UnknownModel { get; } = new("unknown-model"); - /// Persisting the newly-created local session to disk. - public static SessionsOpenProgressStep SaveSession { get; } = new("save-session"); + /// 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 ==(SessionsOpenProgressStep left, SessionsOpenProgressStep 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 !=(SessionsOpenProgressStep left, SessionsOpenProgressStep 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 SessionsOpenProgressStep other && Equals(other); + public override bool Equals(object? obj) => obj is AgentRegistrySpawnValidationErrorReason other && Equals(other); /// - public bool Equals(SessionsOpenProgressStep 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); @@ -16519,71 +23197,62 @@ 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 AgentRegistrySpawnValidationErrorReason 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, AgentRegistrySpawnValidationErrorReason value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionsOpenProgressStep)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentRegistrySpawnValidationErrorReason)); } } } -/// Outcome of the open request. +/// 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 SessionsOpenStatus : 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 SessionsOpenStatus(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; - /// 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"); - - /// Connected to an existing remote session. - public static SessionsOpenStatus Connected { get; } = new("connected"); + /// Standard permission posture (prompts for each request). + public static AgentRegistrySpawnPermissionMode Default { get; } = new("default"); - /// Remote session was handed off to a new local session. - public static SessionsOpenStatus HandedOff { get; } = new("handed_off"); + /// 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 ==(SessionsOpenStatus left, SessionsOpenStatus 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 !=(SessionsOpenStatus left, SessionsOpenStatus 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 SessionsOpenStatus other && Equals(other); + public override bool Equals(object? obj) => obj is AgentRegistrySpawnPermissionMode other && Equals(other); /// - public bool Equals(SessionsOpenStatus 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); @@ -16591,62 +23260,68 @@ 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 AgentRegistrySpawnPermissionMode 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, AgentRegistrySpawnPermissionMode value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionsOpenStatus)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentRegistrySpawnPermissionMode)); } } } -/// Neutral SDK discriminator for the connected remote session kind. +/// 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 ConnectedRemoteSessionMetadataKind : 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 ConnectedRemoteSessionMetadataKind(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; - /// Remote CLI session. - public static ConnectedRemoteSessionMetadataKind RemoteSession { get; } = new("remote-session"); + /// The agent is responding interactively to the user. + public static SendAgentMode Interactive { get; } = new("interactive"); - /// GitHub Copilot coding agent session. - public static ConnectedRemoteSessionMetadataKind CodingAgent { get; } = new("coding-agent"); + /// The agent is preparing a plan before making changes. + public static SendAgentMode Plan { get; } = new("plan"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(ConnectedRemoteSessionMetadataKind left, ConnectedRemoteSessionMetadataKind right) => left.Equals(right); + /// The agent is working autonomously toward task completion. + public static SendAgentMode Autopilot { get; } = new("autopilot"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(ConnectedRemoteSessionMetadataKind left, ConnectedRemoteSessionMetadataKind right) => !(left == right); + /// 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 ==(SendAgentMode left, SendAgentMode right) => left.Equals(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 ConnectedRemoteSessionMetadataKind other && Equals(other); + public override bool Equals(object? obj) => obj is SendAgentMode other && Equals(other); /// - public bool Equals(ConnectedRemoteSessionMetadataKind 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); @@ -16654,65 +23329,62 @@ 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 SendAgentMode 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, SendAgentMode value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ConnectedRemoteSessionMetadataKind)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SendAgentMode)); } } } -/// Which session sources to include. Defaults to `local` for backward compatibility. +/// 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 SessionSource : 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 SessionSource(string value) + public SendMode(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . - public string Value => _value ?? string.Empty; - - /// Return only local sessions. - public static SessionSource Local { get; } = new("local"); + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Return only remote sessions. - public static SessionSource Remote { get; } = new("remote"); + /// Append the message to the normal session queue. + public static SendMode Enqueue { get; } = new("enqueue"); - /// Return both local and remote sessions. - public static SessionSource All { get; } = new("all"); + /// Interject the message during the in-progress turn. + public static SendMode Immediate { get; } = new("immediate"); - /// 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 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 !=(SessionSource left, SessionSource right) => !(left == 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 SessionSource other && Equals(other); + public override bool Equals(object? obj) => obj is SendMode other && Equals(other); /// - public bool Equals(SessionSource 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); @@ -16720,71 +23392,65 @@ 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 SendMode 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, SendMode value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionSource)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SendMode)); } } } -/// Kind of attention required when status === "attention". Meaningful only when status === "attention". +/// 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 AgentRegistryLiveTargetEntryAttentionKind : 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 AgentRegistryLiveTargetEntryAttentionKind(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; - /// Session is blocked on an unrecoverable error. - public static AgentRegistryLiveTargetEntryAttentionKind Error { get; } = new("error"); - - /// Session is waiting for a tool-permission decision. - public static AgentRegistryLiveTargetEntryAttentionKind Permission { get; } = new("permission"); - - /// Session is waiting for the user to approve or reject a plan. - public static AgentRegistryLiveTargetEntryAttentionKind ExitPlan { get; } = new("exit_plan"); + /// Informational message. + public static SessionLogLevel Info { get; } = new("info"); - /// Session is waiting on an elicitation prompt. - public static AgentRegistryLiveTargetEntryAttentionKind Elicitation { get; } = new("elicitation"); + /// Warning message that may require attention. + public static SessionLogLevel Warning { get; } = new("warning"); - /// Session is waiting for free-form user input. - public static AgentRegistryLiveTargetEntryAttentionKind UserInput { get; } = new("user_input"); + /// 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 ==(AgentRegistryLiveTargetEntryAttentionKind left, AgentRegistryLiveTargetEntryAttentionKind 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 !=(AgentRegistryLiveTargetEntryAttentionKind left, AgentRegistryLiveTargetEntryAttentionKind 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 AgentRegistryLiveTargetEntryAttentionKind other && Equals(other); + public override bool Equals(object? obj) => obj is SessionLogLevel other && Equals(other); /// - public bool Equals(AgentRegistryLiveTargetEntryAttentionKind 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); @@ -16792,62 +23458,80 @@ 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 SessionLogLevel 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, SessionLogLevel value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentRegistryLiveTargetEntryAttentionKind)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionLogLevel)); } } } -/// Process kind tag for the registry entry. +/// Authentication type. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct AgentRegistryLiveTargetEntryKind : 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 AgentRegistryLiveTargetEntryKind(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; - /// Interactive Copilot CLI exposing a UI server (legacy/normal CLI process). - public static AgentRegistryLiveTargetEntryKind UiServer { get; } = new("ui-server"); + /// Authentication provided by a GitHub App HMAC credential. + public static AuthInfoType Hmac { get; } = new("hmac"); - /// Headless `--server --managed-server` child spawned by a controller. - public static AgentRegistryLiveTargetEntryKind ManagedServer { get; } = new("managed-server"); + /// 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 ==(AgentRegistryLiveTargetEntryKind left, AgentRegistryLiveTargetEntryKind 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 !=(AgentRegistryLiveTargetEntryKind left, AgentRegistryLiveTargetEntryKind 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 an SDK GitHub token callback. + public static AuthInfoType TokenProvider { get; } = new("token-provider"); + + /// 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 AgentRegistryLiveTargetEntryKind other && Equals(other); + public override bool Equals(object? obj) => obj is AuthInfoType other && Equals(other); /// - public bool Equals(AgentRegistryLiveTargetEntryKind 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); @@ -16855,62 +23539,68 @@ 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 AuthInfoType 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, AuthInfoType value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentRegistryLiveTargetEntryKind)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AuthInfoType)); } } } -/// How the most recent turn ended (clean vs aborted). Lets the renderer distinguish done from done_cancelled. +/// Source category for a collected debug bundle entry. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct AgentRegistryLiveTargetEntryLastTerminalEvent : 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 AgentRegistryLiveTargetEntryLastTerminalEvent(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; - /// Last turn ended cleanly (model returned a final assistant message). - public static AgentRegistryLiveTargetEntryLastTerminalEvent TurnEnd { get; } = new("turn_end"); + /// Session event log. + public static DebugCollectLogsSource Events { get; } = new("events"); - /// Last turn was aborted (e.g. user interrupted). - public static AgentRegistryLiveTargetEntryLastTerminalEvent Abort { get; } = new("abort"); + /// 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 ==(AgentRegistryLiveTargetEntryLastTerminalEvent left, AgentRegistryLiveTargetEntryLastTerminalEvent 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 !=(AgentRegistryLiveTargetEntryLastTerminalEvent left, AgentRegistryLiveTargetEntryLastTerminalEvent 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 AgentRegistryLiveTargetEntryLastTerminalEvent other && Equals(other); + public override bool Equals(object? obj) => obj is DebugCollectLogsSource other && Equals(other); /// - public bool Equals(AgentRegistryLiveTargetEntryLastTerminalEvent 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); @@ -16918,68 +23608,62 @@ 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 DebugCollectLogsSource 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, DebugCollectLogsSource value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentRegistryLiveTargetEntryLastTerminalEvent)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(DebugCollectLogsSource)); } } } -/// Coarse lifecycle status of the foreground session. +/// Destination kind that was written. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct AgentRegistryLiveTargetEntryStatus : 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 AgentRegistryLiveTargetEntryStatus(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; - /// Session is actively processing a turn. - public static AgentRegistryLiveTargetEntryStatus Working { get; } = new("working"); - - /// Session is idle, waiting for input. - public static AgentRegistryLiveTargetEntryStatus Waiting { get; } = new("waiting"); - - /// Last turn completed successfully. - public static AgentRegistryLiveTargetEntryStatus Done { get; } = new("done"); + /// A .tgz archive was written. + public static DebugCollectLogsResultKind Archive { get; } = new("archive"); - /// Session needs user attention (see attentionKind for the specific reason). - public static AgentRegistryLiveTargetEntryStatus Attention { get; } = new("attention"); + /// 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 ==(AgentRegistryLiveTargetEntryStatus left, AgentRegistryLiveTargetEntryStatus 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 !=(AgentRegistryLiveTargetEntryStatus left, AgentRegistryLiveTargetEntryStatus 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 AgentRegistryLiveTargetEntryStatus other && Equals(other); + public override bool Equals(object? obj) => obj is DebugCollectLogsResultKind other && Equals(other); /// - public bool Equals(AgentRegistryLiveTargetEntryStatus 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); @@ -16987,65 +23671,62 @@ 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 DebugCollectLogsResultKind 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, DebugCollectLogsResultKind value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentRegistryLiveTargetEntryStatus)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(DebugCollectLogsResultKind)); } } } -/// Categorized reason for log-open failure. +/// Kind of caller-provided debug log entry. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct AgentRegistryLogCaptureOpenErrorReason : 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 AgentRegistryLogCaptureOpenErrorReason(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; - /// Filesystem permission denied opening the log file. - public static AgentRegistryLogCaptureOpenErrorReason Permission { get; } = new("permission"); - - /// No space left on device. - public static AgentRegistryLogCaptureOpenErrorReason DiskFull { get; } = new("disk_full"); - - /// Other / uncategorized open failure. - public static AgentRegistryLogCaptureOpenErrorReason Other { get; } = new("other"); + /// Include a single server-local file. + public static DebugCollectLogsEntryKind File { get; } = new("file"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(AgentRegistryLogCaptureOpenErrorReason left, AgentRegistryLogCaptureOpenErrorReason right) => left.Equals(right); + /// Include files from a server-local directory recursively. + public static DebugCollectLogsEntryKind Directory { get; } = new("directory"); - /// 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 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 !=(DebugCollectLogsEntryKind left, DebugCollectLogsEntryKind right) => !(left == right); /// - public override bool Equals(object? obj) => obj is AgentRegistryLogCaptureOpenErrorReason other && Equals(other); + public override bool Equals(object? obj) => obj is DebugCollectLogsEntryKind other && Equals(other); /// - public bool Equals(AgentRegistryLogCaptureOpenErrorReason 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); @@ -17053,71 +23734,62 @@ 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 DebugCollectLogsEntryKind 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, DebugCollectLogsEntryKind value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentRegistryLogCaptureOpenErrorReason)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(DebugCollectLogsEntryKind)); } } } -/// Which parameter field was invalid. Omitted when the rejection is not field-specific. +/// How a collected debug entry should be redacted before being staged. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct AgentRegistrySpawnValidationErrorField : 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 AgentRegistrySpawnValidationErrorField(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; - /// 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 model parameter. - public static AgentRegistrySpawnValidationErrorField Model { get; } = new("model"); + /// Redact the file as plain UTF-8 log text. + public static DebugCollectLogsRedaction PlainText { get; } = new("plain-text"); - /// The permissionMode parameter. - public static AgentRegistrySpawnValidationErrorField PermissionMode { get; } = new("permissionMode"); + /// 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 ==(AgentRegistrySpawnValidationErrorField left, AgentRegistrySpawnValidationErrorField 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 !=(AgentRegistrySpawnValidationErrorField left, AgentRegistrySpawnValidationErrorField 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 AgentRegistrySpawnValidationErrorField other && Equals(other); + public override bool Equals(object? obj) => obj is DebugCollectLogsRedaction other && Equals(other); /// - public bool Equals(AgentRegistrySpawnValidationErrorField 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); @@ -17125,74 +23797,65 @@ 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 DebugCollectLogsRedaction 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, DebugCollectLogsRedaction value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentRegistrySpawnValidationErrorField)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(DebugCollectLogsRedaction)); } } } -/// Categorized reason for the rejection. Low-cardinality enum so telemetry can aggregate by reason without leaking raw paths or agent/model names. +/// Cumulative resource ceiling that stopped a factory run. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct AgentRegistrySpawnValidationErrorReason : 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 AgentRegistrySpawnValidationErrorReason(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; - /// 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"); - - /// Requested agent name was not found in builtin or custom agents. - public static AgentRegistrySpawnValidationErrorReason UnknownAgent { get; } = new("unknown-agent"); + /// The run admitted the approved maximum total number of subagents. + public static FactoryRunFailureKind MaxTotalSubagents { get; } = new("maxTotalSubagents"); - /// Requested model is not available to this session. - public static AgentRegistrySpawnValidationErrorReason UnknownModel { get; } = new("unknown-model"); + /// The run reached the approved accumulated active-execution time in seconds. + public static FactoryRunFailureKind TimeoutSeconds { get; } = new("timeoutSeconds"); - /// 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 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 ==(AgentRegistrySpawnValidationErrorReason left, AgentRegistrySpawnValidationErrorReason 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 !=(AgentRegistrySpawnValidationErrorReason left, AgentRegistrySpawnValidationErrorReason 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 AgentRegistrySpawnValidationErrorReason other && Equals(other); + public override bool Equals(object? obj) => obj is FactoryRunFailureKind other && Equals(other); /// - public bool Equals(AgentRegistrySpawnValidationErrorReason 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); @@ -17200,62 +23863,89 @@ 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 FactoryRunFailureKind 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, FactoryRunFailureKind value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentRegistrySpawnValidationErrorReason)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FactoryRunFailureKind)); } } } -/// Permission posture for the new session. 'yolo' requires the controller-local session to currently be in allow-all mode. +/// Execution-critical factory storage operation. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct AgentRegistrySpawnPermissionMode : 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 AgentRegistrySpawnPermissionMode(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; - /// Standard permission posture (prompts for each request). - public static AgentRegistrySpawnPermissionMode Default { get; } = new("default"); + /// Creating the durable run and declared phases. + public static FactoryDurableOperation CreateRun { get; } = new("createRun"); - /// Full allow-all (requires the controller-local session to currently be in allow-all mode). - public static AgentRegistrySpawnPermissionMode Yolo { get; } = new("yolo"); + /// 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 ==(AgentRegistrySpawnPermissionMode left, AgentRegistrySpawnPermissionMode 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 !=(AgentRegistrySpawnPermissionMode left, AgentRegistrySpawnPermissionMode 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"); + + /// Renewing the durable owner lease that proves this process still owns the run. + public static FactoryDurableOperation RefreshLease { get; } = new("refreshLease"); + + /// 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 AgentRegistrySpawnPermissionMode other && Equals(other); + public override bool Equals(object? obj) => obj is FactoryDurableOperation other && Equals(other); /// - public bool Equals(AgentRegistrySpawnPermissionMode 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); @@ -17263,68 +23953,74 @@ 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 FactoryDurableOperation 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, FactoryDurableOperation value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentRegistrySpawnPermissionMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FactoryDurableOperation)); } } } -/// The UI mode the agent was in when this message was sent. Defaults to the session's current mode. +/// Current or terminal state of a factory run. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct SendAgentMode : 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 SendAgentMode(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; - /// The agent is responding interactively to the user. - public static SendAgentMode Interactive { get; } = new("interactive"); + /// The run was minted and is awaiting approval. + public static FactoryRunStatus Pending { get; } = new("pending"); - /// The agent is preparing a plan before making changes. - public static SendAgentMode Plan { get; } = new("plan"); + /// The run is executing. + public static FactoryRunStatus Running { get; } = new("running"); - /// The agent is working autonomously toward task completion. - public static SendAgentMode Autopilot { get; } = new("autopilot"); + /// The run completed successfully. + public static FactoryRunStatus Completed { get; } = new("completed"); - /// The agent is in shell-focused UI mode. - public static SendAgentMode Shell { get; } = new("shell"); + /// The run was interrupted while resource budget remained. + public static FactoryRunStatus Halted { get; } = new("halted"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SendAgentMode left, SendAgentMode right) => left.Equals(right); + /// The run was cancelled before completion. + public static FactoryRunStatus Cancelled { get; } = new("cancelled"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SendAgentMode left, SendAgentMode right) => !(left == right); + /// 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 SendAgentMode other && Equals(other); + public override bool Equals(object? obj) => obj is FactoryRunStatus other && Equals(other); /// - public bool Equals(SendAgentMode 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); @@ -17332,62 +24028,68 @@ 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 FactoryRunStatus 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, FactoryRunStatus value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SendAgentMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FactoryRunStatus)); } } } -/// How to deliver the message. `enqueue` (default) appends to the message queue. `immediate` interjects during an in-progress turn. +/// Derived lifecycle state of a factory phase. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct SendMode : 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 SendMode(string value) + public FactoryPhaseStatus(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 phase has not been entered yet. + public static FactoryPhaseStatus Pending { get; } = new("pending"); - /// Interject the message during the in-progress turn. - public static SendMode Immediate { get; } = new("immediate"); + /// The phase is currently entered and accumulating active time. + public static FactoryPhaseStatus Active { get; } = new("active"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SendMode left, SendMode right) => left.Equals(right); + /// The phase was entered and has since been closed. + public static FactoryPhaseStatus Completed { get; } = new("completed"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SendMode left, SendMode right) => !(left == right); + /// 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 ==(FactoryPhaseStatus left, FactoryPhaseStatus right) => left.Equals(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 SendMode other && Equals(other); + public override bool Equals(object? obj) => obj is FactoryPhaseStatus other && Equals(other); /// - public bool Equals(SendMode 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); @@ -17395,65 +24097,62 @@ 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 FactoryPhaseStatus 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, FactoryPhaseStatus value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SendMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FactoryPhaseStatus)); } } } -/// Log severity level. Determines how the message is displayed in the timeline. Defaults to "info". +/// Kind of factory progress line. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct SessionLogLevel : 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 SessionLogLevel(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; - /// Informational message. - public static SessionLogLevel Info { get; } = new("info"); - - /// Warning message that may require attention. - public static SessionLogLevel Warning { get; } = new("warning"); + /// A narrator log line. + public static FactoryLogLineKind Log { get; } = new("log"); - /// Error message describing a failure. - public static SessionLogLevel Error { get; } = new("error"); + /// 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 ==(SessionLogLevel left, SessionLogLevel 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 !=(SessionLogLevel left, SessionLogLevel 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 SessionLogLevel other && Equals(other); + public override bool Equals(object? obj) => obj is FactoryLogLineKind other && Equals(other); /// - public bool Equals(SessionLogLevel 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); @@ -17461,77 +24160,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 FactoryLogLineKind 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, FactoryLogLineKind value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionLogLevel)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FactoryLogLineKind)); } } } -/// Authentication type. +/// Allowed values for the `WorkspacesWorkspaceDetailsHostType` enumeration. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct AuthInfoType : 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 AuthInfoType(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; - /// 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"); - - /// 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"); + /// Workspace repository is hosted on GitHub. + public static WorkspacesWorkspaceDetailsHostType GitHub { get; } = new("github"); - /// Authentication from a Copilot API token. - public static AuthInfoType CopilotApiToken { get; } = new("copilot-api-token"); + /// 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 ==(AuthInfoType left, AuthInfoType 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 !=(AuthInfoType left, AuthInfoType 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 AuthInfoType other && Equals(other); + public override bool Equals(object? obj) => obj is WorkspacesWorkspaceDetailsHostType other && Equals(other); /// - public bool Equals(AuthInfoType 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); @@ -17539,68 +24223,68 @@ 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 WorkspacesWorkspaceDetailsHostType 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, WorkspacesWorkspaceDetailsHostType value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AuthInfoType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(WorkspacesWorkspaceDetailsHostType)); } } } -/// Source category for a collected debug bundle entry. +/// Type of change represented by this file diff. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct DebugCollectLogsSource : 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 DebugCollectLogsSource(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 event log. - public static DebugCollectLogsSource Events { get; } = new("events"); + /// The file was added. + public static WorkspaceDiffFileChangeType Added { get; } = new("added"); - /// Process log for the session. - public static DebugCollectLogsSource ProcessLog { get; } = new("process-log"); + /// The file was modified. + public static WorkspaceDiffFileChangeType Modified { get; } = new("modified"); - /// Interactive shell log for the session. - public static DebugCollectLogsSource ShellLog { get; } = new("shell-log"); + /// The file was deleted. + public static WorkspaceDiffFileChangeType Deleted { get; } = new("deleted"); - /// Caller-provided diagnostic entry. - public static DebugCollectLogsSource Additional { get; } = new("additional"); + /// The file was renamed. + public static WorkspaceDiffFileChangeType Renamed { get; } = new("renamed"); - /// 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 ==(WorkspaceDiffFileChangeType left, WorkspaceDiffFileChangeType 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 !=(WorkspaceDiffFileChangeType left, WorkspaceDiffFileChangeType right) => !(left == right); /// - public override bool Equals(object? obj) => obj is DebugCollectLogsSource other && Equals(other); + public override bool Equals(object? obj) => obj is WorkspaceDiffFileChangeType other && Equals(other); /// - public bool Equals(DebugCollectLogsSource 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); @@ -17608,62 +24292,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 WorkspaceDiffFileChangeType 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, WorkspaceDiffFileChangeType value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(DebugCollectLogsSource)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(WorkspaceDiffFileChangeType)); } } } -/// Destination kind that was written. +/// Diff mode requested by the client. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct DebugCollectLogsResultKind : 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 DebugCollectLogsResultKind(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; - /// A .tgz archive was written. - public static DebugCollectLogsResultKind Archive { get; } = new("archive"); + /// Return staged, unstaged, and untracked working tree changes. + public static WorkspaceDiffMode Unstaged { get; } = new("unstaged"); - /// A directory containing redacted files was written. - public static DebugCollectLogsResultKind Directory { get; } = new("directory"); + /// Return changes compared with the default branch. + public static WorkspaceDiffMode Branch { get; } = new("branch"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(DebugCollectLogsResultKind left, DebugCollectLogsResultKind right) => left.Equals(right); + /// 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 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 ==(WorkspaceDiffMode left, WorkspaceDiffMode right) => left.Equals(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 DebugCollectLogsResultKind other && Equals(other); + public override bool Equals(object? obj) => obj is WorkspaceDiffMode other && Equals(other); /// - public bool Equals(DebugCollectLogsResultKind 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); @@ -17671,62 +24358,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 WorkspaceDiffMode 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, WorkspaceDiffMode value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(DebugCollectLogsResultKind)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(WorkspaceDiffMode)); } } } -/// Kind of caller-provided debug log entry. +/// 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 DebugCollectLogsEntryKind : 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 DebugCollectLogsEntryKind(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; - /// Include a single server-local file. - public static DebugCollectLogsEntryKind File { get; } = new("file"); + /// The session did not opt into file-change tracking before its first turn. + public static HistoryRewindUnavailableReason FileChangeTrackingDisabled { get; } = new("file-change-tracking-disabled"); - /// Include files from a server-local directory recursively. - public static DebugCollectLogsEntryKind Directory { get; } = new("directory"); + /// 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"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(DebugCollectLogsEntryKind left, DebugCollectLogsEntryKind right) => left.Equals(right); + /// Remote-backed rewind routing is not supported. + public static HistoryRewindUnavailableReason UnsupportedRemoteSession { get; } = new("unsupported-remote-session"); - /// 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 ==(HistoryRewindUnavailableReason left, HistoryRewindUnavailableReason right) => left.Equals(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 DebugCollectLogsEntryKind other && Equals(other); + public override bool Equals(object? obj) => obj is HistoryRewindUnavailableReason other && Equals(other); /// - public bool Equals(DebugCollectLogsEntryKind 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); @@ -17734,62 +24424,62 @@ 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 HistoryRewindUnavailableReason 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, HistoryRewindUnavailableReason value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(DebugCollectLogsEntryKind)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(HistoryRewindUnavailableReason)); } } } -/// How a collected debug entry should be redacted before being staged. +/// Whether task execution is synchronously awaited or managed in the background. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct DebugCollectLogsRedaction : IEquatable +public readonly struct TaskExecutionMode : 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 TaskExecutionMode(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"); + /// The task was started with synchronous waiting. + public static TaskExecutionMode Sync { get; } = new("sync"); - /// 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"); + /// 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 ==(DebugCollectLogsRedaction left, DebugCollectLogsRedaction 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 !=(DebugCollectLogsRedaction left, DebugCollectLogsRedaction 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 DebugCollectLogsRedaction other && Equals(other); + public override bool Equals(object? obj) => obj is TaskExecutionMode other && Equals(other); /// - public bool Equals(DebugCollectLogsRedaction 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); @@ -17797,65 +24487,71 @@ 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 TaskExecutionMode 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, TaskExecutionMode value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(DebugCollectLogsRedaction)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskExecutionMode)); } } } -/// Cumulative resource ceiling that stopped a factory run. +/// Current lifecycle status of the task. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct FactoryRunFailureKind : 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 FactoryRunFailureKind(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; - /// The run admitted the approved maximum total number of subagents. - public static FactoryRunFailureKind MaxTotalSubagents { get; } = new("maxTotalSubagents"); + /// The task is actively executing. + public static TaskStatus Running { get; } = new("running"); - /// The run reached the approved accumulated active-execution time in seconds. - public static FactoryRunFailureKind TimeoutSeconds { get; } = new("timeoutSeconds"); + /// The task is waiting for additional input. + public static TaskStatus Idle { get; } = new("idle"); - /// 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"); + /// The task finished successfully. + public static TaskStatus Completed { get; } = new("completed"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(FactoryRunFailureKind left, FactoryRunFailureKind right) => left.Equals(right); + /// The task finished with an error. + public static TaskStatus Failed { get; } = new("failed"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(FactoryRunFailureKind left, FactoryRunFailureKind right) => !(left == right); + /// 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 FactoryRunFailureKind other && Equals(other); + public override bool Equals(object? obj) => obj is TaskStatus other && Equals(other); /// - public bool Equals(FactoryRunFailureKind 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); @@ -17863,86 +24559,62 @@ 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 TaskStatus 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, TaskStatus value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FactoryRunFailureKind)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskStatus)); } } } -/// Execution-critical factory storage operation. +/// 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 FactoryDurableOperation : 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 FactoryDurableOperation(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; - /// Creating the durable run and declared phases. - public static FactoryDurableOperation CreateRun { get; } = new("createRun"); - - /// Persisting the transition to running. - public static FactoryDurableOperation MarkRunStarted { get; } = new("markRunStarted"); - - /// Persisting the terminal run envelope. - public static FactoryDurableOperation FinishRun { get; } = new("finishRun"); - - /// 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"); + /// The shell runs in a managed PTY session. + public static TaskShellInfoAttachmentMode Attached { get; } = new("attached"); - /// Persisting a journal entry before reporting success. - public static FactoryDurableOperation JournalPut { get; } = new("journalPut"); + /// 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 ==(FactoryDurableOperation left, FactoryDurableOperation 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 !=(FactoryDurableOperation left, FactoryDurableOperation 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 FactoryDurableOperation other && Equals(other); + public override bool Equals(object? obj) => obj is TaskShellInfoAttachmentMode other && Equals(other); /// - public bool Equals(FactoryDurableOperation 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); @@ -17950,74 +24622,62 @@ public FactoryDurableOperation(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 FactoryDurableOperation 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, FactoryDurableOperation value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, TaskShellInfoAttachmentMode value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FactoryDurableOperation)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskShellInfoAttachmentMode)); } } } -/// Current or terminal state of a factory run. +/// Consumer allowed to call an MCP tool. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct FactoryRunStatus : 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 FactoryRunStatus(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; - /// 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"); - - /// The run was cancelled before completion. - public static FactoryRunStatus Cancelled { get; } = new("cancelled"); + /// The model may call the tool. + public static McpToolUiVisibility Model { get; } = new("model"); - /// The factory body failed or reached a cumulative resource ceiling. - public static FactoryRunStatus Error { get; } = new("error"); + /// 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 ==(FactoryRunStatus left, FactoryRunStatus 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 !=(FactoryRunStatus left, FactoryRunStatus 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 FactoryRunStatus other && Equals(other); + public override bool Equals(object? obj) => obj is McpToolUiVisibility other && Equals(other); /// - public bool Equals(FactoryRunStatus 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); @@ -18025,68 +24685,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 McpToolUiVisibility 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, McpToolUiVisibility value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FactoryRunStatus)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpToolUiVisibility)); } } } -/// Derived lifecycle state of a factory phase. +/// 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 FactoryPhaseStatus : 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 FactoryPhaseStatus(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; - /// The phase has not been entered yet. - public static FactoryPhaseStatus Pending { get; } = new("pending"); - - /// The phase is currently entered and accumulating active time. - public static FactoryPhaseStatus Active { get; } = new("active"); + /// The sampling inference completed and produced a result. + public static McpSamplingExecutionAction Success { get; } = new("success"); - /// The phase was entered and has since been closed. - public static FactoryPhaseStatus Completed { get; } = new("completed"); + /// The sampling inference failed or was rejected. + public static McpSamplingExecutionAction Failure { get; } = new("failure"); - /// 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"); + /// 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 ==(FactoryPhaseStatus left, FactoryPhaseStatus 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 !=(FactoryPhaseStatus left, FactoryPhaseStatus 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 FactoryPhaseStatus other && Equals(other); + public override bool Equals(object? obj) => obj is McpSamplingExecutionAction other && Equals(other); /// - public bool Equals(FactoryPhaseStatus 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); @@ -18094,62 +24751,62 @@ public FactoryPhaseStatus(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 FactoryPhaseStatus 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, FactoryPhaseStatus value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, McpSamplingExecutionAction value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FactoryPhaseStatus)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpSamplingExecutionAction)); } } } -/// Kind of factory progress line. +/// 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 FactoryLogLineKind : 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 FactoryLogLineKind(string value) + public McpSetEnvValueModeDetails(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"); + /// Treat MCP server environment values as literal strings. + public static McpSetEnvValueModeDetails Direct { get; } = new("direct"); - /// A named factory phase marker. - public static FactoryLogLineKind Phase { get; } = new("phase"); + /// 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 ==(FactoryLogLineKind left, FactoryLogLineKind 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 !=(FactoryLogLineKind left, FactoryLogLineKind 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 FactoryLogLineKind other && Equals(other); + public override bool Equals(object? obj) => obj is McpSetEnvValueModeDetails other && Equals(other); /// - public bool Equals(FactoryLogLineKind 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); @@ -18157,62 +24814,62 @@ 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 McpSetEnvValueModeDetails 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, McpSetEnvValueModeDetails value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FactoryLogLineKind)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpSetEnvValueModeDetails)); } } } -/// Allowed values for the `WorkspacesWorkspaceDetailsHostType` enumeration. +/// OAuth grant type override for this login. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct WorkspacesWorkspaceDetailsHostType : 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 WorkspacesWorkspaceDetailsHostType(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; - /// Workspace repository is hosted on GitHub. - public static WorkspacesWorkspaceDetailsHostType GitHub { get; } = new("github"); + /// Interactive browser-based OAuth flow using an authorization code, typically with PKCE. + public static McpOauthLoginGrantType AuthorizationCode { get; } = new("authorization_code"); - /// Workspace repository is hosted on Azure DevOps. - public static WorkspacesWorkspaceDetailsHostType Ado { get; } = new("ado"); + /// 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 ==(WorkspacesWorkspaceDetailsHostType left, WorkspacesWorkspaceDetailsHostType 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 !=(WorkspacesWorkspaceDetailsHostType left, WorkspacesWorkspaceDetailsHostType 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 WorkspacesWorkspaceDetailsHostType other && Equals(other); + public override bool Equals(object? obj) => obj is McpOauthLoginGrantType other && Equals(other); /// - public bool Equals(WorkspacesWorkspaceDetailsHostType 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); @@ -18220,68 +24877,65 @@ 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 McpOauthLoginGrantType 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, McpOauthLoginGrantType value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(WorkspacesWorkspaceDetailsHostType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpOauthLoginGrantType)); } } } -/// Type of change represented by this file diff. +/// Why a passive MCP OAuth probe determined authentication is needed. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct WorkspaceDiffFileChangeType : IEquatable +public readonly struct McpOauthProbeNeedsAuthReason : 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 McpOauthProbeNeedsAuthReason(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"); + /// No token was sent and the server requires authentication. + public static McpOauthProbeNeedsAuthReason Initial { get; } = new("initial"); - /// The file was deleted. - public static WorkspaceDiffFileChangeType Deleted { get; } = new("deleted"); + /// A cached token was sent and rejected. + public static McpOauthProbeNeedsAuthReason Refresh { get; } = new("refresh"); - /// The file was renamed. - public static WorkspaceDiffFileChangeType Renamed { get; } = new("renamed"); + /// The server returned a 403 insufficient_scope challenge, indicating additional scopes or audience are needed. + public static McpOauthProbeNeedsAuthReason Upscope { get; } = new("upscope"); - /// 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 ==(McpOauthProbeNeedsAuthReason left, McpOauthProbeNeedsAuthReason 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 !=(McpOauthProbeNeedsAuthReason left, McpOauthProbeNeedsAuthReason right) => !(left == right); /// - public override bool Equals(object? obj) => obj is WorkspaceDiffFileChangeType other && Equals(other); + public override bool Equals(object? obj) => obj is McpOauthProbeNeedsAuthReason other && Equals(other); /// - public bool Equals(WorkspaceDiffFileChangeType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(McpOauthProbeNeedsAuthReason other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -18289,65 +24943,65 @@ 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 McpOauthProbeNeedsAuthReason 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, McpOauthProbeNeedsAuthReason value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(WorkspaceDiffFileChangeType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpOauthProbeNeedsAuthReason)); } } } -/// Diff mode requested by the client. +/// Allowed values for the `McpAppsSetHostContextDetailsAvailableDisplayMode` enumeration. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct WorkspaceDiffMode : 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 WorkspaceDiffMode(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; - /// Return staged, unstaged, and untracked working tree changes. - public static WorkspaceDiffMode Unstaged { get; } = new("unstaged"); + /// Rendered inline within the host conversation surface. + public static McpAppsSetHostContextDetailsAvailableDisplayMode Inline { get; } = new("inline"); - /// Return changes compared with the default branch. - public static WorkspaceDiffMode Branch { get; } = new("branch"); + /// Rendered as a fullscreen overlay. + public static McpAppsSetHostContextDetailsAvailableDisplayMode Fullscreen { get; } = new("fullscreen"); - /// Return the cumulative diff of files Copilot changed this session (used in non-git workspaces). - public static WorkspaceDiffMode Session { get; } = new("session"); + /// Rendered as a picture-in-picture floating panel. + public static McpAppsSetHostContextDetailsAvailableDisplayMode Pip { get; } = new("pip"); - /// 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 ==(McpAppsSetHostContextDetailsAvailableDisplayMode left, McpAppsSetHostContextDetailsAvailableDisplayMode 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 !=(McpAppsSetHostContextDetailsAvailableDisplayMode left, McpAppsSetHostContextDetailsAvailableDisplayMode right) => !(left == right); /// - public override bool Equals(object? obj) => obj is WorkspaceDiffMode other && Equals(other); + public override bool Equals(object? obj) => obj is McpAppsSetHostContextDetailsAvailableDisplayMode other && Equals(other); /// - public bool Equals(WorkspaceDiffMode 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); @@ -18355,65 +25009,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 McpAppsSetHostContextDetailsAvailableDisplayMode 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, McpAppsSetHostContextDetailsAvailableDisplayMode value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(WorkspaceDiffMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpAppsSetHostContextDetailsAvailableDisplayMode)); } } } -/// Reason a rewind read (rewind points, file-restore preview, or session diff) could not be answered from the session's file-change captures. +/// Current display mode (SEP-1865). [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct HistoryRewindUnavailableReason : 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 HistoryRewindUnavailableReason(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; - /// The session did not opt into file-change tracking before its first turn. - public static HistoryRewindUnavailableReason FileChangeTrackingDisabled { get; } = new("file-change-tracking-disabled"); + /// Rendered inline within the host conversation surface. + public static McpAppsSetHostContextDetailsDisplayMode Inline { get; } = new("inline"); - /// 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"); + /// Rendered as a fullscreen overlay. + public static McpAppsSetHostContextDetailsDisplayMode Fullscreen { get; } = new("fullscreen"); - /// Remote-backed rewind routing is not supported. - public static HistoryRewindUnavailableReason UnsupportedRemoteSession { get; } = new("unsupported-remote-session"); + /// Rendered as a picture-in-picture floating panel. + public static McpAppsSetHostContextDetailsDisplayMode Pip { get; } = new("pip"); - /// 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 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 !=(HistoryRewindUnavailableReason left, HistoryRewindUnavailableReason right) => !(left == 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 HistoryRewindUnavailableReason other && Equals(other); + public override bool Equals(object? obj) => obj is McpAppsSetHostContextDetailsDisplayMode other && Equals(other); /// - public bool Equals(HistoryRewindUnavailableReason 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); @@ -18421,62 +25075,65 @@ public HistoryRewindUnavailableReason(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 HistoryRewindUnavailableReason 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, HistoryRewindUnavailableReason value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, McpAppsSetHostContextDetailsDisplayMode value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(HistoryRewindUnavailableReason)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpAppsSetHostContextDetailsDisplayMode)); } } } -/// Whether task execution is synchronously awaited or managed in the background. +/// Platform type for responsive design. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct TaskExecutionMode : 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 TaskExecutionMode(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; - /// The task was started with synchronous waiting. - public static TaskExecutionMode Sync { get; } = new("sync"); + /// Host runs in a web browser. + public static McpAppsSetHostContextDetailsPlatform Web { get; } = new("web"); - /// The task is managed in the background. - public static TaskExecutionMode Background { get; } = new("background"); + /// 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 ==(TaskExecutionMode left, TaskExecutionMode 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 !=(TaskExecutionMode left, TaskExecutionMode 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 TaskExecutionMode other && Equals(other); + public override bool Equals(object? obj) => obj is McpAppsSetHostContextDetailsPlatform other && Equals(other); /// - public bool Equals(TaskExecutionMode 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); @@ -18484,71 +25141,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 McpAppsSetHostContextDetailsPlatform 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, McpAppsSetHostContextDetailsPlatform value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskExecutionMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpAppsSetHostContextDetailsPlatform)); } } } -/// Current lifecycle status of the task. +/// UI theme preference per SEP-1865. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct TaskStatus : 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 TaskStatus(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 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"); + /// Light UI theme. + public static McpAppsSetHostContextDetailsTheme Light { get; } = new("light"); - /// The task was cancelled before completion. - public static TaskStatus Cancelled { get; } = new("cancelled"); + /// Dark UI theme. + public static McpAppsSetHostContextDetailsTheme Dark { get; } = new("dark"); - /// 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 ==(McpAppsSetHostContextDetailsTheme left, McpAppsSetHostContextDetailsTheme 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 !=(McpAppsSetHostContextDetailsTheme left, McpAppsSetHostContextDetailsTheme right) => !(left == right); /// - public override bool Equals(object? obj) => obj is TaskStatus other && Equals(other); + public override bool Equals(object? obj) => obj is McpAppsSetHostContextDetailsTheme other && Equals(other); /// - public bool Equals(TaskStatus 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); @@ -18556,62 +25204,65 @@ 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 McpAppsSetHostContextDetailsTheme 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, McpAppsSetHostContextDetailsTheme value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskStatus)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpAppsSetHostContextDetailsTheme)); } } } -/// Whether the shell runs inside a managed PTY session or as an independent background process. +/// Allowed values for the `McpAppsHostContextDetailsAvailableDisplayMode` enumeration. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct TaskShellInfoAttachmentMode : 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 TaskShellInfoAttachmentMode(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 shell runs in a managed PTY session. - public static TaskShellInfoAttachmentMode Attached { get; } = new("attached"); + /// Rendered inline within the host conversation surface. + public static McpAppsHostContextDetailsAvailableDisplayMode Inline { get; } = new("inline"); - /// The shell runs as an independent background process. - public static TaskShellInfoAttachmentMode Detached { get; } = new("detached"); + /// Rendered as a fullscreen overlay. + public static McpAppsHostContextDetailsAvailableDisplayMode Fullscreen { get; } = new("fullscreen"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(TaskShellInfoAttachmentMode left, TaskShellInfoAttachmentMode right) => left.Equals(right); + /// Rendered as a picture-in-picture floating panel. + public static McpAppsHostContextDetailsAvailableDisplayMode Pip { get; } = new("pip"); - /// 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 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 !=(McpAppsHostContextDetailsAvailableDisplayMode left, McpAppsHostContextDetailsAvailableDisplayMode right) => !(left == right); /// - public override bool Equals(object? obj) => obj is TaskShellInfoAttachmentMode other && Equals(other); + public override bool Equals(object? obj) => obj is McpAppsHostContextDetailsAvailableDisplayMode other && Equals(other); /// - public bool Equals(TaskShellInfoAttachmentMode 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); @@ -18619,62 +25270,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 McpAppsHostContextDetailsAvailableDisplayMode 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, McpAppsHostContextDetailsAvailableDisplayMode value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskShellInfoAttachmentMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpAppsHostContextDetailsAvailableDisplayMode)); } } } -/// Consumer allowed to call an MCP tool. +/// Current display mode (SEP-1865). [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct McpToolUiVisibility : 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 McpToolUiVisibility(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; - /// The model may call the tool. - public static McpToolUiVisibility Model { get; } = new("model"); + /// Rendered inline within the host conversation surface. + public static McpAppsHostContextDetailsDisplayMode Inline { get; } = new("inline"); - /// An MCP App view may call the tool. - public static McpToolUiVisibility App { get; } = new("app"); + /// 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 ==(McpToolUiVisibility left, McpToolUiVisibility 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 !=(McpToolUiVisibility left, McpToolUiVisibility 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 McpToolUiVisibility other && Equals(other); + public override bool Equals(object? obj) => obj is McpAppsHostContextDetailsDisplayMode other && Equals(other); /// - public bool Equals(McpToolUiVisibility 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); @@ -18682,65 +25336,65 @@ 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 McpAppsHostContextDetailsDisplayMode 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, McpAppsHostContextDetailsDisplayMode value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpToolUiVisibility)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpAppsHostContextDetailsDisplayMode)); } } } -/// 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. +/// Platform type for responsive design. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct McpSamplingExecutionAction : 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 McpSamplingExecutionAction(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; - /// The sampling inference completed and produced a result. - public static McpSamplingExecutionAction Success { get; } = new("success"); + /// Host runs in a web browser. + public static McpAppsHostContextDetailsPlatform Web { get; } = new("web"); - /// The sampling inference failed or was rejected. - public static McpSamplingExecutionAction Failure { get; } = new("failure"); + /// Host runs as a desktop application. + public static McpAppsHostContextDetailsPlatform Desktop { get; } = new("desktop"); - /// The sampling inference was cancelled before completion. - public static McpSamplingExecutionAction Cancelled { get; } = new("cancelled"); + /// Host runs on a mobile device. + public static McpAppsHostContextDetailsPlatform Mobile { get; } = new("mobile"); - /// 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 ==(McpAppsHostContextDetailsPlatform left, McpAppsHostContextDetailsPlatform 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 !=(McpAppsHostContextDetailsPlatform left, McpAppsHostContextDetailsPlatform right) => !(left == right); /// - public override bool Equals(object? obj) => obj is McpSamplingExecutionAction other && Equals(other); + public override bool Equals(object? obj) => obj is McpAppsHostContextDetailsPlatform other && Equals(other); /// - public bool Equals(McpSamplingExecutionAction 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); @@ -18748,62 +25402,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 McpAppsHostContextDetailsPlatform 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, McpAppsHostContextDetailsPlatform value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpSamplingExecutionAction)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpAppsHostContextDetailsPlatform)); } } } -/// 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". +/// UI theme preference per SEP-1865. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct McpSetEnvValueModeDetails : 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 McpSetEnvValueModeDetails(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; - /// Treat MCP server environment values as literal strings. - public static McpSetEnvValueModeDetails Direct { get; } = new("direct"); + /// Light UI theme. + public static McpAppsHostContextDetailsTheme Light { get; } = new("light"); - /// Treat MCP server environment values as host-side references to resolve before launch. - public static McpSetEnvValueModeDetails Indirect { get; } = new("indirect"); + /// Dark UI theme. + public static McpAppsHostContextDetailsTheme Dark { get; } = new("dark"); - /// 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 ==(McpAppsHostContextDetailsTheme left, McpAppsHostContextDetailsTheme 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 !=(McpAppsHostContextDetailsTheme left, McpAppsHostContextDetailsTheme right) => !(left == right); /// - public override bool Equals(object? obj) => obj is McpSetEnvValueModeDetails other && Equals(other); + public override bool Equals(object? obj) => obj is McpAppsHostContextDetailsTheme other && Equals(other); /// - public bool Equals(McpSetEnvValueModeDetails 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); @@ -18811,62 +25465,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 McpAppsHostContextDetailsTheme 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, McpAppsHostContextDetailsTheme value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpSetEnvValueModeDetails)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpAppsHostContextDetailsTheme)); } } } -/// OAuth grant type override for this login. +/// Transport to be used for provider requests. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct McpOauthLoginGrantType : 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 McpOauthLoginGrantType(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; - /// Interactive browser-based OAuth flow using an authorization code, typically with PKCE. - public static McpOauthLoginGrantType AuthorizationCode { get; } = new("authorization_code"); + /// HTTP request/streaming transport. + public static ProviderEndpointTransport Http { get; } = new("http"); - /// Headless OAuth flow where a confidential client authenticates directly with a client secret. - public static McpOauthLoginGrantType ClientCredentials { get; } = new("client_credentials"); + /// WebSocket transport. + public static ProviderEndpointTransport Websockets { get; } = new("websockets"); - /// 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 ==(ProviderEndpointTransport left, ProviderEndpointTransport 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 !=(ProviderEndpointTransport left, ProviderEndpointTransport right) => !(left == right); /// - public override bool Equals(object? obj) => obj is McpOauthLoginGrantType other && Equals(other); + public override bool Equals(object? obj) => obj is ProviderEndpointTransport other && Equals(other); /// - public bool Equals(McpOauthLoginGrantType 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); @@ -18874,65 +25528,65 @@ 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 ProviderEndpointTransport 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, ProviderEndpointTransport value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpOauthLoginGrantType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ProviderEndpointTransport)); } } } -/// Allowed values for the `McpAppsSetHostContextDetailsAvailableDisplayMode` enumeration. +/// Provider family. Matches the `type` field of a BYOK provider config. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct McpAppsSetHostContextDetailsAvailableDisplayMode : 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 McpAppsSetHostContextDetailsAvailableDisplayMode(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; - /// Rendered inline within the host conversation surface. - public static McpAppsSetHostContextDetailsAvailableDisplayMode Inline { get; } = new("inline"); + /// OpenAI-compatible endpoint (use the OpenAI client library). + public static ProviderEndpointType Openai { get; } = new("openai"); - /// Rendered as a fullscreen overlay. - public static McpAppsSetHostContextDetailsAvailableDisplayMode Fullscreen { get; } = new("fullscreen"); + /// Azure OpenAI endpoint (use the OpenAI client library with the Azure base URL). + public static ProviderEndpointType Azure { get; } = new("azure"); - /// Rendered as a picture-in-picture floating panel. - public static McpAppsSetHostContextDetailsAvailableDisplayMode Pip { get; } = new("pip"); + /// 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 ==(McpAppsSetHostContextDetailsAvailableDisplayMode left, McpAppsSetHostContextDetailsAvailableDisplayMode 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 !=(McpAppsSetHostContextDetailsAvailableDisplayMode left, McpAppsSetHostContextDetailsAvailableDisplayMode 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 McpAppsSetHostContextDetailsAvailableDisplayMode other && Equals(other); + public override bool Equals(object? obj) => obj is ProviderEndpointType other && Equals(other); /// - public bool Equals(McpAppsSetHostContextDetailsAvailableDisplayMode 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); @@ -18940,65 +25594,62 @@ 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 ProviderEndpointType 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, ProviderEndpointType value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpAppsSetHostContextDetailsAvailableDisplayMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ProviderEndpointType)); } } } -/// Current display mode (SEP-1865). +/// Wire API to be used, when required for the provider type. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct McpAppsSetHostContextDetailsDisplayMode : 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 McpAppsSetHostContextDetailsDisplayMode(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; - /// Rendered inline within the host conversation surface. - public static McpAppsSetHostContextDetailsDisplayMode Inline { get; } = new("inline"); - - /// Rendered as a fullscreen overlay. - public static McpAppsSetHostContextDetailsDisplayMode Fullscreen { get; } = new("fullscreen"); + /// Classic chat-completions request shape. + public static ProviderEndpointWireApi Completions { get; } = new("completions"); - /// Rendered as a picture-in-picture floating panel. - public static McpAppsSetHostContextDetailsDisplayMode Pip { get; } = new("pip"); + /// Newer responses request shape. + public static ProviderEndpointWireApi Responses { get; } = new("responses"); - /// 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 ==(ProviderEndpointWireApi left, ProviderEndpointWireApi 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 !=(ProviderEndpointWireApi left, ProviderEndpointWireApi right) => !(left == right); /// - public override bool Equals(object? obj) => obj is McpAppsSetHostContextDetailsDisplayMode other && Equals(other); + public override bool Equals(object? obj) => obj is ProviderEndpointWireApi other && Equals(other); /// - public bool Equals(McpAppsSetHostContextDetailsDisplayMode 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); @@ -19006,65 +25657,62 @@ 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 ProviderEndpointWireApi 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, ProviderEndpointWireApi value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpAppsSetHostContextDetailsDisplayMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ProviderEndpointWireApi)); } } } -/// Platform type for responsive design. +/// Provider transport. Defaults to "http". [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct McpAppsSetHostContextDetailsPlatform : 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 McpAppsSetHostContextDetailsPlatform(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; - /// Host runs in a web browser. - public static McpAppsSetHostContextDetailsPlatform Web { get; } = new("web"); - - /// Host runs as a desktop application. - public static McpAppsSetHostContextDetailsPlatform Desktop { get; } = new("desktop"); + /// HTTP request/streaming transport. + public static ProviderConfigTransport Http { get; } = new("http"); - /// Host runs on a mobile device. - public static McpAppsSetHostContextDetailsPlatform Mobile { get; } = new("mobile"); + /// WebSocket transport. + public static ProviderConfigTransport Websockets { get; } = new("websockets"); - /// 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 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 !=(McpAppsSetHostContextDetailsPlatform left, McpAppsSetHostContextDetailsPlatform 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 McpAppsSetHostContextDetailsPlatform other && Equals(other); + public override bool Equals(object? obj) => obj is ProviderConfigTransport other && Equals(other); /// - public bool Equals(McpAppsSetHostContextDetailsPlatform 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); @@ -19072,62 +25720,65 @@ 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 ProviderConfigTransport 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, ProviderConfigTransport value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpAppsSetHostContextDetailsPlatform)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ProviderConfigTransport)); } } } -/// UI theme preference per SEP-1865. +/// Provider type. Defaults to "openai" for generic OpenAI-compatible APIs. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct McpAppsSetHostContextDetailsTheme : 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 McpAppsSetHostContextDetailsTheme(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; - /// Light UI theme. - public static McpAppsSetHostContextDetailsTheme Light { get; } = new("light"); + /// Generic OpenAI-compatible API. + public static ProviderConfigType Openai { get; } = new("openai"); - /// Dark UI theme. - public static McpAppsSetHostContextDetailsTheme Dark { get; } = new("dark"); + /// Azure OpenAI Service endpoint. + public static ProviderConfigType Azure { get; } = new("azure"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(McpAppsSetHostContextDetailsTheme left, McpAppsSetHostContextDetailsTheme 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 !=(McpAppsSetHostContextDetailsTheme left, McpAppsSetHostContextDetailsTheme 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 McpAppsSetHostContextDetailsTheme other && Equals(other); + public override bool Equals(object? obj) => obj is ProviderConfigType other && Equals(other); /// - public bool Equals(McpAppsSetHostContextDetailsTheme 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); @@ -19135,65 +25786,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 ProviderConfigType 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, ProviderConfigType value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpAppsSetHostContextDetailsTheme)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ProviderConfigType)); } } } -/// Allowed values for the `McpAppsHostContextDetailsAvailableDisplayMode` enumeration. +/// Wire API format (openai/azure only). Defaults to "completions". [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct McpAppsHostContextDetailsAvailableDisplayMode : 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 McpAppsHostContextDetailsAvailableDisplayMode(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; - /// 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"); - - /// Rendered as a picture-in-picture floating panel. - public static McpAppsHostContextDetailsAvailableDisplayMode Pip { get; } = new("pip"); + /// OpenAI Chat Completions wire format. + public static ProviderConfigWireApi Completions { get; } = new("completions"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(McpAppsHostContextDetailsAvailableDisplayMode left, McpAppsHostContextDetailsAvailableDisplayMode right) => left.Equals(right); + /// OpenAI Responses API wire format. + public static ProviderConfigWireApi Responses { get; } = new("responses"); - /// 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 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 !=(ProviderConfigWireApi left, ProviderConfigWireApi right) => !(left == right); /// - public override bool Equals(object? obj) => obj is McpAppsHostContextDetailsAvailableDisplayMode other && Equals(other); + public override bool Equals(object? obj) => obj is ProviderConfigWireApi other && Equals(other); /// - public bool Equals(McpAppsHostContextDetailsAvailableDisplayMode 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); @@ -19201,65 +25849,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 ProviderConfigWireApi 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, ProviderConfigWireApi value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpAppsHostContextDetailsAvailableDisplayMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ProviderConfigWireApi)); } } } -/// Current display mode (SEP-1865). +/// Allowed values for the `OptionsUpdateAdditionalContentExclusionPolicyScope` enumeration. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct McpAppsHostContextDetailsDisplayMode : 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 McpAppsHostContextDetailsDisplayMode(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; - /// 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"); + /// The content exclusion policy applies to the current repository. + public static OptionsUpdateAdditionalContentExclusionPolicyScope Repo { get; } = new("repo"); - /// Rendered as a picture-in-picture floating panel. - public static McpAppsHostContextDetailsDisplayMode Pip { get; } = new("pip"); + /// 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 ==(McpAppsHostContextDetailsDisplayMode left, McpAppsHostContextDetailsDisplayMode 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 !=(McpAppsHostContextDetailsDisplayMode left, McpAppsHostContextDetailsDisplayMode 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 McpAppsHostContextDetailsDisplayMode other && Equals(other); + public override bool Equals(object? obj) => obj is OptionsUpdateAdditionalContentExclusionPolicyScope other && Equals(other); /// - public bool Equals(McpAppsHostContextDetailsDisplayMode 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); @@ -19267,65 +25912,62 @@ 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 OptionsUpdateAdditionalContentExclusionPolicyScope 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, OptionsUpdateAdditionalContentExclusionPolicyScope value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpAppsHostContextDetailsDisplayMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(OptionsUpdateAdditionalContentExclusionPolicyScope)); } } } -/// Platform type for responsive design. +/// 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 McpAppsHostContextDetailsPlatform : 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 McpAppsHostContextDetailsPlatform(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; - /// Host runs in a web browser. - public static McpAppsHostContextDetailsPlatform Web { get; } = new("web"); - - /// Host runs as a desktop application. - public static McpAppsHostContextDetailsPlatform Desktop { get; } = new("desktop"); + /// Use the model's default context tier and its standard token limits / pricing. + public static OptionsUpdateContextTier Default { get; } = new("default"); - /// Host runs on a mobile device. - public static McpAppsHostContextDetailsPlatform Mobile { get; } = new("mobile"); + /// 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 ==(McpAppsHostContextDetailsPlatform left, McpAppsHostContextDetailsPlatform 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 !=(McpAppsHostContextDetailsPlatform left, McpAppsHostContextDetailsPlatform 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 McpAppsHostContextDetailsPlatform other && Equals(other); + public override bool Equals(object? obj) => obj is OptionsUpdateContextTier other && Equals(other); /// - public bool Equals(McpAppsHostContextDetailsPlatform 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); @@ -19333,62 +25975,62 @@ 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 OptionsUpdateContextTier 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, OptionsUpdateContextTier value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpAppsHostContextDetailsPlatform)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(OptionsUpdateContextTier)); } } } -/// UI theme preference per SEP-1865. +/// 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 McpAppsHostContextDetailsTheme : 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 McpAppsHostContextDetailsTheme(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; - /// Light UI theme. - public static McpAppsHostContextDetailsTheme Light { get; } = new("light"); + /// Pass MCP server environment values as literal strings. + public static OptionsUpdateEnvValueMode Direct { get; } = new("direct"); - /// Dark UI theme. - public static McpAppsHostContextDetailsTheme Dark { get; } = new("dark"); + /// 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 ==(McpAppsHostContextDetailsTheme left, McpAppsHostContextDetailsTheme 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 !=(McpAppsHostContextDetailsTheme left, McpAppsHostContextDetailsTheme 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 McpAppsHostContextDetailsTheme other && Equals(other); + public override bool Equals(object? obj) => obj is OptionsUpdateEnvValueMode other && Equals(other); /// - public bool Equals(McpAppsHostContextDetailsTheme 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); @@ -19396,62 +26038,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 OptionsUpdateEnvValueMode 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, OptionsUpdateEnvValueMode value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpAppsHostContextDetailsTheme)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(OptionsUpdateEnvValueMode)); } } } -/// Transport to be used for provider requests. +/// Reasoning summary mode for supported model clients. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ProviderEndpointTransport : 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 ProviderEndpointTransport(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; - /// HTTP request/streaming transport. - public static ProviderEndpointTransport Http { get; } = new("http"); + /// Do not request reasoning summaries from the model. + public static OptionsUpdateReasoningSummary None { get; } = new("none"); - /// WebSocket transport. - public static ProviderEndpointTransport Websockets { get; } = new("websockets"); + /// Request a concise summary of model reasoning. + public static OptionsUpdateReasoningSummary Concise { get; } = new("concise"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(ProviderEndpointTransport left, ProviderEndpointTransport right) => left.Equals(right); + /// Request a detailed summary of model reasoning. + public static OptionsUpdateReasoningSummary Detailed { get; } = new("detailed"); - /// 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 ==(OptionsUpdateReasoningSummary left, OptionsUpdateReasoningSummary right) => left.Equals(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 ProviderEndpointTransport other && Equals(other); + public override bool Equals(object? obj) => obj is OptionsUpdateReasoningSummary other && Equals(other); /// - public bool Equals(ProviderEndpointTransport 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); @@ -19459,65 +26104,77 @@ 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 OptionsUpdateReasoningSummary 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, OptionsUpdateReasoningSummary value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ProviderEndpointTransport)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(OptionsUpdateReasoningSummary)); } } } -/// Provider family. Matches the `type` field of a BYOK provider config. +/// Origin of the sandbox choice supplied by an internal client. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ProviderEndpointType : IEquatable +public readonly struct SandboxConfigSource : 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 SandboxConfigSource(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"); + /// The client applied the default because no sandbox preference was configured. + public static SandboxConfigSource NeverConfigured { get; } = new("never_configured"); - /// Azure OpenAI endpoint (use the OpenAI client library with the Azure base URL). - public static ProviderEndpointType Azure { get; } = new("azure"); + /// The user's persisted settings enabled the sandbox. + public static SandboxConfigSource UserEnabled { get; } = new("user_enabled"); - /// Anthropic endpoint (use the Anthropic client library). - public static ProviderEndpointType Anthropic { get; } = new("anthropic"); + /// The user's persisted settings disabled the sandbox. + public static SandboxConfigSource UserDisabled { get; } = new("user_disabled"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(ProviderEndpointType left, ProviderEndpointType right) => left.Equals(right); + /// A command-line flag selected the sandbox state for this session. + public static SandboxConfigSource SessionFlag { get; } = new("session_flag"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(ProviderEndpointType left, ProviderEndpointType right) => !(left == right); + /// The user disabled the sandbox for the current session. + public static SandboxConfigSource SessionDisabled { get; } = new("session_disabled"); + + /// The client disabled the sandbox because the host cannot enforce it. + public static SandboxConfigSource UnsupportedHost { get; } = new("unsupported_host"); + + /// A repository policy selected the sandbox state. + public static SandboxConfigSource RepositoryPolicy { get; } = new("repository_policy"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SandboxConfigSource left, SandboxConfigSource right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SandboxConfigSource left, SandboxConfigSource right) => !(left == right); /// - public override bool Equals(object? obj) => obj is ProviderEndpointType other && Equals(other); + public override bool Equals(object? obj) => obj is SandboxConfigSource other && Equals(other); /// - public bool Equals(ProviderEndpointType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(SandboxConfigSource other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -19525,62 +26182,89 @@ 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 SandboxConfigSource 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, SandboxConfigSource value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ProviderEndpointType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SandboxConfigSource)); } } } -/// Wire API to be used, when required for the provider type. +/// Session capability enabled for this session. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ProviderEndpointWireApi : IEquatable +public readonly struct SessionCapability : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . - [JsonConstructor] - public ProviderEndpointWireApi(string value) - { - ArgumentException.ThrowIfNullOrWhiteSpace(value); - _value = value; - } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public SessionCapability(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// 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"); - /// Gets the value associated with this . - public string Value => _value ?? string.Empty; + /// Automatic hidden system notifications. + public static SessionCapability SystemNotifications { get; } = new("system-notifications"); - /// Classic chat-completions request shape. - public static ProviderEndpointWireApi Completions { get; } = new("completions"); + /// SDK elicitation support. + public static SessionCapability Elicitation { get; } = new("elicitation"); - /// Newer responses request shape. - public static ProviderEndpointWireApi Responses { get; } = new("responses"); + /// Cross-session history tools and session-store SQL prompt/tool metadata. + public static SessionCapability SessionStore { get; } = new("session-store"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(ProviderEndpointWireApi left, ProviderEndpointWireApi right) => left.Equals(right); + /// MCP Apps UI passthrough. + public static SessionCapability McpApps { get; } = new("mcp-apps"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(ProviderEndpointWireApi left, ProviderEndpointWireApi right) => !(left == right); + /// 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 ProviderEndpointWireApi other && Equals(other); + public override bool Equals(object? obj) => obj is SessionCapability other && Equals(other); /// - public bool Equals(ProviderEndpointWireApi 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); @@ -19588,62 +26272,62 @@ 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 SessionCapability 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, SessionCapability value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ProviderEndpointWireApi)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionCapability)); } } } -/// Provider transport. Defaults to "http". +/// Controls automatic non-interactive profile loading where supported. Explicit initScripts are unaffected. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ProviderConfigTransport : 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 ProviderConfigTransport(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; - /// HTTP request/streaming transport. - public static ProviderConfigTransport Http { get; } = new("http"); + /// Disable automatic non-interactive profile loading. Explicit initScripts still run. + public static ShellInitProfile None { get; } = new("none"); - /// WebSocket transport. - public static ProviderConfigTransport Websockets { get; } = new("websockets"); + /// 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 ==(ProviderConfigTransport left, ProviderConfigTransport 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 !=(ProviderConfigTransport left, ProviderConfigTransport 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 ProviderConfigTransport other && Equals(other); + public override bool Equals(object? obj) => obj is ShellInitProfile other && Equals(other); /// - public bool Equals(ProviderConfigTransport 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); @@ -19651,65 +26335,62 @@ 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 ShellInitProfile 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, ShellInitProfile value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ProviderConfigTransport)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ShellInitProfile)); } } } -/// Provider type. Defaults to "openai" for generic OpenAI-compatible APIs. +/// Supported built-in shells for initialization scripts. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ProviderConfigType : 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 ProviderConfigType(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; - /// Generic OpenAI-compatible API. - public static ProviderConfigType Openai { get; } = new("openai"); - - /// Azure OpenAI Service endpoint. - public static ProviderConfigType Azure { get; } = new("azure"); + /// Source the script in the built-in Bash shell on macOS and Linux. + public static ShellInitScriptShell Bash { get; } = new("bash"); - /// Anthropic API endpoint. - public static ProviderConfigType Anthropic { get; } = new("anthropic"); + /// 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 ==(ProviderConfigType left, ProviderConfigType 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 !=(ProviderConfigType left, ProviderConfigType 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 ProviderConfigType other && Equals(other); + public override bool Equals(object? obj) => obj is ShellInitScriptShell other && Equals(other); /// - public bool Equals(ProviderConfigType 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); @@ -19717,62 +26398,62 @@ 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 ShellInitScriptShell 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, ShellInitScriptShell value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ProviderConfigType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ShellInitScriptShell)); } } } -/// Wire API format (openai/azure only). Defaults to "completions". +/// Controls how availableTools (allowlist) and excludedTools (denylist) combine when both are set. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ProviderConfigWireApi : 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 ProviderConfigWireApi(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; - /// OpenAI Chat Completions wire format. - public static ProviderConfigWireApi Completions { get; } = new("completions"); + /// 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"); - /// OpenAI Responses API wire format. - public static ProviderConfigWireApi Responses { get; } = new("responses"); + /// 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 ==(ProviderConfigWireApi left, ProviderConfigWireApi 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 !=(ProviderConfigWireApi left, ProviderConfigWireApi 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 ProviderConfigWireApi other && Equals(other); + public override bool Equals(object? obj) => obj is OptionsUpdateToolFilterPrecedence other && Equals(other); /// - public bool Equals(ProviderConfigWireApi 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); @@ -19780,62 +26461,68 @@ 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 OptionsUpdateToolFilterPrecedence 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, OptionsUpdateToolFilterPrecedence value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ProviderConfigWireApi)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(OptionsUpdateToolFilterPrecedence)); } } } -/// Allowed values for the `OptionsUpdateAdditionalContentExclusionPolicyScope` enumeration. +/// 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 OptionsUpdateAdditionalContentExclusionPolicyScope : 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 OptionsUpdateAdditionalContentExclusionPolicyScope(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; - /// The content exclusion policy applies to the current repository. - public static OptionsUpdateAdditionalContentExclusionPolicyScope Repo { get; } = new("repo"); + /// Extension discovered from the current project's .github/extensions directory. + public static ExtensionSource Project { get; } = new("project"); - /// The content exclusion policy applies across all repositories. - public static OptionsUpdateAdditionalContentExclusionPolicyScope All { get; } = new("all"); + /// Extension discovered from the user's ~/.copilot/extensions directory. + public static ExtensionSource User { get; } = new("user"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(OptionsUpdateAdditionalContentExclusionPolicyScope left, OptionsUpdateAdditionalContentExclusionPolicyScope right) => left.Equals(right); + /// Extension contributed by an installed plugin. + public static ExtensionSource Plugin { get; } = new("plugin"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(OptionsUpdateAdditionalContentExclusionPolicyScope left, OptionsUpdateAdditionalContentExclusionPolicyScope right) => !(left == 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 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 OptionsUpdateAdditionalContentExclusionPolicyScope other && Equals(other); + public override bool Equals(object? obj) => obj is ExtensionSource other && Equals(other); /// - public bool Equals(OptionsUpdateAdditionalContentExclusionPolicyScope 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); @@ -19843,62 +26530,68 @@ 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 ExtensionSource 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, ExtensionSource value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(OptionsUpdateAdditionalContentExclusionPolicyScope)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ExtensionSource)); } } } -/// 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. +/// Current status: running, disabled, failed, or starting. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct OptionsUpdateContextTier : 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 OptionsUpdateContextTier(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; - /// Use the model's default context tier and its standard token limits / pricing. - public static OptionsUpdateContextTier Default { get; } = new("default"); + /// The extension process is running. + public static ExtensionStatus Running { get; } = new("running"); - /// 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 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 ==(OptionsUpdateContextTier left, OptionsUpdateContextTier right) => left.Equals(right); + /// The extension failed to start or crashed. + public static ExtensionStatus Failed { get; } = new("failed"); + + /// 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 !=(OptionsUpdateContextTier left, OptionsUpdateContextTier right) => !(left == 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 OptionsUpdateContextTier other && Equals(other); + public override bool Equals(object? obj) => obj is ExtensionStatus other && Equals(other); /// - public bool Equals(OptionsUpdateContextTier 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); @@ -19906,62 +26599,65 @@ 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 ExtensionStatus 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, ExtensionStatus value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(OptionsUpdateContextTier)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ExtensionStatus)); } } } -/// How env values are passed to MCP servers (`direct` inlines literal values; `indirect` resolves at launch). +/// Type of GitHub reference. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct OptionsUpdateEnvValueMode : 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 OptionsUpdateEnvValueMode(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; - /// Pass MCP server environment values as literal strings. - public static OptionsUpdateEnvValueMode Direct { get; } = new("direct"); + /// GitHub issue reference. + public static PushAttachmentGitHubReferenceType Issue { get; } = new("issue"); - /// Resolve MCP server environment values from host-side references. - public static OptionsUpdateEnvValueMode Indirect { get; } = new("indirect"); + /// GitHub pull request reference. + public static PushAttachmentGitHubReferenceType Pr { get; } = new("pr"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(OptionsUpdateEnvValueMode left, OptionsUpdateEnvValueMode 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 !=(OptionsUpdateEnvValueMode left, OptionsUpdateEnvValueMode 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 OptionsUpdateEnvValueMode other && Equals(other); + public override bool Equals(object? obj) => obj is PushAttachmentGitHubReferenceType other && Equals(other); /// - public bool Equals(OptionsUpdateEnvValueMode 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); @@ -19969,65 +26665,59 @@ 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 PushAttachmentGitHubReferenceType 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, PushAttachmentGitHubReferenceType value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(OptionsUpdateEnvValueMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PushAttachmentGitHubReferenceType)); } } } -/// Reasoning summary mode for supported model clients. +/// Custom input-format kind. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct OptionsUpdateReasoningSummary : IEquatable +public readonly struct BuiltinToolFormatType : 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 BuiltinToolFormatType(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"); - - /// Request a concise summary of model reasoning. - public static OptionsUpdateReasoningSummary Concise { get; } = new("concise"); - - /// Request a detailed summary of model reasoning. - public static OptionsUpdateReasoningSummary Detailed { get; } = new("detailed"); + /// The tool input is parsed with the supplied grammar. + public static BuiltinToolFormatType Grammar { get; } = new("grammar"); - /// 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 equivalent. + public static bool operator ==(BuiltinToolFormatType left, BuiltinToolFormatType right) => left.Equals(right); - /// 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 not equivalent. + public static bool operator !=(BuiltinToolFormatType left, BuiltinToolFormatType right) => !(left == right); /// - public override bool Equals(object? obj) => obj is OptionsUpdateReasoningSummary other && Equals(other); + public override bool Equals(object? obj) => obj is BuiltinToolFormatType other && Equals(other); /// - public bool Equals(OptionsUpdateReasoningSummary other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(BuiltinToolFormatType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -20035,89 +26725,59 @@ 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 BuiltinToolFormatType 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, BuiltinToolFormatType value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(OptionsUpdateReasoningSummary)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(BuiltinToolFormatType)); } } } -/// Session capability enabled for this session. +/// Root JSON Schema type for a built-in tool input. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct SessionCapability : IEquatable +public readonly struct BuiltinToolInputSchemaType : 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 BuiltinToolInputSchemaType(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"); - - /// 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"); + /// The tool accepts a JSON object. + public static BuiltinToolInputSchemaType Object { get; } = new("object"); - /// 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 ==(BuiltinToolInputSchemaType left, BuiltinToolInputSchemaType 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 !=(BuiltinToolInputSchemaType left, BuiltinToolInputSchemaType right) => !(left == right); /// - public override bool Equals(object? obj) => obj is SessionCapability other && Equals(other); + public override bool Equals(object? obj) => obj is BuiltinToolInputSchemaType other && Equals(other); /// - public bool Equals(SessionCapability other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(BuiltinToolInputSchemaType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -20125,62 +26785,62 @@ 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 BuiltinToolInputSchemaType 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, BuiltinToolInputSchemaType value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionCapability)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(BuiltinToolInputSchemaType)); } } } -/// Controls automatic non-interactive profile loading where supported. Explicit initScripts are unaffected. +/// Binary result type discriminator. Use "image" for images and "resource" for other binary data. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ShellInitProfile : IEquatable +public readonly struct ExternalToolTextResultForLlmBinaryResultsForLlmType : 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 ShellInitProfile(string value) + public ExternalToolTextResultForLlmBinaryResultsForLlmType(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; - /// Disable automatic non-interactive profile loading. Explicit initScripts still run. - public static ShellInitProfile None { get; } = new("none"); + /// Binary image data. + public static ExternalToolTextResultForLlmBinaryResultsForLlmType Image { get; } = new("image"); - /// Allow automatic non-interactive profile loading when supported. Explicit initScripts still run. - public static ShellInitProfile NonInteractive { get; } = new("non-interactive"); + /// Other binary resource data. + public static ExternalToolTextResultForLlmBinaryResultsForLlmType Resource { get; } = new("resource"); - /// 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 equivalent. + public static bool operator ==(ExternalToolTextResultForLlmBinaryResultsForLlmType left, ExternalToolTextResultForLlmBinaryResultsForLlmType right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(ShellInitProfile left, ShellInitProfile right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ExternalToolTextResultForLlmBinaryResultsForLlmType left, ExternalToolTextResultForLlmBinaryResultsForLlmType right) => !(left == right); /// - public override bool Equals(object? obj) => obj is ShellInitProfile other && Equals(other); + public override bool Equals(object? obj) => obj is ExternalToolTextResultForLlmBinaryResultsForLlmType other && Equals(other); /// - public bool Equals(ShellInitProfile other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(ExternalToolTextResultForLlmBinaryResultsForLlmType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -20188,62 +26848,62 @@ public ShellInitProfile(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 ShellInitProfile Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override ExternalToolTextResultForLlmBinaryResultsForLlmType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, ShellInitProfile value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, ExternalToolTextResultForLlmBinaryResultsForLlmType value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ShellInitProfile)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ExternalToolTextResultForLlmBinaryResultsForLlmType)); } } } -/// Supported built-in shells for initialization scripts. +/// Theme variant this icon is intended for. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ShellInitScriptShell : IEquatable +public readonly struct ExternalToolTextResultForLlmContentResourceLinkIconTheme : 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 ShellInitScriptShell(string value) + public ExternalToolTextResultForLlmContentResourceLinkIconTheme(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; - /// Source the script in the built-in Bash shell on macOS and Linux. - public static ShellInitScriptShell Bash { get; } = new("bash"); + /// Icon intended for light themes. + public static ExternalToolTextResultForLlmContentResourceLinkIconTheme Light { get; } = new("light"); - /// Source the script in the built-in PowerShell shell on Windows. - public static ShellInitScriptShell Powershell { get; } = new("powershell"); + /// Icon intended for dark themes. + public static ExternalToolTextResultForLlmContentResourceLinkIconTheme Dark { get; } = new("dark"); - /// 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 equivalent. + public static bool operator ==(ExternalToolTextResultForLlmContentResourceLinkIconTheme left, ExternalToolTextResultForLlmContentResourceLinkIconTheme right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(ShellInitScriptShell left, ShellInitScriptShell right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ExternalToolTextResultForLlmContentResourceLinkIconTheme left, ExternalToolTextResultForLlmContentResourceLinkIconTheme right) => !(left == right); /// - public override bool Equals(object? obj) => obj is ShellInitScriptShell other && Equals(other); + public override bool Equals(object? obj) => obj is ExternalToolTextResultForLlmContentResourceLinkIconTheme other && Equals(other); /// - public bool Equals(ShellInitScriptShell other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(ExternalToolTextResultForLlmContentResourceLinkIconTheme other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -20251,62 +26911,71 @@ public ShellInitScriptShell(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 ShellInitScriptShell Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override ExternalToolTextResultForLlmContentResourceLinkIconTheme Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, ShellInitScriptShell value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, ExternalToolTextResultForLlmContentResourceLinkIconTheme value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ShellInitScriptShell)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ExternalToolTextResultForLlmContentResourceLinkIconTheme)); } } } -/// Controls how availableTools (allowlist) and excludedTools (denylist) combine when both are set. +/// Execution outcome classification. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct OptionsUpdateToolFilterPrecedence : IEquatable +public readonly struct ToolResultType : 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 ToolResultType(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"); + /// The tool completed successfully. + public static ToolResultType Success { get; } = new("success"); - /// 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"); + /// The tool failed. + public static ToolResultType Failure { get; } = new("failure"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(OptionsUpdateToolFilterPrecedence left, OptionsUpdateToolFilterPrecedence right) => left.Equals(right); + /// The tool exceeded its execution timeout. + public static ToolResultType Timeout { get; } = new("timeout"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(OptionsUpdateToolFilterPrecedence left, OptionsUpdateToolFilterPrecedence right) => !(left == right); + /// The tool request was rejected before execution. + public static ToolResultType Rejected { get; } = new("rejected"); + + /// Permission policy denied the tool request. + public static ToolResultType Denied { get; } = new("denied"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ToolResultType left, ToolResultType right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ToolResultType left, ToolResultType right) => !(left == right); /// - public override bool Equals(object? obj) => obj is OptionsUpdateToolFilterPrecedence other && Equals(other); + public override bool Equals(object? obj) => obj is ToolResultType other && Equals(other); /// - public bool Equals(OptionsUpdateToolFilterPrecedence other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(ToolResultType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -20314,68 +26983,62 @@ 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 ToolResultType 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, ToolResultType value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(OptionsUpdateToolFilterPrecedence)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ToolResultType)); } } } -/// Discovery source: project (.github/extensions/), user (~/.copilot/extensions/), plugin (installed plugin), or session (session-state/<id>/extensions/). +/// Controls whether the runtime may defer loading an external tool definition. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ExtensionSource : IEquatable +public readonly struct ProtocolExternalToolDefer : 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 ProtocolExternalToolDefer(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"); - - /// Extension discovered from the user's ~/.copilot/extensions directory. - public static ExtensionSource User { get; } = new("user"); - - /// Extension contributed by an installed plugin. - public static ExtensionSource Plugin { get; } = new("plugin"); + /// The runtime may defer the tool according to its tool-loading policy. + public static ProtocolExternalToolDefer Auto { get; } = new("auto"); - /// Extension discovered from the current session's state directory (loaded only for this session). - public static ExtensionSource Session { get; } = new("session"); + /// The runtime must include the tool without deferring it. + public static ProtocolExternalToolDefer Never { get; } = new("never"); - /// 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 ==(ProtocolExternalToolDefer left, ProtocolExternalToolDefer 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 !=(ProtocolExternalToolDefer left, ProtocolExternalToolDefer right) => !(left == right); /// - public override bool Equals(object? obj) => obj is ExtensionSource other && Equals(other); + public override bool Equals(object? obj) => obj is ProtocolExternalToolDefer other && Equals(other); /// - public bool Equals(ExtensionSource other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(ProtocolExternalToolDefer other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -20383,68 +27046,65 @@ 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 ProtocolExternalToolDefer 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, ProtocolExternalToolDefer value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ExtensionSource)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ProtocolExternalToolDefer)); } } } -/// Current status: running, disabled, failed, or starting. +/// Context tier override for matching subagents. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ExtensionStatus : 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 ExtensionStatus(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; - /// 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"); + /// Inherit the parent session's effective context tier at dispatch time. + public static SubagentSettingsEntryContextTier Inherit { get; } = new("inherit"); - /// The extension failed to start or crashed. - public static ExtensionStatus Failed { get; } = new("failed"); + /// Use the model's default context window. + public static SubagentSettingsEntryContextTier Default { get; } = new("default"); - /// The extension process is starting. - public static ExtensionStatus Starting { get; } = new("starting"); + /// 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 ==(ExtensionStatus left, ExtensionStatus 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 !=(ExtensionStatus left, ExtensionStatus 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 ExtensionStatus other && Equals(other); + public override bool Equals(object? obj) => obj is SubagentSettingsEntryContextTier other && Equals(other); /// - public bool Equals(ExtensionStatus 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); @@ -20452,65 +27112,59 @@ 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 SubagentSettingsEntryContextTier 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, SubagentSettingsEntryContextTier value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ExtensionStatus)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SubagentSettingsEntryContextTier)); } } } -/// Type of GitHub reference. +/// Defines the allowed values. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct PushAttachmentGitHubReferenceType : IEquatable +public readonly struct CommandsInvocationOrigin : 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 CommandsInvocationOrigin(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"); - - /// GitHub discussion reference. - public static PushAttachmentGitHubReferenceType Discussion { get; } = new("discussion"); + /// Gets the settings value. + public static CommandsInvocationOrigin Settings { get; } = new("settings"); - /// 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 ==(CommandsInvocationOrigin left, CommandsInvocationOrigin 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 !=(CommandsInvocationOrigin left, CommandsInvocationOrigin right) => !(left == right); /// - public override bool Equals(object? obj) => obj is PushAttachmentGitHubReferenceType other && Equals(other); + public override bool Equals(object? obj) => obj is CommandsInvocationOrigin other && Equals(other); /// - public bool Equals(PushAttachmentGitHubReferenceType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(CommandsInvocationOrigin other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -20518,65 +27172,62 @@ 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 CommandsInvocationOrigin 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, CommandsInvocationOrigin value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PushAttachmentGitHubReferenceType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(CommandsInvocationOrigin)); } } } -/// Context tier override for matching subagents. +/// Whether a pending slash-command invocation effect was applied or cancelled by the host. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct SubagentSettingsEntryContextTier : IEquatable +public readonly struct CommandsInvocationEffectOutcome : 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 CommandsInvocationEffectOutcome(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"); - - /// Use the model's default context window. - public static SubagentSettingsEntryContextTier Default { get; } = new("default"); + /// The host applied the pending invocation effect. + public static CommandsInvocationEffectOutcome Applied { get; } = new("applied"); - /// Pin the subagent to the long-context tier when supported. - public static SubagentSettingsEntryContextTier LongContext { get; } = new("long_context"); + /// The host cancelled the pending invocation effect, so any provisional state must be reverted. + public static CommandsInvocationEffectOutcome Cancelled { get; } = new("cancelled"); - /// 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 ==(CommandsInvocationEffectOutcome left, CommandsInvocationEffectOutcome 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 !=(CommandsInvocationEffectOutcome left, CommandsInvocationEffectOutcome right) => !(left == right); /// - public override bool Equals(object? obj) => obj is SubagentSettingsEntryContextTier other && Equals(other); + public override bool Equals(object? obj) => obj is CommandsInvocationEffectOutcome other && Equals(other); /// - public bool Equals(SubagentSettingsEntryContextTier other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(CommandsInvocationEffectOutcome other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -20584,20 +27235,20 @@ 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 CommandsInvocationEffectOutcome 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, CommandsInvocationEffectOutcome value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SubagentSettingsEntryContextTier)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(CommandsInvocationEffectOutcome)); } } } @@ -20669,6 +27320,66 @@ public override void Write(Utf8JsonWriter writer, UIElicitationResponseAction va } +/// Structured MCP elicitation mode. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct McpElicitationFormMode : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public McpElicitationFormMode(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Gets the form value. + public static McpElicitationFormMode Form { get; } = new("form"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(McpElicitationFormMode left, McpElicitationFormMode right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(McpElicitationFormMode left, McpElicitationFormMode right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is McpElicitationFormMode other && Equals(other); + + /// + public bool Equals(McpElicitationFormMode 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 McpElicitationFormMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, McpElicitationFormMode value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpElicitationFormMode)); + } + } +} + + /// 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))] @@ -20936,49 +27647,253 @@ public override void Write(Utf8JsonWriter writer, PermissionsConfigureAdditional } -/// Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers. +/// Disposition of a permission request as observed by the responding client. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct PermissionsSetApproveAllSource : 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 PermissionsSetApproveAllSource(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; - /// Allow-all was enabled from a CLI command-line flag. - public static PermissionsSetApproveAllSource CliFlag { get; } = new("cli_flag"); + /// The request was approved automatically without a new human decision. + public static PermissionDecisionOutcome AutoApproved { get; } = new("auto_approved"); - /// Allow-all was enabled by a slash command. - public static PermissionsSetApproveAllSource SlashCommand { get; } = new("slash_command"); + /// The request was denied without an interactive user decision; source records why. + public static PermissionDecisionOutcome AutopilotDenied { get; } = new("autopilot_denied"); - /// Allow-all was enabled by confirming autopilot behavior. - public static PermissionsSetApproveAllSource AutopilotConfirmation { get; } = new("autopilot_confirmation"); + /// The response came from an interactive user prompt. + public static PermissionDecisionOutcome PromptedUser { get; } = new("prompted_user"); + + /// 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 PermissionDecisionOutcome other && Equals(other); + + /// + public bool Equals(PermissionDecisionOutcome 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 PermissionDecisionOutcome Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, PermissionDecisionOutcome value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionDecisionOutcome)); + } + } +} + + +/// Response capability available to the client when it settled a permission request. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct PermissionResponseCapability : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public PermissionResponseCapability(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The client could ask a user for this decision. + public static PermissionResponseCapability Interactive { get; } = new("interactive"); + + /// The client could return an automated response but could not ask a user. + public static PermissionResponseCapability Headless { get; } = new("headless"); + + /// The client had no response path available. + public static PermissionResponseCapability None { get; } = new("none"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(PermissionResponseCapability left, PermissionResponseCapability right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(PermissionResponseCapability left, PermissionResponseCapability right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is PermissionResponseCapability other && Equals(other); + + /// + public bool Equals(PermissionResponseCapability 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 PermissionResponseCapability Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, PermissionResponseCapability value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionResponseCapability)); + } + } +} + + +/// Controlled reason or actor responsible for a permission response. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct PermissionDecisionSource : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public PermissionDecisionSource(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The response followed the assisted-approval judge recommendation. + public static PermissionDecisionSource AssistedApproval { get; } = new("assisted_approval"); + + /// A human supplied the response through an interactive prompt. + public static PermissionDecisionSource HumanResponse { get; } = new("human_response"); + + /// The host applied a standing policy or override rather than a judge recommendation or human decision. + public static PermissionDecisionSource HostPolicy { get; } = new("host_policy"); + + /// 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 PermissionDecisionSource other && Equals(other); + + /// + public bool Equals(PermissionDecisionSource 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 PermissionDecisionSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, PermissionDecisionSource value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionDecisionSource)); + } + } +} + + +/// Client surface that submitted a permission response. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct PermissionDecisionSurface : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public PermissionDecisionSurface(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The interactive Copilot CLI terminal UI. + public static PermissionDecisionSurface Tui { get; } = new("tui"); + + /// The non-interactive Copilot CLI prompt mode. + public static PermissionDecisionSurface PromptMode { get; } = new("prompt_mode"); + + /// The Copilot App client. + public static PermissionDecisionSurface CopilotApp { get; } = new("copilot_app"); - /// Allow-all was enabled through an RPC caller. - public static PermissionsSetApproveAllSource Rpc { get; } = new("rpc"); + /// An Agent Client Protocol host. + public static PermissionDecisionSurface Acp { get; } = new("acp"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(PermissionsSetApproveAllSource left, PermissionsSetApproveAllSource 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 !=(PermissionsSetApproveAllSource left, PermissionsSetApproveAllSource 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 PermissionsSetApproveAllSource other && Equals(other); + public override bool Equals(object? obj) => obj is PermissionDecisionSurface other && Equals(other); /// - public bool Equals(PermissionsSetApproveAllSource 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); @@ -20986,65 +27901,71 @@ 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 PermissionDecisionSurface 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, PermissionDecisionSurface value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionsSetApproveAllSource)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionDecisionSurface)); } } } -/// Current or requested allow-all mode. +/// 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 PermissionsAllowAllMode : 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 PermissionsAllowAllMode(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; - /// Permission requests follow the normal approval flow. - public static PermissionsAllowAllMode Off { get; } = new("off"); + /// 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"); + + /// Allow-all was enabled by confirming autopilot behavior. + public static PermissionsSetApproveAllSource AutopilotConfirmation { get; } = new("autopilot_confirmation"); - /// Tool, path, and URL permission requests are automatically approved. - public static PermissionsAllowAllMode On { get; } = new("on"); + /// Allow-all was enabled at startup by the `defaultPermissionMode` user setting. + public static PermissionsSetApproveAllSource UserSetting { get; } = new("user_setting"); - /// 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"); + /// 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 ==(PermissionsAllowAllMode left, PermissionsAllowAllMode 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 !=(PermissionsAllowAllMode left, PermissionsAllowAllMode 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 PermissionsAllowAllMode other && Equals(other); + public override bool Equals(object? obj) => obj is PermissionsSetApproveAllSource other && Equals(other); /// - public bool Equals(PermissionsAllowAllMode 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); @@ -21052,68 +27973,71 @@ 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 PermissionsSetApproveAllSource 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, PermissionsSetApproveAllSource value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionsAllowAllMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionsSetApproveAllSource)); } } } -/// Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers. +/// Optional source for permission-mode telemetry. Defaults to `rpc` when omitted for SDK callers. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct PermissionsSetAllowAllSource : IEquatable +public readonly struct PermissionModeSource : 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 PermissionModeSource(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"); + /// The mode was set from a CLI command-line flag. + public static PermissionModeSource CliFlag { get; } = new("cli_flag"); - /// Allow-all was enabled by a slash command. - public static PermissionsSetAllowAllSource SlashCommand { get; } = new("slash_command"); + /// The mode was set by a slash command. + public static PermissionModeSource SlashCommand { get; } = new("slash_command"); - /// Allow-all was enabled by confirming autopilot behavior. - public static PermissionsSetAllowAllSource AutopilotConfirmation { get; } = new("autopilot_confirmation"); + /// The mode was set by confirming autopilot behavior. + public static PermissionModeSource AutopilotConfirmation { get; } = new("autopilot_confirmation"); - /// Allow-all was enabled through an RPC caller. - public static PermissionsSetAllowAllSource Rpc { get; } = new("rpc"); + /// The mode was set at startup by the `defaultPermissionMode` user setting. + public static PermissionModeSource UserSetting { get; } = new("user_setting"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(PermissionsSetAllowAllSource left, PermissionsSetAllowAllSource right) => left.Equals(right); + /// The mode was set through an RPC caller. + public static PermissionModeSource Rpc { get; } = new("rpc"); - /// 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 equivalent. + public static bool operator ==(PermissionModeSource left, PermissionModeSource right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(PermissionModeSource left, PermissionModeSource right) => !(left == right); /// - public override bool Equals(object? obj) => obj is PermissionsSetAllowAllSource other && Equals(other); + public override bool Equals(object? obj) => obj is PermissionModeSource other && Equals(other); /// - public bool Equals(PermissionsSetAllowAllSource other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(PermissionModeSource other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -21121,20 +28045,20 @@ 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 PermissionModeSource 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, PermissionModeSource value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionsSetAllowAllSource)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionModeSource)); } } } @@ -22102,69 +29026,6 @@ public override void Write(Utf8JsonWriter writer, QueuePendingItemsKind value, J } -/// 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 EventsCursorStatus : IEquatable -{ - private readonly string? _value; - - /// Initializes a new instance of the struct. - /// The value to associate with this . - [JsonConstructor] - public EventsCursorStatus(string value) - { - ArgumentException.ThrowIfNullOrWhiteSpace(value); - _value = value; - } - - /// 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 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 ==(EventsCursorStatus left, EventsCursorStatus right) => left.Equals(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 EventsCursorStatus other && Equals(other); - - /// - public bool Equals(EventsCursorStatus 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 EventsCursorStatus 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) - { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(EventsCursorStatus)); - } - } -} - - /// 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))] @@ -22228,69 +29089,6 @@ public override void Write(Utf8JsonWriter writer, EventsAgentScope value, JsonSe } -/// 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 EventsReadDirection : IEquatable -{ - private readonly string? _value; - - /// Initializes a new instance of the struct. - /// The value to associate with this . - [JsonConstructor] - public EventsReadDirection(string value) - { - ArgumentException.ThrowIfNullOrWhiteSpace(value); - _value = value; - } - - /// Gets the value associated with this . - public string Value => _value ?? string.Empty; - - /// Page from the cursor toward newer events (default). - public static EventsReadDirection Forward { get; } = new("forward"); - - /// Tail-first: return the newest events and page toward older events. - public static EventsReadDirection Backward { get; } = new("backward"); - - /// 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 !=(EventsReadDirection left, EventsReadDirection right) => !(left == right); - - /// - public override bool Equals(object? obj) => obj is EventsReadDirection other && Equals(other); - - /// - public bool Equals(EventsReadDirection 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 EventsReadDirection Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); - } - - /// - public override void Write(Utf8JsonWriter writer, EventsReadDirection value, JsonSerializerOptions options) - { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(EventsReadDirection)); - } - } -} - - /// Client population used for the prediction baseline. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] @@ -23002,6 +29800,69 @@ public override void Write(Utf8JsonWriter writer, LlmInferenceHttpRequestStartTr } +/// Why the runtime is requesting a GitHub credential. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct GitHubTokenAcquireReason : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public GitHubTokenAcquireReason(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The runtime is acquiring the registration's first credential. + public static GitHubTokenAcquireReason Initial { get; } = new("initial"); + + /// The runtime is replacing a credential that is approaching expiry. + public static GitHubTokenAcquireReason Refresh { get; } = new("refresh"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(GitHubTokenAcquireReason left, GitHubTokenAcquireReason right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(GitHubTokenAcquireReason left, GitHubTokenAcquireReason right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is GitHubTokenAcquireReason other && Equals(other); + + /// + public bool Equals(GitHubTokenAcquireReason 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 GitHubTokenAcquireReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, GitHubTokenAcquireReason value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(GitHubTokenAcquireReason)); + } + } +} + + /// Provides server-scoped RPC methods (no session required). public sealed class ServerRpc { @@ -23024,17 +29885,32 @@ 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. 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. + /// Identity of the integrating host. Optional; omit it to keep the default attribution. + /// Connection token; required when the server was started with COPILOT_CONNECTION_TOKEN. /// 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)] - internal async Task ConnectAsync(string? token = null, bool? enableGitHubTelemetryForwarding = null, CancellationToken cancellationToken = default) + internal async Task ConnectAsync(bool? enableGitHubTelemetryForwarding = null, ConnectClientInfo? clientInfo = null, string? token = null, CancellationToken cancellationToken = default) { - var request = new ConnectRequest { Token = token, EnableGitHubTelemetryForwarding = enableGitHubTelemetryForwarding }; + var request = new ConnectRequest { EnableGitHubTelemetryForwarding = enableGitHubTelemetryForwarding, ClientInfo = clientInfo, Token = token }; 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 uses its built-in extension launcher. + /// 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); + } + + /// Hooks APIs. + public ServerHooksApi Hooks => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + /// Models APIs. public ServerModelsApi Models => field ?? @@ -23071,6 +29947,12 @@ internal async Task ConnectAsync(string? token = null, bool? enab Interlocked.CompareExchange(ref field, new(_rpc), null) ?? field; + /// Catalog APIs. + public ServerCatalogApi Catalog => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + /// Plugins APIs. public ServerPluginsApi Plugins => field ?? @@ -23107,6 +29989,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 ?? @@ -23138,6 +30026,29 @@ internal async Task ConnectAsync(string? token = null, bool? enab field; } +/// Provides server-scoped Hooks APIs. +[Experimental(Diagnostics.Experimental)] +public sealed class ServerHooksApi +{ + private readonly JsonRpc _rpc; + + internal ServerHooksApi(JsonRpc rpc) + { + _rpc = rpc; + } + + /// Discovers hook actions enabled under server-side discovery settings from user, repository, plugin, and managed-policy sources. + /// Optional project directory paths whose trusted repository and project-expanded plugin hooks should be discovered. When omitted or empty, user, managed-policy, and globally enabled installed or explicit plugin hooks are returned without project expansion. + /// When true, omit host-owned user and plugin hook rows and their diagnostics. Managed-policy hooks and trusted repository hooks remain visible, and host disabledHooks still contribute to each remaining row's effective enabled state. This filters sources rather than simulating a host with no settings. + /// The to monitor for cancellation requests. The default is . + /// Server-discovered hook actions and partial-load diagnostics from user, repository, plugin, and managed-policy sources. Concrete sessions may include additional session-specific hook sources. + public async Task DiscoverAsync(IList? projectPaths = null, bool? excludeHostHooks = null, CancellationToken cancellationToken = default) + { + var request = new HooksDiscoverRequest { ProjectPaths = projectPaths, ExcludeHostHooks = excludeHostHooks }; + return await CopilotClient.InvokeRpcAsync(_rpc, "hooks.discover", [request], cancellationToken); + } +} + /// Provides server-scoped Models APIs. [Experimental(Diagnostics.Experimental)] public sealed class ServerModelsApi @@ -23150,12 +30061,13 @@ internal ServerModelsApi(JsonRpc rpc) } /// Lists Copilot models available to the authenticated user. - /// 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. + /// Opaque account identifier returned by `account.getAllUsers`. When omitted, the current account is used. + /// GitHub token accepted for compatibility with existing SDK clients. When provided, resolves this token instead of using the current account. /// The to monitor for cancellation requests. The default is . /// List of Copilot models available to the resolved user, including capabilities and billing metadata. - public async Task ListAsync(string? gitHubToken = null, CancellationToken cancellationToken = default) + public async Task ListAsync(string? selectionId = null, string? gitHubToken = null, CancellationToken cancellationToken = default) { - var request = new ModelsListRequest { GitHubToken = gitHubToken }; + var request = new ModelsListRequest { SelectionId = selectionId, GitHubToken = gitHubToken }; return await CopilotClient.InvokeRpcAsync(_rpc, "models.list", [request], cancellationToken); } @@ -23201,13 +30113,14 @@ internal ServerAccountApi(JsonRpc rpc) _rpc = rpc; } - /// Gets Copilot quota usage for the authenticated user or supplied GitHub token. - /// GitHub token for per-user quota lookup. When provided, resolves this token to determine the user's quota instead of using the global auth. + /// Gets Copilot quota usage for the current or opaquely selected authenticated user. + /// Opaque account identifier returned by `account.getAllUsers`. When omitted, the current account is used. + /// GitHub token accepted for compatibility with existing SDK clients. When provided, resolves this token instead of using the current account. /// The to monitor for cancellation requests. The default is . /// Quota usage snapshots for the resolved user, keyed by quota type. - public async Task GetQuotaAsync(string? gitHubToken = null, CancellationToken cancellationToken = default) + public async Task GetQuotaAsync(string? selectionId = null, string? gitHubToken = null, CancellationToken cancellationToken = default) { - var request = new AccountGetQuotaRequest { GitHubToken = gitHubToken }; + var request = new AccountGetQuotaRequest { SelectionId = selectionId, GitHubToken = gitHubToken }; return await CopilotClient.InvokeRpcAsync(_rpc, "account.getQuota", [request], cancellationToken); } @@ -23227,31 +30140,29 @@ public async Task> GetAllUsersAsync(CancellationToken can return await CopilotClient.InvokeRpcAsync>(_rpc, "account.getAllUsers", [], cancellationToken); } - /// Stores authentication credentials after successful login (e.g., device code flow). + /// Validates and stores authentication credentials. When login is omitted, resolves the authenticated user from the token before persistence. /// GitHub host URL. - /// User login/username. /// GitHub authentication token. + /// User login/username. When omitted, the runtime validates the token and resolves the login from GitHub. /// The to monitor for cancellation requests. The default is . /// Result of a successful login; throws on failure. - public async Task LoginAsync(string host, string login, string token, CancellationToken cancellationToken = default) + public async Task LoginAsync(string host, string token, string? login = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(host); - ArgumentNullException.ThrowIfNull(login); ArgumentNullException.ThrowIfNull(token); - var request = new AccountLoginRequest { Host = host, Login = login, Token = token }; + var request = new AccountLoginRequest { Host = host, Token = token, Login = login }; return await CopilotClient.InvokeRpcAsync(_rpc, "account.login", [request], cancellationToken); } /// Removes user authentication from keychain and persisted state. + /// Opaque account identifier returned by `account.getAllUsers`. /// Authentication information for the user to log out. /// The to monitor for cancellation requests. The default is . /// Logout result indicating if more users remain. - public async Task LogoutAsync(AuthInfo authInfo, CancellationToken cancellationToken = default) + public async Task LogoutAsync(string? selectionId = null, AuthInfo? authInfo = null, CancellationToken cancellationToken = default) { - ArgumentNullException.ThrowIfNull(authInfo); - - var request = new AccountLogoutRequest { AuthInfo = authInfo }; + var request = new AccountLogoutRequest { SelectionId = selectionId, AuthInfo = authInfo }; return await CopilotClient.InvokeRpcAsync(_rpc, "account.logout", [request], cancellationToken); } } @@ -23301,6 +30212,21 @@ public async Task DiscoverAsync(string? workingDirectory = nu return await CopilotClient.InvokeRpcAsync(_rpc, "mcp.discover", [request], cancellationToken); } + /// Requests a side-effect-free MCP install plan from a catalog candidate handle or a caller-supplied card. This host-implemented server method is available through SDK/TUI hosts; standalone and C-ABI runtimes whose host does not implement server-method dispatch return JSON-RPC MethodNotFound. A runtime with planning available returns a normalised plan and opaque single-use plan handle; a runtime without it returns the typed planning-unavailable result. A completed plan reports resource identity, provenance, eligible transport choices, the user-scope target, required typed values and secret placeholders, the policy result, the configuration changes installing would make, and whether a reload would be needed. Planning never writes configuration, stores a secret, or reloads MCP servers, so abandoning a plan needs no call and leaves nothing behind. + /// Protocol version and capabilities the caller requires. + /// What to plan: either a candidate handle from a previous search, or a card supplied directly. + /// Configuration scope the plan targets. Defaults to user scope when omitted. + /// The to monitor for cancellation requests. The default is . + /// Outcome of an mcp.planInstall call: either a normalised plan, or one typed refusal. Nothing is written in either case. + public async Task PlanInstallAsync(CatalogClientContract contract, McpPlanInstallSource source, McpPlanScope? scope = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(contract); + ArgumentNullException.ThrowIfNull(source); + + var request = new McpPlanInstallRequest { Contract = contract, Source = source, Scope = scope }; + return await CopilotClient.InvokeRpcAsync(_rpc, "mcp.planInstall", [request], cancellationToken); + } + /// Config APIs. public ServerMcpConfigApi Config => field ?? @@ -23436,6 +30362,34 @@ public async Task DisableAsync(IList ids, CancellationToken cancellation } } +/// Provides server-scoped Catalog APIs. +[Experimental(Diagnostics.Experimental)] +public sealed class ServerCatalogApi +{ + private readonly JsonRpc _rpc; + + internal ServerCatalogApi(JsonRpc rpc) + { + _rpc = rpc; + } + + /// Requests a bounded catalog search. This host-implemented server method is available through SDK/TUI hosts; standalone and C-ABI runtimes whose host does not implement server-method dispatch return JSON-RPC MethodNotFound. A runtime with search available returns inert candidate summaries, each with an opaque single-use handle scoped to this runtime instance; a runtime without it returns the typed search-unavailable result. Public authorities may be searched anonymously, while an authority that requires credentials yields the typed authentication-required result. All returned text, URLs, and package metadata are untrusted external data and can never trigger instructions, tools, or installation. Read-only: nothing is installed, configured, or persisted. + /// Protocol version and capabilities the caller requires. + /// Free-text search query. Persisted as tool input for session continuity, but omitted from telemetry. + /// Maximum number of candidates to return. Defaults to 10 when omitted. + /// Restrict results to these candidate kinds. When omitted, every kind the runtime supports is searched. + /// The to monitor for cancellation requests. The default is . + /// Outcome of a catalog.search call: either bounded inert candidates, or one typed refusal. Never a partial success. + public async Task SearchAsync(CatalogClientContract contract, string query, int? limit = null, IList? kinds = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(contract); + ArgumentNullException.ThrowIfNull(query); + + var request = new CatalogSearchRequest { Contract = contract, Query = query, Limit = limit, Kinds = kinds }; + return await CopilotClient.InvokeRpcAsync(_rpc, "catalog.search", [request], cancellationToken); + } +} + /// Provides server-scoped Plugins APIs. [Experimental(Diagnostics.Experimental)] public sealed class ServerPluginsApi @@ -23522,6 +30476,12 @@ public async Task DisableAsync(IList names, CancellationToken cancellati await CopilotClient.InvokeRpcAsync(_rpc, "plugins.disable", [request], cancellationToken); } + /// Builtin APIs. + public ServerPluginsBuiltinApi Builtin => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + /// Marketplaces APIs. public ServerPluginsMarketplacesApi Marketplaces => field ?? @@ -23529,6 +30489,29 @@ public async Task DisableAsync(IList names, CancellationToken cancellati field; } +/// Provides server-scoped PluginsBuiltin APIs. +[Experimental(Diagnostics.Experimental)] +public sealed class ServerPluginsBuiltinApi +{ + private readonly JsonRpc _rpc; + + internal ServerPluginsBuiltinApi(JsonRpc rpc) + { + _rpc = rpc; + } + + /// Replaces this server's trusted built-in plugin directories while no sessions are active. + /// Complete replacement set of trusted built-in plugin directories. Every entry must be an absolute local filesystem path no longer than 4096 characters. + /// The to monitor for cancellation requests. The default is . + public async Task SetAsync(IList paths, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(paths); + + var request = new PluginsBuiltinSetRequest { Paths = paths }; + await CopilotClient.InvokeRpcAsync(_rpc, "plugins.builtin.set", [request], cancellationToken); + } +} + /// Provides server-scoped PluginsMarketplaces APIs. [Experimental(Diagnostics.Experimental)] public sealed class ServerPluginsMarketplacesApi @@ -23659,6 +30642,18 @@ public async Task SetDisabledSkillsAsync(IList disabledSkills, Cancellat var request = new SkillsConfigSetDisabledSkillsRequest { DisabledSkills = disabledSkills }; await CopilotClient.InvokeRpcAsync(_rpc, "skills.config.setDisabledSkills", [request], cancellationToken); } + + /// Atomically adds or removes one skill from the disabled list. + /// Name of the skill to add to or remove from the disabled list. + /// True to disable the skill, false to enable it. + /// The to monitor for cancellation requests. The default is . + public async Task SetSkillDisabledAsync(string name, bool disabled, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(name); + + var request = new SkillsConfigSetSkillDisabledRequest { Name = name, Disabled = disabled }; + await CopilotClient.InvokeRpcAsync(_rpc, "skills.config.setSkillDisabled", [request], cancellationToken); + } } /// Provides server-scoped Agents APIs. @@ -23806,6 +30801,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 @@ -23875,7 +30890,7 @@ public async Task SetProviderAsync(CancellationTo /// Delivers the response head (status + headers) for an in-flight request, correlated by the requestId the runtime supplied in httpRequestStart. Must be called exactly once per request before any httpResponseChunk frames. /// Matches the requestId from the originating httpRequestStart frame. /// HTTP status code. - /// The headers parameter. + /// HTTP response headers, preserving multiple values per name. /// Optional HTTP status reason phrase. /// The to monitor for cancellation requests. The default is . /// Whether the start frame was accepted. @@ -23977,6 +30992,21 @@ internal async Task GetMetadataAsync(string sessionId return await CopilotClient.InvokeRpcAsync(_rpc, "sessions.getMetadata", [request], cancellationToken); } + /// Reads a page of durable events directly from a local session's persisted journal without creating, resuming, or activating the session. The initial backward read uses a bounded tail scan for fast first paint; cursor continuations preserve the session event-log paging semantics. Persisted events may omit payloads that are reconstructed only for an active session. + /// Session ID whose persisted event journal should be read. + /// Opaque cursor returned by a previous persisted-event read. Omit on the first call. + /// Maximum number of events to return in this batch (1–1000, default 200). + /// Direction to page through persisted history. Forward starts at the beginning; backward starts with the newest events. Events in each page remain chronological. + /// 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 ReadPersistedEventsAsync(string sessionId, string? cursor = null, long? max = null, EventsReadDirection? direction = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(sessionId); + + var request = new SessionsReadPersistedEventsRequest { SessionId = sessionId, Cursor = cursor, Max = max, Direction = direction }; + return await CopilotClient.InvokeRpcAsync(_rpc, "sessions.readPersistedEvents", [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 . @@ -24257,28 +31287,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); } } @@ -24324,6 +31351,12 @@ internal SessionRpc(CopilotSession session) internal CopilotSession Session => _session; + /// Sandbox APIs. + public SandboxApi Sandbox => + field ?? + Interlocked.CompareExchange(ref field, new(_session), null) ?? + field; + /// GitHubAuth APIs. public GitHubAuthApi GitHubAuth => field ?? @@ -24696,6 +31729,29 @@ public async Task LogAsync(string message, SessionLogLevel? level = n } } +/// Provides session-scoped Sandbox APIs. +[Experimental(Diagnostics.Experimental)] +public sealed class SandboxApi +{ + private readonly CopilotSession _session; + + internal SandboxApi(CopilotSession session) + { + _session = session; + } + + /// Returns whether managed policy requires sandbox enforcement and whether an enforcement failure has permanently blocked the session. + /// The to monitor for cancellation requests. The default is . + /// Managed sandbox enforcement state for a session. + public async Task GetEnforcementStatusAsync(CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new SessionSandboxGetEnforcementStatusRequest { SessionId = _session.SessionId }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.sandbox.getEnforcementStatus", [request], cancellationToken); + } +} + /// Provides session-scoped GitHubAuth APIs. [Experimental(Diagnostics.Experimental)] public sealed class GitHubAuthApi @@ -24722,13 +31778,112 @@ public async Task GetStatusAsync(CancellationToken cancellati /// 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. /// The to monitor for cancellation requests. The default is . /// Indicates whether the credential update succeeded. - public async Task SetCredentialsAsync(AuthInfo? credentials = null, CancellationToken cancellationToken = default) + public async Task SetCredentialsAsync(SettableAuthInfo? credentials = null, CancellationToken cancellationToken = default) { _session.ThrowIfDisposed(); var request = new SessionSetCredentialsParams { SessionId = _session.SessionId, Credentials = credentials }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.gitHubAuth.setCredentials", [request], cancellationToken); } + + /// Gets the current authentication information for internal session hosts. + /// The to monitor for cancellation requests. The default is . + /// Current authentication information, or null when no authentication is active. + internal async Task GetCurrentAuthInfoAsync(CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new SessionGitHubAuthGetCurrentAuthInfoRequest { SessionId = _session.SessionId }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.gitHubAuth.getCurrentAuthInfo", [request], cancellationToken); + } + + /// Gets all authentication accounts available to the internal session host. + /// The to monitor for cancellation requests. The default is . + /// Authentication accounts available to the internal session host. + internal async Task> GetAllAuthAvailableAsync(CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new SessionGitHubAuthGetAllAuthAvailableRequest { SessionId = _session.SessionId }; + return await CopilotClient.InvokeRpcAsync>(_session.Rpc, "session.gitHubAuth.getAllAuthAvailable", [request], cancellationToken); + } + + /// Refreshes Copilot account metadata for the current authentication. + /// The to monitor for cancellation requests. The default is . + /// Current authentication information, or null when no authentication is active. + internal async Task RefreshCopilotUserAsync(CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new SessionGitHubAuthRefreshCopilotUserRequest { SessionId = _session.SessionId }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.gitHubAuth.refreshCopilotUser", [request], cancellationToken); + } + + /// Logs in a GitHub user through the internal session host. + /// GitHub host URL. + /// GitHub login. + /// GitHub authentication token. + /// Whether to persist the token after login. + /// The to monitor for cancellation requests. The default is . + /// Authentication credentials accepted only at native protocol ingress. Runtime outputs use credential-free `AuthIdentity` metadata. + internal async Task LoginAsync(string host, string login, string token, bool? persist = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(host); + ArgumentNullException.ThrowIfNull(login); + ArgumentNullException.ThrowIfNull(token); + _session.ThrowIfDisposed(); + + var request = new SessionAuthLoginRequest { SessionId = _session.SessionId, Host = host, Login = login, Token = token, Persist = persist }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.gitHubAuth.login", [request], cancellationToken); + } + + /// Switches the session to another available authentication. + /// Authentication information to activate. + /// Optional token paired with the authentication information. + /// The to monitor for cancellation requests. The default is . + internal async Task SwitchToAuthAsync(AuthInfo authInfo, string? token = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(authInfo); + _session.ThrowIfDisposed(); + + var request = new SessionAuthSwitchRequest { SessionId = _session.SessionId, AuthInfo = authInfo, Token = token }; + await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.gitHubAuth.switchToAuth", [request], cancellationToken); + } + + /// Logs out the session's current GitHub authentication. + /// The to monitor for cancellation requests. The default is . + /// Whether the current authentication was logged out. + internal async Task LogoutAsync(CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new SessionGitHubAuthLogoutRequest { SessionId = _session.SessionId }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.gitHubAuth.logout", [request], cancellationToken); + } + + /// Logs out a specific GitHub authentication. + /// Authentication information to log out. + /// The to monitor for cancellation requests. The default is . + /// Whether the requested authentication was logged out. + internal async Task LogoutUserAsync(AuthInfo authInfo, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(authInfo); + _session.ThrowIfDisposed(); + + var request = new SessionAuthLogoutUserRequest { SessionId = _session.SessionId, AuthInfo = authInfo }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.gitHubAuth.logoutUser", [request], cancellationToken); + } + + /// Gets validation errors from the most recent authentication attempt. + /// The to monitor for cancellation requests. The default is . + /// Validation errors from the most recent authentication attempt. + internal async Task> LastAuthErrorsAsync(CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new SessionGitHubAuthLastAuthErrorsRequest { SessionId = _session.SessionId }; + return await CopilotClient.InvokeRpcAsync>(_session.Rpc, "session.gitHubAuth.lastAuthErrors", [request], cancellationToken); + } } /// Provides session-scoped Debug APIs. @@ -24825,6 +31980,12 @@ public async Task CloseAsync(string instanceId, CancellationToken cancellationTo field ?? Interlocked.CompareExchange(ref field, new(_session), null) ?? field; + + /// Provider APIs. + public CanvasProviderApi Provider => + field ?? + Interlocked.CompareExchange(ref field, new(_session), null) ?? + field; } /// Provides session-scoped CanvasAction APIs. @@ -24855,6 +32016,46 @@ public async Task InvokeAsync(string instanceId, strin } } +/// Provides session-scoped CanvasProvider APIs. +[Experimental(Diagnostics.Experimental)] +public sealed class CanvasProviderApi +{ + private readonly CopilotSession _session; + + internal CanvasProviderApi(CopilotSession session) + { + _session = session; + } + + /// Registers an internal canvas provider connection and its contributions. + /// Connection identifier for callback routing. + /// Provider metadata supplied by the host. + /// Canvas contributions supplied by the provider. + /// The to monitor for cancellation requests. The default is . + internal async Task RegisterAsync(string connectionId, object info, IList canvases, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(connectionId); + ArgumentNullException.ThrowIfNull(info); + ArgumentNullException.ThrowIfNull(canvases); + _session.ThrowIfDisposed(); + + var request = new CanvasProviderRegisterRequest { SessionId = _session.SessionId, ConnectionId = connectionId, Info = CopilotClient.ToJsonElementForWire(info)!.Value, Canvases = canvases.Select(static v => CopilotClient.ToJsonElementForWire(v)!.Value).ToList() }; + await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.canvas.provider.register", [request], cancellationToken); + } + + /// Unregisters an internal canvas provider connection. + /// Connection identifier to unregister. + /// The to monitor for cancellation requests. The default is . + internal async Task UnregisterAsync(string connectionId, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(connectionId); + _session.ThrowIfDisposed(); + + var request = new CanvasProviderUnregisterRequest { SessionId = _session.SessionId, ConnectionId = connectionId }; + await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.canvas.provider.unregister", [request], cancellationToken); + } +} + /// Provides session-scoped Factory APIs. [Experimental(Diagnostics.Experimental)] public sealed class FactoryApi @@ -24885,17 +32086,51 @@ public async Task RunAsync(string name, object args, RunOption /// Resumes a factory run using its persisted name, arguments, journal, and accounting. /// Factory run identifier. /// Optional per-invocation resource ceiling overrides. + /// Whether to notify the originating session when the factory completes. + /// Whether to emit factory phase names to the session transcript. /// 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) + public async Task ResumeAsync(string runId, FactoryRunLimits? limits = null, bool? notifyOnComplete = null, bool? logPhaseNames = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(runId); _session.ThrowIfDisposed(); - var request = new FactoryResumeRequest { SessionId = _session.SessionId, RunId = runId, Limits = limits }; + var request = new FactoryResumeRequest { SessionId = _session.SessionId, RunId = runId, Limits = limits, NotifyOnComplete = notifyOnComplete, LogPhaseNames = logPhaseNames }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.factory.resume", [request], cancellationToken); } + /// Internal tool-originated factory invocation. + /// Registered factory name. + /// Factory input value. + /// Tool-originated factory invocation options. + /// Opaque identifier of the originating tool call. + /// The to monitor for cancellation requests. The default is . + /// Complete current or terminal factory run envelope. + internal async Task RunFromToolAsync(string name, object args, FactoryToolRunOptions? options = null, string? toolCallId = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(name); + ArgumentNullException.ThrowIfNull(args); + _session.ThrowIfDisposed(); + + var request = new FactoryToolRunRequest { SessionId = _session.SessionId, Name = name, Args = CopilotClient.ToJsonElementForWire(args)!.Value, Options = options, ToolCallId = toolCallId }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.factory.runFromTool", [request], cancellationToken); + } + + /// Internal tool-originated factory resume. + /// Factory run identifier. + /// Optional per-invocation resource ceiling overrides. + /// Opaque identifier of the originating tool call. + /// The to monitor for cancellation requests. The default is . + /// Resolved persisted factory identity and resumed run envelope. + internal async Task ResumeFromToolAsync(string runId, FactoryRunLimits? limits = null, string? toolCallId = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(runId); + _session.ThrowIfDisposed(); + + var request = new FactoryToolResumeRequest { SessionId = _session.SessionId, RunId = runId, Limits = limits, ToolCallId = toolCallId }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.factory.resumeFromTool", [request], cancellationToken); + } + /// Gets the current or settled envelope for a factory run. /// Factory run identifier. /// The to monitor for cancellation requests. The default is . @@ -24910,13 +32145,16 @@ public async Task GetRunAsync(string runId, 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 . - /// Factory runs in durable creation order. - public async Task ListRunsAsync(CancellationToken cancellationToken = default) + /// 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 }; + var request = new FactoryListRunsRequest { SessionId = _session.SessionId, AfterSeq = afterSeq, BeforeSeq = beforeSeq, Limit = limit }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.factory.listRuns", [request], cancellationToken); } @@ -25083,18 +32321,44 @@ public async Task GetCurrentAsync(CancellationToken cancellationTo /// 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. + /// Origin to record on the effective `session.model_change` event for trusted in-process calls. Transport SDK calls are always recorded as `sdk`, regardless of this value. /// 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). + /// Explicit response to a model-switch compaction preflight. Omit to request a confirmation projection when compaction is necessary. + /// When true, evaluate context-window compaction policy before applying the switch. + /// Optional repository settings scope to persist after the switch commits. + /// Settings scope used when persisting the selected model. + /// Require the target to be currently available and enabled before applying the switch. + /// Optional settings context and explicit-override flags used to persist a picker selection. /// 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, bool? deferIfModelChangeQueued = 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, ModelChangeSource? source = null, bool? deferIfModelChangeQueued = null, string? compactionDecision = null, bool? runCompactionPreflight = null, string? repoScope = null, string? modelChangeScope = null, bool? requireAvailable = null, ModelPickerPersistenceRequest? pickerPersistence = 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, DeferIfModelChangeQueued = deferIfModelChangeQueued }; + var request = new ModelSwitchToRequest { SessionId = _session.SessionId, ModelId = modelId, ReasoningEffort = reasoningEffort, ReasoningSummary = reasoningSummary, Verbosity = verbosity, ModelCapabilities = modelCapabilities, ContextTier = contextTier, Source = source, DeferIfModelChangeQueued = deferIfModelChangeQueued, CompactionDecision = compactionDecision, RunCompactionPreflight = runCompactionPreflight, RepoScope = repoScope, ModelChangeScope = modelChangeScope, RequireAvailable = requireAvailable, PickerPersistence = pickerPersistence }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.model.switchTo", [request], cancellationToken); } + /// Resolves and applies organization-managed and repository model overlays. + /// Model required by device-managed policy, when configured. + /// Model required by server-managed policy, when configured. + /// Startup default model from the enterprise policy helper, when configured. Weakest of the managed sources: it applies only when neither device nor server policy names a model, and an explicit user selection still wins. + /// Model selected by repository settings, when configured. + /// Reasoning effort selected by repository settings, when configured. + /// Context tier selected by repository settings, when configured. + /// Model explicitly selected by the CLI, when provided. + /// Whether the overlay is being applied while resuming a deferred session. + /// The to monitor for cancellation requests. The default is . + /// The model identifier active on the session after the switch. + internal async Task ApplyStartupOverlayAsync(string? deviceManagedModel = null, string? serverManagedModel = null, string? policyHelperModel = null, string? repoModel = null, string? repoReasoningEffort = null, string? repoContextTier = null, string? cliModel = null, bool? deferredResume = null, CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new ModelApplyStartupOverlayRequest { SessionId = _session.SessionId, DeviceManagedModel = deviceManagedModel, ServerManagedModel = serverManagedModel, PolicyHelperModel = policyHelperModel, RepoModel = repoModel, RepoReasoningEffort = repoReasoningEffort, RepoContextTier = repoContextTier, CliModel = cliModel, DeferredResume = deferredResume }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.model.applyStartupOverlay", [request], cancellationToken); + } + /// Updates the session's reasoning effort without changing the selected model. /// 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. /// The to monitor for cancellation requests. The default is . @@ -25145,13 +32409,24 @@ public async Task GetAsync(CancellationToken cancellationToken = de /// Sets the current agent interaction mode. /// The session mode the agent is operating in. - /// The to monitor for cancellation requests. The default is . - public async Task SetAsync(SessionMode mode, CancellationToken cancellationToken = default) + /// Session whose plan-mode base state should be inherited. + /// Whether a dedicated plan model is configured. + /// Dedicated model to use in plan mode, when configured. + /// Reasoning effort to use with the dedicated plan model. + /// Context tier to use with the dedicated plan model. + /// Explicit response to a model-switch compaction preflight. + /// Whether leaving plan mode should restore the session's previous model. + /// Whether the selected plan model should be persisted. + /// Settings context used when persisting the selected plan model. + /// Action to perform when leaving plan mode. + /// The to monitor for cancellation requests. The default is . + /// Outcome of a session mode change, including any model switch it triggered and follow-up the host must perform. + public async Task SetAsync(SessionMode mode, string? inheritPlanBaseFromSessionId = null, bool? planModelConfigured = null, string? planModel = null, string? planReasoningEffort = null, string? planContextTier = null, string? compactionDecision = null, bool? restorePlanModel = null, bool? persistPlanSelection = null, ModelPickerSettingsContext? pickerSettingsContext = null, string? planExitAction = null, CancellationToken cancellationToken = default) { _session.ThrowIfDisposed(); - var request = new ModeSetRequest { SessionId = _session.SessionId, Mode = mode }; - await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.mode.set", [request], cancellationToken); + var request = new ModeSetRequest { SessionId = _session.SessionId, Mode = mode, InheritPlanBaseFromSessionId = inheritPlanBaseFromSessionId, PlanModelConfigured = planModelConfigured, PlanModel = planModel, PlanReasoningEffort = planReasoningEffort, PlanContextTier = planContextTier, CompactionDecision = compactionDecision, RestorePlanModel = restorePlanModel, PersistPlanSelection = persistPlanSelection, PickerSettingsContext = pickerSettingsContext, PlanExitAction = planExitAction }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.mode.set", [request], cancellationToken); } } @@ -25659,7 +32934,7 @@ internal TasksApi(CopilotSession session) /// Starts a background agent task in the session. /// Type of agent to start (e.g., 'explore', 'task', 'general-purpose'). /// Task prompt for the agent. - /// Short name for the agent, used to generate a human-readable ID. + /// Friendly, non-unique name used when displaying the agent. /// Short description of the task. /// Optional model override. /// The to monitor for cancellation requests. The default is . @@ -25947,16 +33222,25 @@ public async Task ReloadAsync(CancellationToken cancellationToken = default) await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.mcp.reload", [request], cancellationToken); } + /// Releases any turns waiting on an in-flight MCP load without cancelling the load, letting the agent proceed while MCP servers finish connecting in the background. No-op when no MCP load is in flight or waiting turns were already released. + /// The to monitor for cancellation requests. The default is . + /// Result of moving in-flight MCP loading to the background. + public async Task MoveLoadingToBackgroundAsync(CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new SessionMcpMoveLoadingToBackgroundRequest { SessionId = _session.SessionId }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.mcp.moveLoadingToBackground", [request], cancellationToken); + } + /// 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); } @@ -26016,15 +33300,13 @@ 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); } @@ -26068,19 +33350,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); } @@ -26192,6 +33468,19 @@ public async Task LoginAsync(string serverName, bool? force return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.mcp.oauth.login", [request], cancellationToken); } + /// Passively probes a configured remote MCP server to classify whether OAuth is required or a cached/override token is accepted. Does not start OAuth, emit pending OAuth requests, or mutate MCP connection state. + /// Name of the configured remote MCP server to probe. + /// The to monitor for cancellation requests. The default is . + /// Passive MCP OAuth probe result. `authenticated` means the server accepted the probe request while an OAuth-origin access token was attached; it does not prove the server required or independently validated that token. The probe does not make a second unauthenticated request. Failed is an expected probe-domain outcome; JSON-RPC errors are reserved for API-call failures. + public async Task ProbeAsync(string serverName, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(serverName); + _session.ThrowIfDisposed(); + + var request = new McpOauthProbeRequest { SessionId = _session.SessionId, ServerName = serverName }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.mcp.oauth.probe", [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 . @@ -26490,10 +33779,12 @@ internal OptionsApi(CopilotSession session) /// 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. + /// Origin of the sandbox choice. The runtime uses this only for internal telemetry provenance; managed policy is derived independently. /// 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. + /// Built-in skill names to include in this session. When specified, only these runtime-bundled skills are available. Skills from other sources with the same name remain available. Set to null to remove the allowlist restriction. /// 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`. /// 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. @@ -26521,16 +33812,16 @@ internal OptionsApi(CopilotSession session) /// Whether to enable loading of `.github/hooks/` filesystem hooks. Separate from the SDK callback hook mechanism. /// Whether to enable host git operations (context resolution, child repo scanning, git info in system prompt). /// Whether to enable cross-session store writes and reads. - /// Whether to enable skill directory scanning and loading. Falls back to enableConfigDiscovery when unset. + /// Whether skill loading is enabled. Explicit false disables every source, including a bound SDK provider; changing the value invalidates the loaded skill snapshot. When omitted, creation falls back to enableConfigDiscovery unless an SDK skill provider is registered. /// 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. /// 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, 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) + 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, SandboxConfigSource? sandboxConfigSource = null, bool? logInteractiveShells = null, OptionsUpdateEnvValueMode? envValueMode = null, bool? allowAllMcpServerInstructions = null, IList? skillDirectories = null, IList? includedBuiltinSkills = 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, 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 }; + 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, SandboxConfigSource = sandboxConfigSource, LogInteractiveShells = logInteractiveShells, EnvValueMode = envValueMode, AllowAllMcpServerInstructions = allowAllMcpServerInstructions, SkillDirectories = skillDirectories, IncludedBuiltinSkills = includedBuiltinSkills, 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); } } @@ -26641,6 +33932,55 @@ internal ToolsApi(CopilotSession session) _session = session; } + /// Executes one tool from the session's currently offered tool set through the native invocation pipeline. + /// Name of the currently offered tool to execute. + /// Arguments supplied to the tool. + /// Optional identifier used to correlate this invocation with its tool call. + /// The to monitor for cancellation requests. The default is . + /// Canonical result returned by a session tool. + public async Task ExecuteAsync(string name, object arguments, string? toolCallId = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(name); + ArgumentNullException.ThrowIfNull(arguments); + _session.ThrowIfDisposed(); + + var request = new ToolsExecuteRequest { SessionId = _session.SessionId, Name = name, Arguments = CopilotClient.ToJsonElementForWire(arguments)!.Value, ToolCallId = toolCallId }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.tools.execute", [request], cancellationToken); + } + + /// Returns the Rust-owned built-in tool descriptors used to construct the session's offered tool set. + /// Whether descriptors should favor fewer user-intervention prompts. + /// Whether tool descriptors should include authoring metadata. + /// Whether semantic skill lookup is available. + /// Shell-specific names and description lines for shell tools. + /// Whether the configured shell supports PowerShell 7 syntax. + /// Default shell timeout in milliseconds. + /// Whether background task completion notifications are enabled. + /// The to monitor for cancellation requests. The default is . + /// Rust-owned built-in tool descriptors for the session. + public async Task GetBuiltinDescriptorsAsync(bool? reduceUserIntervention = null, bool? includeAuthor = null, bool? skillEmbeddingEnabled = null, ToolsShellDescriptorConfig? shellConfig = null, bool? shellSupportsPowerShell7Syntax = null, double? shellTimeoutMs = null, bool? backgroundTaskNotificationsEnabled = null, CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new ToolsGetBuiltinDescriptorsRequest { SessionId = _session.SessionId, ReduceUserIntervention = reduceUserIntervention, IncludeAuthor = includeAuthor, SkillEmbeddingEnabled = skillEmbeddingEnabled, ShellConfig = shellConfig, ShellSupportsPowerShell7Syntax = shellSupportsPowerShell7Syntax, ShellTimeoutMs = shellTimeoutMs, BackgroundTaskNotificationsEnabled = backgroundTaskNotificationsEnabled }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.tools.getBuiltinDescriptors", [request], cancellationToken); + } + + /// Projects a completed task_complete tool call into its label-safe session event payload. + /// Arguments supplied to the completed task_complete tool call. + /// Final expanded result returned by the task_complete tool. + /// The to monitor for cancellation requests. The default is . + /// Task completion notification with summary from the agent. + public async Task TaskCompleteEventDataAsync(object toolArgs, ToolResultExpanded finalResult, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(toolArgs); + ArgumentNullException.ThrowIfNull(finalResult); + _session.ThrowIfDisposed(); + + var request = new ToolsTaskCompleteEventDataRequest { SessionId = _session.SessionId, ToolArgs = CopilotClient.ToJsonElementForWire(toolArgs)!.Value, FinalResult = finalResult }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.tools.taskCompleteEventData", [request], cancellationToken); + } + /// Provides the result for a pending external tool call. /// Request ID of the pending tool call. /// Tool call result (string or expanded result object). @@ -26678,6 +34018,19 @@ public async Task GetCurrentMetadataAsync(Cancell return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.tools.getCurrentMetadata", [request], cancellationToken); } + /// Atomically replaces the complete externally implemented tool list supplied by the calling connection. Built-in, MCP/plugin, extension-discovered, subagent, and tools supplied by other connections remain unchanged. + /// Complete replacement list for the calling connection. + /// The to monitor for cancellation requests. The default is . + /// Empty result after replacing the calling connection's externally implemented tools. + public async Task SetAsync(IList tools, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(tools); + _session.ThrowIfDisposed(); + + var request = new ToolsSetRequest { SessionId = _session.SessionId, Tools = tools }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.tools.set", [request], cancellationToken); + } + /// Updates the current session's live subagent settings after user settings change. The persisted user settings remain the source of truth for future sessions. /// Subagent settings to apply, or null to clear the live session override. /// The to monitor for cancellation requests. The default is . @@ -26717,17 +34070,32 @@ public async Task ListAsync(SessionCommandsListRequest? request = n /// Invokes a slash command in the session. /// Command name. Leading slashes are stripped and the name is matched case-insensitively. /// Raw input after the command name. + /// Optional client surface that initiated the invocation. /// The to monitor for cancellation requests. The default is . /// Result of invoking the slash command (text output, prompt to send to the agent, completion, or subcommand selection). - public async Task InvokeAsync(string name, string? input = null, CancellationToken cancellationToken = default) + public async Task InvokeAsync(string name, string? input = null, CommandsInvocationOrigin? origin = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(name); _session.ThrowIfDisposed(); - var request = new CommandsInvokeRequest { SessionId = _session.SessionId, Name = name, Input = input }; + var request = new CommandsInvokeRequest { SessionId = _session.SessionId, Name = name, Input = input, Origin = origin }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.commands.invoke", [request], cancellationToken); } + /// Finalizes persistence associated with a client-applied slash-command effect. + /// The slash-command result object that produced the pending effect, echoed back unchanged. + /// Whether the host applied or cancelled the pending invocation effect. + /// The to monitor for cancellation requests. The default is . + /// Whether finalizing the invocation effect succeeded, and the failure reason when it did not. + internal async Task FinalizeInvocationEffectAsync(CommandsFinalizeInvocationEffectRequestEffect effect, CommandsInvocationEffectOutcome outcome, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(effect); + _session.ThrowIfDisposed(); + + var request = new CommandsFinalizeInvocationEffectRequest { SessionId = _session.SessionId, Effect = effect, Outcome = outcome }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.commands.finalizeInvocationEffect", [request], cancellationToken); + } + /// Reports completion of a pending client-handled slash command. /// Request ID from the command invocation event. /// Error message if the command handler failed. @@ -26759,14 +34127,15 @@ public async Task ExecuteAsync(string commandName, string /// Enqueues a slash command for FIFO processing on the local session. /// 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. + /// Optional user-facing text for the queue row. The command string is shown when omitted. /// The to monitor for cancellation requests. The default is . /// Indicates whether the command was accepted into the local execution queue. - public async Task EnqueueAsync(string command, CancellationToken cancellationToken = default) + public async Task EnqueueAsync(string command, string? displayText = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(command); _session.ThrowIfDisposed(); - var request = new EnqueueCommandParams { SessionId = _session.SessionId, Command = command }; + var request = new EnqueueCommandParams { SessionId = _session.SessionId, Command = command, DisplayText = displayText }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.commands.enqueue", [request], cancellationToken); } @@ -26834,31 +34203,32 @@ 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) + /// Completed transient query. Ordered chunks and the terminal outcome are also delivered through `ui.ephemeral_query` session events while it runs. + 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); } /// Requests structured input from a UI-capable client. /// Message describing what information is needed from the user. /// JSON Schema describing the form fields to present to the user. + /// Elicitation mode. Omitted and form are equivalent for structured elicitation. + /// MCP request metadata. + /// MCP task metadata. /// The to monitor for cancellation requests. The default is . /// The elicitation response (accept with form values, decline, or cancel). - public async Task ElicitationAsync(string message, UIElicitationSchema requestedSchema, CancellationToken cancellationToken = default) + public async Task ElicitationAsync(string message, UIElicitationSchema requestedSchema, McpElicitationFormMode? mode = null, UIElicitationRequestMeta? _meta = null, McpTaskMetadata? task = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(message); ArgumentNullException.ThrowIfNull(requestedSchema); _session.ThrowIfDisposed(); - var request = new UIElicitationRequest { SessionId = _session.SessionId, Message = message, RequestedSchema = requestedSchema }; + var request = new UIElicitationRequest { SessionId = _session.SessionId, Message = message, RequestedSchema = requestedSchema, Mode = mode, Meta = _meta, Task = task }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.ui.elicitation", [request], cancellationToken); } @@ -27006,15 +34376,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); } @@ -27042,30 +34413,29 @@ public async Task SetApproveAllAsync(bool enable return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.permissions.setApproveAll", [request], cancellationToken); } - /// 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 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. + /// Sets the permission mode for the session. `manual` follows the normal approval flow, `assisted` attaches LLM safety recommendations, and `allow-all` automatically approves permission requests. The result returns the authoritative post-mutation mode so callers can update local state without racing the `session.permissions_changed` notification. + /// Permission mode to apply. + /// Optional judge model id for assisted mode. When omitted, the session resolves the provider default: `gpt-5.5` for CAPI sessions and the active session model for BYOK sessions. + /// Optional source for permission-mode 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. - public async Task SetAllowAllAsync(PermissionsAllowAllMode? mode = null, bool? enabled = null, string? model = null, PermissionsSetAllowAllSource? source = null, CancellationToken cancellationToken = default) + /// Indicates whether the requested permission mode was applied and reports the authoritative post-mutation mode. + public async Task SetModeAsync(PermissionMode mode, string? assistedApprovalModel = null, PermissionModeSource? source = null, CancellationToken cancellationToken = default) { _session.ThrowIfDisposed(); - var request = new PermissionsSetAllowAllRequest { SessionId = _session.SessionId, Mode = mode, Enabled = enabled, Model = model, Source = source }; - return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.permissions.setAllowAll", [request], cancellationToken); + var request = new PermissionsSetModeRequest { SessionId = _session.SessionId, Mode = mode, AssistedApprovalModel = assistedApprovalModel, Source = source }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.permissions.setMode", [request], cancellationToken); } - /// Returns the current allow-all permission mode for the session. + /// Returns the current permission mode for the session. /// The to monitor for cancellation requests. The default is . - /// Current allow-all permission mode. - public async Task GetAllowAllAsync(CancellationToken cancellationToken = default) + /// Current permission mode. + public async Task GetModeAsync(CancellationToken cancellationToken = default) { _session.ThrowIfDisposed(); - var request = new PermissionsGetAllowAllRequest { SessionId = _session.SessionId }; - return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.permissions.getAllowAll", [request], cancellationToken); + var request = new PermissionsGetModeRequest { SessionId = _session.SessionId }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.permissions.getMode", [request], cancellationToken); } /// Adds or removes session-scoped or location-scoped permission rules. @@ -27167,8 +34537,8 @@ public async Task ListAsync(CancellationToken cancellationT return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.permissions.paths.list", [request], cancellationToken); } - /// Adds a directory to the session's allow-list. - /// Directory to add to the allow-list. The runtime resolves and validates the path before adding. + /// Adds a directory to the session's allow-list and activates conventional skill and agent definitions under it. + /// Directory to add to the allow-list. The runtime resolves and validates the path before adding, then loads conventional `.github/skills/` and `.github/agents/` definitions under it when their subsystem gates are enabled. Adding the directory is therefore also a trust decision for configuration stored there. /// The to monitor for cancellation requests. The default is . /// Indicates whether the operation succeeded. public async Task AddAsync(string path, CancellationToken cancellationToken = default) @@ -27756,7 +35126,7 @@ public async Task MoveItemAsync(string id, long toPosition, /// 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. + /// Queued message contents and delivery metadata. /// 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) @@ -27769,7 +35139,7 @@ public async Task InsertAtAsync(long position, QueueInsertM } /// Removes an addressable queued item by its stable id. - /// The id parameter. + /// Stable opaque ID of the queued item to remove. /// The to monitor for cancellation requests. The default is . /// Result of removing a queued item. public async Task RemoveAtAsync(string id, CancellationToken cancellationToken = default) @@ -27782,9 +35152,9 @@ public async Task RemoveAtAsync(string id, CancellationToke } /// Updates the text of an addressable single-message queue item. - /// The id parameter. - /// The prompt parameter. - /// The displayPrompt parameter. + /// Stable opaque ID of the queued item to edit. + /// Replacement prompt sent to the model. + /// Optional replacement prompt displayed to the user. /// 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) @@ -27798,7 +35168,7 @@ public async Task UpdateTextAsync(string id, string promp } /// Duplicates an addressable queued item immediately after its source. - /// The id parameter. + /// Stable opaque ID of the queued item to duplicate. /// The to monitor for cancellation requests. The default is . /// Result of duplicating a queued item. public async Task DuplicateAtAsync(string id, CancellationToken cancellationToken = default) @@ -27811,7 +35181,7 @@ public async Task DuplicateAtAsync(string id, Cancellati } /// Acquires or releases the queued-lane drain pause. - /// The paused parameter. + /// Whether queued-lane draining should be paused. /// The to monitor for cancellation requests. The default is . public async Task SetDrainPausedAsync(bool paused, CancellationToken cancellationToken = default) { @@ -27822,7 +35192,7 @@ public async Task SetDrainPausedAsync(bool paused, CancellationToken cancellatio } /// Moves an addressable queued message into the live turn's steering lane. - /// The id parameter. + /// Stable opaque ID of the queued item to steer into the live turn. /// 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) @@ -28528,6 +35898,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 @@ -28554,14 +35935,31 @@ public interface IGitHubTelemetryHandler Task EventAsync(GitHubTelemetryNotification request, CancellationToken cancellationToken = default); } +/// Handles `gitHubToken` client global API methods. +[Experimental(Diagnostics.Experimental)] +public interface IGitHubTokenHandler +{ + /// Asks the SDK client to mint a GitHub access token for a session whose configuration supplied a GitHub token provider. The runtime acquires the initial token during bootstrap and refreshes it during expiry preflight when one hour or less remains. + /// Asks the SDK client to acquire a GitHub access token from an opaque callback registration. + /// The to monitor for cancellation requests. The default is . + /// SDK host response to a GitHub credential request. + Task GetTokenAsync(GitHubTokenAcquireRequest request, CancellationToken cancellationToken = default); +} + /// 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; } /// Optional handler for GitHubTelemetry client global API methods. public IGitHubTelemetryHandler? GitHubTelemetry { get; set; } + + /// Optional handler for GitHubToken client global API methods. + public IGitHubTokenHandler? GitHubToken { get; set; } } /// Registers client global API handlers on a JSON-RPC connection. @@ -28575,6 +35973,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"); @@ -28590,6 +35993,11 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH var handler = handlers.GitHubTelemetry ?? throw new InvalidOperationException("No gitHubTelemetry client-global handler registered"); await handler.EventAsync(request, cancellationToken); }), singleObjectParam: true); + rpc.SetLocalRpcMethod("gitHubToken.getToken", (Func>)(async (request, cancellationToken) => + { + var handler = handlers.GitHubToken ?? throw new InvalidOperationException("No gitHubToken client-global handler registered"); + return await handler.GetTokenAsync(request, cancellationToken); + }), singleObjectParam: true); } } @@ -28605,6 +36013,15 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.AbortData), TypeInfoPropertyName = "SessionEventsAbortData")] [JsonSerializable(typeof(GitHub.Copilot.AbortEvent), TypeInfoPropertyName = "SessionEventsAbortEvent")] [JsonSerializable(typeof(GitHub.Copilot.AbortReason), TypeInfoPropertyName = "SessionEventsAbortReason")] +[JsonSerializable(typeof(GitHub.Copilot.AgentInterruptedActivity), TypeInfoPropertyName = "SessionEventsAgentInterruptedActivity")] +[JsonSerializable(typeof(GitHub.Copilot.AgentInterruptedCancelPhase), TypeInfoPropertyName = "SessionEventsAgentInterruptedCancelPhase")] +[JsonSerializable(typeof(GitHub.Copilot.AgentInterruptedData), TypeInfoPropertyName = "SessionEventsAgentInterruptedData")] +[JsonSerializable(typeof(GitHub.Copilot.AgentInterruptedEvent), TypeInfoPropertyName = "SessionEventsAgentInterruptedEvent")] +[JsonSerializable(typeof(GitHub.Copilot.AgentModelPolicy), TypeInfoPropertyName = "SessionEventsAgentModelPolicy")] +[JsonSerializable(typeof(GitHub.Copilot.AssistantFusionPhaseActivityEvent), TypeInfoPropertyName = "SessionEventsAssistantFusionPhaseActivityEvent")] +[JsonSerializable(typeof(GitHub.Copilot.AssistantFusionPhaseCompletedEvent), TypeInfoPropertyName = "SessionEventsAssistantFusionPhaseCompletedEvent")] +[JsonSerializable(typeof(GitHub.Copilot.AssistantFusionPhaseFailedEvent), TypeInfoPropertyName = "SessionEventsAssistantFusionPhaseFailedEvent")] +[JsonSerializable(typeof(GitHub.Copilot.AssistantFusionPhaseStartedEvent), TypeInfoPropertyName = "SessionEventsAssistantFusionPhaseStartedEvent")] [JsonSerializable(typeof(GitHub.Copilot.AssistantIdleData), TypeInfoPropertyName = "SessionEventsAssistantIdleData")] [JsonSerializable(typeof(GitHub.Copilot.AssistantIdleEvent), TypeInfoPropertyName = "SessionEventsAssistantIdleEvent")] [JsonSerializable(typeof(GitHub.Copilot.AssistantIntentData), TypeInfoPropertyName = "SessionEventsAssistantIntentData")] @@ -28613,10 +36030,13 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.AssistantMessageDeltaData), TypeInfoPropertyName = "SessionEventsAssistantMessageDeltaData")] [JsonSerializable(typeof(GitHub.Copilot.AssistantMessageDeltaEvent), TypeInfoPropertyName = "SessionEventsAssistantMessageDeltaEvent")] [JsonSerializable(typeof(GitHub.Copilot.AssistantMessageEvent), TypeInfoPropertyName = "SessionEventsAssistantMessageEvent")] +[JsonSerializable(typeof(GitHub.Copilot.AssistantMessageReasoningBlocks), TypeInfoPropertyName = "SessionEventsAssistantMessageReasoningBlocks")] [JsonSerializable(typeof(GitHub.Copilot.AssistantMessageServerTools), TypeInfoPropertyName = "SessionEventsAssistantMessageServerTools")] [JsonSerializable(typeof(GitHub.Copilot.AssistantMessageStartData), TypeInfoPropertyName = "SessionEventsAssistantMessageStartData")] [JsonSerializable(typeof(GitHub.Copilot.AssistantMessageStartEvent), TypeInfoPropertyName = "SessionEventsAssistantMessageStartEvent")] [JsonSerializable(typeof(GitHub.Copilot.AssistantMessageToolRequest), TypeInfoPropertyName = "SessionEventsAssistantMessageToolRequest")] +[JsonSerializable(typeof(GitHub.Copilot.AssistantMessageToolRequestCaller), TypeInfoPropertyName = "SessionEventsAssistantMessageToolRequestCaller")] +[JsonSerializable(typeof(GitHub.Copilot.AssistantMessageToolRequestCallerType), TypeInfoPropertyName = "SessionEventsAssistantMessageToolRequestCallerType")] [JsonSerializable(typeof(GitHub.Copilot.AssistantMessageToolRequestType), TypeInfoPropertyName = "SessionEventsAssistantMessageToolRequestType")] [JsonSerializable(typeof(GitHub.Copilot.AssistantReasoningData), TypeInfoPropertyName = "SessionEventsAssistantReasoningData")] [JsonSerializable(typeof(GitHub.Copilot.AssistantReasoningDeltaData), TypeInfoPropertyName = "SessionEventsAssistantReasoningDeltaData")] @@ -28639,6 +36059,9 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.AssistantUsageCopilotUsageTokenDetail), TypeInfoPropertyName = "SessionEventsAssistantUsageCopilotUsageTokenDetail")] [JsonSerializable(typeof(GitHub.Copilot.AssistantUsageData), TypeInfoPropertyName = "SessionEventsAssistantUsageData")] [JsonSerializable(typeof(GitHub.Copilot.AssistantUsageEvent), TypeInfoPropertyName = "SessionEventsAssistantUsageEvent")] +[JsonSerializable(typeof(GitHub.Copilot.AssistantUsageTransport), TypeInfoPropertyName = "SessionEventsAssistantUsageTransport")] +[JsonSerializable(typeof(GitHub.Copilot.AssistedApprovalJudgeFailureReason), TypeInfoPropertyName = "SessionEventsAssistedApprovalJudgeFailureReason")] +[JsonSerializable(typeof(GitHub.Copilot.AssistedApprovalRecommendation), TypeInfoPropertyName = "SessionEventsAssistedApprovalRecommendation")] [JsonSerializable(typeof(GitHub.Copilot.Attachment), TypeInfoPropertyName = "SessionEventsAttachment")] [JsonSerializable(typeof(GitHub.Copilot.AttachmentBlob), TypeInfoPropertyName = "SessionEventsAttachmentBlob")] [JsonSerializable(typeof(GitHub.Copilot.AttachmentDirectory), TypeInfoPropertyName = "SessionEventsAttachmentDirectory")] @@ -28662,14 +36085,13 @@ 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")] [JsonSerializable(typeof(GitHub.Copilot.AutoModeSwitchCompletedEvent), TypeInfoPropertyName = "SessionEventsAutoModeSwitchCompletedEvent")] [JsonSerializable(typeof(GitHub.Copilot.AutoModeSwitchRequestedData), TypeInfoPropertyName = "SessionEventsAutoModeSwitchRequestedData")] [JsonSerializable(typeof(GitHub.Copilot.AutoModeSwitchRequestedEvent), TypeInfoPropertyName = "SessionEventsAutoModeSwitchRequestedEvent")] [JsonSerializable(typeof(GitHub.Copilot.AutoModeSwitchResponse), TypeInfoPropertyName = "SessionEventsAutoModeSwitchResponse")] +[JsonSerializable(typeof(GitHub.Copilot.AutoTier), TypeInfoPropertyName = "SessionEventsAutoTier")] [JsonSerializable(typeof(GitHub.Copilot.AutopilotObjectiveChangedOperation), TypeInfoPropertyName = "SessionEventsAutopilotObjectiveChangedOperation")] [JsonSerializable(typeof(GitHub.Copilot.AutopilotObjectiveChangedStatus), TypeInfoPropertyName = "SessionEventsAutopilotObjectiveChangedStatus")] [JsonSerializable(typeof(GitHub.Copilot.BinaryAssetReference), TypeInfoPropertyName = "SessionEventsBinaryAssetReference")] @@ -28702,6 +36124,10 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [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.CompletionReceiptEventRange), TypeInfoPropertyName = "SessionEventsCompletionReceiptEventRange")] +[JsonSerializable(typeof(GitHub.Copilot.CompletionReceiptFinalTool), TypeInfoPropertyName = "SessionEventsCompletionReceiptFinalTool")] +[JsonSerializable(typeof(GitHub.Copilot.CompletionReceiptStopReason), TypeInfoPropertyName = "SessionEventsCompletionReceiptStopReason")] +[JsonSerializable(typeof(GitHub.Copilot.CompletionReceiptToolStatus), TypeInfoPropertyName = "SessionEventsCompletionReceiptToolStatus")] [JsonSerializable(typeof(GitHub.Copilot.ContextTier), TypeInfoPropertyName = "SessionEventsContextTier")] [JsonSerializable(typeof(GitHub.Copilot.CustomAgentsUpdatedAgent), TypeInfoPropertyName = "SessionEventsCustomAgentsUpdatedAgent")] [JsonSerializable(typeof(GitHub.Copilot.ElicitationCompletedAction), TypeInfoPropertyName = "SessionEventsElicitationCompletedAction")] @@ -28727,8 +36153,26 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [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.FactoryRunSettledData), TypeInfoPropertyName = "SessionEventsFactoryRunSettledData")] +[JsonSerializable(typeof(GitHub.Copilot.FactoryRunSettledEvent), TypeInfoPropertyName = "SessionEventsFactoryRunSettledEvent")] +[JsonSerializable(typeof(GitHub.Copilot.FactoryRunSettledStatus), TypeInfoPropertyName = "SessionEventsFactoryRunSettledStatus")] +[JsonSerializable(typeof(GitHub.Copilot.FactoryRunStartedData), TypeInfoPropertyName = "SessionEventsFactoryRunStartedData")] +[JsonSerializable(typeof(GitHub.Copilot.FactoryRunStartedEvent), TypeInfoPropertyName = "SessionEventsFactoryRunStartedEvent")] [JsonSerializable(typeof(GitHub.Copilot.FactoryRunUpdatedData), TypeInfoPropertyName = "SessionEventsFactoryRunUpdatedData")] [JsonSerializable(typeof(GitHub.Copilot.FactoryRunUpdatedEvent), TypeInfoPropertyName = "SessionEventsFactoryRunUpdatedEvent")] +[JsonSerializable(typeof(GitHub.Copilot.FusionAttribution), TypeInfoPropertyName = "SessionEventsFusionAttribution")] +[JsonSerializable(typeof(GitHub.Copilot.FusionConversationScope), TypeInfoPropertyName = "SessionEventsFusionConversationScope")] +[JsonSerializable(typeof(GitHub.Copilot.FusionFollowUpAction), TypeInfoPropertyName = "SessionEventsFusionFollowUpAction")] +[JsonSerializable(typeof(GitHub.Copilot.FusionFollowUpRecommendation), TypeInfoPropertyName = "SessionEventsFusionFollowUpRecommendation")] +[JsonSerializable(typeof(GitHub.Copilot.FusionPattern), TypeInfoPropertyName = "SessionEventsFusionPattern")] +[JsonSerializable(typeof(GitHub.Copilot.FusionPhaseActivityKind), TypeInfoPropertyName = "SessionEventsFusionPhaseActivityKind")] +[JsonSerializable(typeof(GitHub.Copilot.FusionPhaseKind), TypeInfoPropertyName = "SessionEventsFusionPhaseKind")] +[JsonSerializable(typeof(GitHub.Copilot.FusionPhasePlanStep), TypeInfoPropertyName = "SessionEventsFusionPhasePlanStep")] +[JsonSerializable(typeof(GitHub.Copilot.FusionPhaseStatus), TypeInfoPropertyName = "SessionEventsFusionPhaseStatus")] +[JsonSerializable(typeof(GitHub.Copilot.FusionPhaseUsage), TypeInfoPropertyName = "SessionEventsFusionPhaseUsage")] +[JsonSerializable(typeof(GitHub.Copilot.FusionProjectionMode), TypeInfoPropertyName = "SessionEventsFusionProjectionMode")] +[JsonSerializable(typeof(GitHub.Copilot.FusionScores), TypeInfoPropertyName = "SessionEventsFusionScores")] +[JsonSerializable(typeof(GitHub.Copilot.FusionTurnKind), TypeInfoPropertyName = "SessionEventsFusionTurnKind")] [JsonSerializable(typeof(GitHub.Copilot.GitHubMcpToolConfig), TypeInfoPropertyName = "SessionEventsGitHubMcpToolConfig")] [JsonSerializable(typeof(GitHub.Copilot.GitHubRepoRef), TypeInfoPropertyName = "SessionEventsGitHubRepoRef")] [JsonSerializable(typeof(GitHub.Copilot.HandoffRepository), TypeInfoPropertyName = "SessionEventsHandoffRepository")] @@ -28778,20 +36222,25 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.ModelCallFailureRequestFingerprint), TypeInfoPropertyName = "SessionEventsModelCallFailureRequestFingerprint")] [JsonSerializable(typeof(GitHub.Copilot.ModelCallFailureSource), TypeInfoPropertyName = "SessionEventsModelCallFailureSource")] [JsonSerializable(typeof(GitHub.Copilot.ModelCallFailureTransport), TypeInfoPropertyName = "SessionEventsModelCallFailureTransport")] +[JsonSerializable(typeof(GitHub.Copilot.ModelCallFinishedData), TypeInfoPropertyName = "SessionEventsModelCallFinishedData")] +[JsonSerializable(typeof(GitHub.Copilot.ModelCallFinishedEvent), TypeInfoPropertyName = "SessionEventsModelCallFinishedEvent")] +[JsonSerializable(typeof(GitHub.Copilot.ModelCallFinishedOutcome), TypeInfoPropertyName = "SessionEventsModelCallFinishedOutcome")] [JsonSerializable(typeof(GitHub.Copilot.ModelCallStartData), TypeInfoPropertyName = "SessionEventsModelCallStartData")] [JsonSerializable(typeof(GitHub.Copilot.ModelCallStartEvent), TypeInfoPropertyName = "SessionEventsModelCallStartEvent")] +[JsonSerializable(typeof(GitHub.Copilot.ModelChangeSource), TypeInfoPropertyName = "SessionEventsModelChangeSource")] [JsonSerializable(typeof(GitHub.Copilot.OmittedBinaryOmittedReason), TypeInfoPropertyName = "SessionEventsOmittedBinaryOmittedReason")] [JsonSerializable(typeof(GitHub.Copilot.OmittedBinaryResult), TypeInfoPropertyName = "SessionEventsOmittedBinaryResult")] [JsonSerializable(typeof(GitHub.Copilot.OmittedBinaryType), TypeInfoPropertyName = "SessionEventsOmittedBinaryType")] [JsonSerializable(typeof(GitHub.Copilot.PendingMessagesModifiedData), TypeInfoPropertyName = "SessionEventsPendingMessagesModifiedData")] [JsonSerializable(typeof(GitHub.Copilot.PendingMessagesModifiedEvent), TypeInfoPropertyName = "SessionEventsPendingMessagesModifiedEvent")] -[JsonSerializable(typeof(GitHub.Copilot.PermissionAllowAllMode), TypeInfoPropertyName = "SessionEventsPermissionAllowAllMode")] -[JsonSerializable(typeof(GitHub.Copilot.PermissionAutoApproval), TypeInfoPropertyName = "SessionEventsPermissionAutoApproval")] +[JsonSerializable(typeof(GitHub.Copilot.PermissionAssistedApproval), TypeInfoPropertyName = "SessionEventsPermissionAssistedApproval")] [JsonSerializable(typeof(GitHub.Copilot.PermissionCompletedData), TypeInfoPropertyName = "SessionEventsPermissionCompletedData")] [JsonSerializable(typeof(GitHub.Copilot.PermissionCompletedEvent), TypeInfoPropertyName = "SessionEventsPermissionCompletedEvent")] +[JsonSerializable(typeof(GitHub.Copilot.PermissionMode), TypeInfoPropertyName = "SessionEventsPermissionMode")] [JsonSerializable(typeof(GitHub.Copilot.PermissionPromptRequest), TypeInfoPropertyName = "SessionEventsPermissionPromptRequest")] [JsonSerializable(typeof(GitHub.Copilot.PermissionPromptRequestCommands), TypeInfoPropertyName = "SessionEventsPermissionPromptRequestCommands")] [JsonSerializable(typeof(GitHub.Copilot.PermissionPromptRequestCustomTool), TypeInfoPropertyName = "SessionEventsPermissionPromptRequestCustomTool")] +[JsonSerializable(typeof(GitHub.Copilot.PermissionPromptRequestExtensionEnvAccess), TypeInfoPropertyName = "SessionEventsPermissionPromptRequestExtensionEnvAccess")] [JsonSerializable(typeof(GitHub.Copilot.PermissionPromptRequestExtensionManagement), TypeInfoPropertyName = "SessionEventsPermissionPromptRequestExtensionManagement")] [JsonSerializable(typeof(GitHub.Copilot.PermissionPromptRequestExtensionPermissionAccess), TypeInfoPropertyName = "SessionEventsPermissionPromptRequestExtensionPermissionAccess")] [JsonSerializable(typeof(GitHub.Copilot.PermissionPromptRequestFactory), TypeInfoPropertyName = "SessionEventsPermissionPromptRequestFactory")] @@ -28803,8 +36252,10 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.PermissionPromptRequestRead), TypeInfoPropertyName = "SessionEventsPermissionPromptRequestRead")] [JsonSerializable(typeof(GitHub.Copilot.PermissionPromptRequestUrl), TypeInfoPropertyName = "SessionEventsPermissionPromptRequestUrl")] [JsonSerializable(typeof(GitHub.Copilot.PermissionPromptRequestWrite), TypeInfoPropertyName = "SessionEventsPermissionPromptRequestWrite")] +[JsonSerializable(typeof(GitHub.Copilot.PermissionRecommendation), TypeInfoPropertyName = "SessionEventsPermissionRecommendation")] [JsonSerializable(typeof(GitHub.Copilot.PermissionRequest), TypeInfoPropertyName = "SessionEventsPermissionRequest")] [JsonSerializable(typeof(GitHub.Copilot.PermissionRequestCustomTool), TypeInfoPropertyName = "SessionEventsPermissionRequestCustomTool")] +[JsonSerializable(typeof(GitHub.Copilot.PermissionRequestExtensionEnvAccess), TypeInfoPropertyName = "SessionEventsPermissionRequestExtensionEnvAccess")] [JsonSerializable(typeof(GitHub.Copilot.PermissionRequestExtensionManagement), TypeInfoPropertyName = "SessionEventsPermissionRequestExtensionManagement")] [JsonSerializable(typeof(GitHub.Copilot.PermissionRequestExtensionPermissionAccess), TypeInfoPropertyName = "SessionEventsPermissionRequestExtensionPermissionAccess")] [JsonSerializable(typeof(GitHub.Copilot.PermissionRequestFactory), TypeInfoPropertyName = "SessionEventsPermissionRequestFactory")] @@ -28813,6 +36264,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.PermissionRequestMemory), TypeInfoPropertyName = "SessionEventsPermissionRequestMemory")] [JsonSerializable(typeof(GitHub.Copilot.PermissionRequestMemoryAction), TypeInfoPropertyName = "SessionEventsPermissionRequestMemoryAction")] [JsonSerializable(typeof(GitHub.Copilot.PermissionRequestMemoryDirection), TypeInfoPropertyName = "SessionEventsPermissionRequestMemoryDirection")] +[JsonSerializable(typeof(GitHub.Copilot.PermissionRequestMemoryScope), TypeInfoPropertyName = "SessionEventsPermissionRequestMemoryScope")] [JsonSerializable(typeof(GitHub.Copilot.PermissionRequestRead), TypeInfoPropertyName = "SessionEventsPermissionRequestRead")] [JsonSerializable(typeof(GitHub.Copilot.PermissionRequestShell), TypeInfoPropertyName = "SessionEventsPermissionRequestShell")] [JsonSerializable(typeof(GitHub.Copilot.PermissionRequestShellCommand), TypeInfoPropertyName = "SessionEventsPermissionRequestShellCommand")] @@ -28828,11 +36280,15 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.PersistedBinaryImageType), TypeInfoPropertyName = "SessionEventsPersistedBinaryImageType")] [JsonSerializable(typeof(GitHub.Copilot.PersistedBinaryResult), TypeInfoPropertyName = "SessionEventsPersistedBinaryResult")] [JsonSerializable(typeof(GitHub.Copilot.PlanChangedOperation), TypeInfoPropertyName = "SessionEventsPlanChangedOperation")] +[JsonSerializable(typeof(GitHub.Copilot.PromptCacheBreakData), TypeInfoPropertyName = "SessionEventsPromptCacheBreakData")] +[JsonSerializable(typeof(GitHub.Copilot.PromptCacheBreakEvent), TypeInfoPropertyName = "SessionEventsPromptCacheBreakEvent")] [JsonSerializable(typeof(GitHub.Copilot.ReasoningSummary), TypeInfoPropertyName = "SessionEventsReasoningSummary")] [JsonSerializable(typeof(GitHub.Copilot.SamplingCompletedData), TypeInfoPropertyName = "SessionEventsSamplingCompletedData")] [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.SandboxDecisionData), TypeInfoPropertyName = "SessionEventsSandboxDecisionData")] +[JsonSerializable(typeof(GitHub.Copilot.SandboxDecisionEvent), TypeInfoPropertyName = "SessionEventsSandboxDecisionEvent")] [JsonSerializable(typeof(GitHub.Copilot.ScheduleOrigin), TypeInfoPropertyName = "SessionEventsScheduleOrigin")] [JsonSerializable(typeof(GitHub.Copilot.SessionEvent), TypeInfoPropertyName = "SessionEventsSessionEvent")] [JsonSerializable(typeof(GitHub.Copilot.SessionLimitsConfig), TypeInfoPropertyName = "SessionEventsSessionLimitsConfig")] @@ -28843,6 +36299,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.SessionLimitsExhaustedResponse), TypeInfoPropertyName = "SessionEventsSessionLimitsExhaustedResponse")] [JsonSerializable(typeof(GitHub.Copilot.SessionLimitsExhaustedResponseAction), TypeInfoPropertyName = "SessionEventsSessionLimitsExhaustedResponseAction")] [JsonSerializable(typeof(GitHub.Copilot.SessionMode), TypeInfoPropertyName = "SessionEventsSessionMode")] +[JsonSerializable(typeof(GitHub.Copilot.ShutdownAgentMetric), TypeInfoPropertyName = "SessionEventsShutdownAgentMetric")] [JsonSerializable(typeof(GitHub.Copilot.ShutdownCodeChanges), TypeInfoPropertyName = "SessionEventsShutdownCodeChanges")] [JsonSerializable(typeof(GitHub.Copilot.ShutdownModelMetric), TypeInfoPropertyName = "SessionEventsShutdownModelMetric")] [JsonSerializable(typeof(GitHub.Copilot.ShutdownModelMetricRequests), TypeInfoPropertyName = "SessionEventsShutdownModelMetricRequests")] @@ -28857,6 +36314,8 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.SkillsLoadedSkill), TypeInfoPropertyName = "SessionEventsSkillsLoadedSkill")] [JsonSerializable(typeof(GitHub.Copilot.SubagentCompletedData), TypeInfoPropertyName = "SessionEventsSubagentCompletedData")] [JsonSerializable(typeof(GitHub.Copilot.SubagentCompletedEvent), TypeInfoPropertyName = "SessionEventsSubagentCompletedEvent")] +[JsonSerializable(typeof(GitHub.Copilot.SubagentConfiguredData), TypeInfoPropertyName = "SessionEventsSubagentConfiguredData")] +[JsonSerializable(typeof(GitHub.Copilot.SubagentConfiguredEvent), TypeInfoPropertyName = "SessionEventsSubagentConfiguredEvent")] [JsonSerializable(typeof(GitHub.Copilot.SubagentDeselectedData), TypeInfoPropertyName = "SessionEventsSubagentDeselectedData")] [JsonSerializable(typeof(GitHub.Copilot.SubagentDeselectedEvent), TypeInfoPropertyName = "SessionEventsSubagentDeselectedEvent")] [JsonSerializable(typeof(GitHub.Copilot.SubagentFailedData), TypeInfoPropertyName = "SessionEventsSubagentFailedData")] @@ -28925,6 +36384,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.ToolSearchActivatedEvent), TypeInfoPropertyName = "SessionEventsToolSearchActivatedEvent")] [JsonSerializable(typeof(GitHub.Copilot.ToolUserRequestedData), TypeInfoPropertyName = "SessionEventsToolUserRequestedData")] [JsonSerializable(typeof(GitHub.Copilot.ToolUserRequestedEvent), TypeInfoPropertyName = "SessionEventsToolUserRequestedEvent")] +[JsonSerializable(typeof(GitHub.Copilot.UIEphemeralQueryPhase), TypeInfoPropertyName = "SessionEventsUIEphemeralQueryPhase")] [JsonSerializable(typeof(GitHub.Copilot.UserInputCompletedData), TypeInfoPropertyName = "SessionEventsUserInputCompletedData")] [JsonSerializable(typeof(GitHub.Copilot.UserInputCompletedEvent), TypeInfoPropertyName = "SessionEventsUserInputCompletedEvent")] [JsonSerializable(typeof(GitHub.Copilot.UserInputRequestedData), TypeInfoPropertyName = "SessionEventsUserInputRequestedData")] @@ -28936,6 +36396,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.UserToolSessionApproval), TypeInfoPropertyName = "SessionEventsUserToolSessionApproval")] [JsonSerializable(typeof(GitHub.Copilot.UserToolSessionApprovalCommands), TypeInfoPropertyName = "SessionEventsUserToolSessionApprovalCommands")] [JsonSerializable(typeof(GitHub.Copilot.UserToolSessionApprovalCustomTool), TypeInfoPropertyName = "SessionEventsUserToolSessionApprovalCustomTool")] +[JsonSerializable(typeof(GitHub.Copilot.UserToolSessionApprovalExtensionEnvAccess), TypeInfoPropertyName = "SessionEventsUserToolSessionApprovalExtensionEnvAccess")] [JsonSerializable(typeof(GitHub.Copilot.UserToolSessionApprovalExtensionManagement), TypeInfoPropertyName = "SessionEventsUserToolSessionApprovalExtensionManagement")] [JsonSerializable(typeof(GitHub.Copilot.UserToolSessionApprovalExtensionPermissionAccess), TypeInfoPropertyName = "SessionEventsUserToolSessionApprovalExtensionPermissionAccess")] [JsonSerializable(typeof(GitHub.Copilot.UserToolSessionApprovalFactory), TypeInfoPropertyName = "SessionEventsUserToolSessionApprovalFactory")] @@ -28973,11 +36434,14 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(AgentSetPromptRequest))] [JsonSerializable(typeof(AgentsDiscoverRequest))] [JsonSerializable(typeof(AgentsGetDiscoveryPathsRequest))] -[JsonSerializable(typeof(AllowAllPermissionSetResult))] -[JsonSerializable(typeof(AllowAllPermissionState))] +[JsonSerializable(typeof(AuthIdentity))] [JsonSerializable(typeof(AuthInfo))] +[JsonSerializable(typeof(AuthValidationError))] [JsonSerializable(typeof(BuiltInModelCatalog))] [JsonSerializable(typeof(BuiltInModelCatalogEntry))] +[JsonSerializable(typeof(BuiltinToolDescriptor))] +[JsonSerializable(typeof(BuiltinToolFormat))] +[JsonSerializable(typeof(BuiltinToolInputSchema))] [JsonSerializable(typeof(CancelUserRequestedShellCommandResult))] [JsonSerializable(typeof(CanvasAction))] [JsonSerializable(typeof(CanvasActionInvokeRequest))] @@ -28992,9 +36456,23 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(CanvasProviderInvokeActionRequest))] [JsonSerializable(typeof(CanvasProviderOpenRequest))] [JsonSerializable(typeof(CanvasProviderOpenResult))] +[JsonSerializable(typeof(CanvasProviderRegisterRequest))] +[JsonSerializable(typeof(CanvasProviderUnregisterRequest))] [JsonSerializable(typeof(CanvasSessionContext))] [JsonSerializable(typeof(CapiSessionOptions))] +[JsonSerializable(typeof(CardDigest))] +[JsonSerializable(typeof(CatalogAiSkillCandidateProvenance))] +[JsonSerializable(typeof(CatalogCandidate))] +[JsonSerializable(typeof(CatalogCandidateSource))] +[JsonSerializable(typeof(CatalogClientContract))] +[JsonSerializable(typeof(CatalogMcpServerCandidateProvenance))] +[JsonSerializable(typeof(CatalogNegotiatedContract))] +[JsonSerializable(typeof(CatalogSearchRequest))] +[JsonSerializable(typeof(CatalogSearchResult))] [JsonSerializable(typeof(CommandList))] +[JsonSerializable(typeof(CommandsFinalizeInvocationEffectRequest))] +[JsonSerializable(typeof(CommandsFinalizeInvocationEffectRequestEffect))] +[JsonSerializable(typeof(CommandsFinalizeInvocationEffectResult))] [JsonSerializable(typeof(CommandsHandlePendingCommandRequest))] [JsonSerializable(typeof(CommandsHandlePendingCommandResult))] [JsonSerializable(typeof(CommandsInvokeRequest))] @@ -29004,6 +36482,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(CompletionsRequestRequest))] [JsonSerializable(typeof(CompletionsRequestResult))] [JsonSerializable(typeof(ConfigureSessionExtensionsParams))] +[JsonSerializable(typeof(ConnectClientInfo))] [JsonSerializable(typeof(ConnectRemoteSessionParams))] [JsonSerializable(typeof(ConnectRequest))] [JsonSerializable(typeof(ConnectResult))] @@ -29035,6 +36514,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(DiscoveredExtensions))] [JsonSerializable(typeof(DiscoveredExtensionsDisableRequest))] [JsonSerializable(typeof(DiscoveredExtensionsEnableRequest))] +[JsonSerializable(typeof(DiscoveredHook))] [JsonSerializable(typeof(DiscoveredMcpServer))] [JsonSerializable(typeof(EnqueueCommandParams))] [JsonSerializable(typeof(EnqueueCommandResult))] @@ -29045,9 +36525,15 @@ 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))] +[JsonSerializable(typeof(ExternalToolTextResultForLlmBinaryResultsForLlm))] +[JsonSerializable(typeof(ExternalToolTextResultForLlmContent))] +[JsonSerializable(typeof(ExternalToolTextResultForLlmContentResourceLinkIcon))] [JsonSerializable(typeof(FactoryAbortRequest))] [JsonSerializable(typeof(FactoryAckResult))] [JsonSerializable(typeof(FactoryAgentOptions))] @@ -29081,6 +36567,9 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(FactoryRunResult))] [JsonSerializable(typeof(FactoryRunSummary))] [JsonSerializable(typeof(FactoryRunTerminal))] +[JsonSerializable(typeof(FactoryToolResumeRequest))] +[JsonSerializable(typeof(FactoryToolRunOptions))] +[JsonSerializable(typeof(FactoryToolRunRequest))] [JsonSerializable(typeof(FleetStartRequest))] [JsonSerializable(typeof(FleetStartResult))] [JsonSerializable(typeof(FolderTrustAddParams))] @@ -29089,6 +36578,8 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHubTelemetryClientInfo))] [JsonSerializable(typeof(GitHubTelemetryEvent))] [JsonSerializable(typeof(GitHubTelemetryNotification))] +[JsonSerializable(typeof(GitHubTokenAcquireRequest))] +[JsonSerializable(typeof(GitHubTokenAcquireResult))] [JsonSerializable(typeof(HandlePendingToolCallRequest))] [JsonSerializable(typeof(HandlePendingToolCallResult))] [JsonSerializable(typeof(HistoryAbortManualCompactionResult))] @@ -29108,8 +36599,12 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(HistorySummarizeForHandoffResult))] [JsonSerializable(typeof(HistoryTruncateRequest))] [JsonSerializable(typeof(HistoryTruncateResult))] +[JsonSerializable(typeof(HooksDiscoverRequest))] +[JsonSerializable(typeof(HooksDiscoverResult))] [JsonSerializable(typeof(IDictionary))] [JsonSerializable(typeof(IList))] +[JsonSerializable(typeof(IList))] +[JsonSerializable(typeof(IList))] [JsonSerializable(typeof(InstalledPlugin))] [JsonSerializable(typeof(InstalledPluginInfo))] [JsonSerializable(typeof(InstructionDiscoveryPath))] @@ -29134,6 +36629,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))] @@ -29174,11 +36670,13 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(McpExecuteSamplingParams))] [JsonSerializable(typeof(McpExecuteSamplingRequest))] [JsonSerializable(typeof(McpExecuteSamplingResult))] +[JsonSerializable(typeof(McpFailedServer))] [JsonSerializable(typeof(McpFilteredServer))] [JsonSerializable(typeof(McpHeadersHandlePendingHeadersRefreshRequest))] [JsonSerializable(typeof(McpHeadersHandlePendingHeadersRefreshRequestRequest))] [JsonSerializable(typeof(McpHeadersHandlePendingHeadersRefreshRequestResult))] [JsonSerializable(typeof(McpHostState))] +[JsonSerializable(typeof(McpInstallPlan))] [JsonSerializable(typeof(McpIsServerRunningRequest))] [JsonSerializable(typeof(McpIsServerRunningResult))] [JsonSerializable(typeof(McpListToolsRequest))] @@ -29189,8 +36687,21 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(McpOauthLoginRequest))] [JsonSerializable(typeof(McpOauthLoginResult))] [JsonSerializable(typeof(McpOauthPendingRequestResponse))] +[JsonSerializable(typeof(McpOauthProbeRequest))] +[JsonSerializable(typeof(McpOauthProbeResult))] [JsonSerializable(typeof(McpOauthRespondRequest))] [JsonSerializable(typeof(McpOauthRespondResult))] +[JsonSerializable(typeof(McpPlanConfigurationChange))] +[JsonSerializable(typeof(McpPlanInstallRequest))] +[JsonSerializable(typeof(McpPlanInstallResult))] +[JsonSerializable(typeof(McpPlanInstallSource))] +[JsonSerializable(typeof(McpPlanPolicyResult))] +[JsonSerializable(typeof(McpPlanProvenance))] +[JsonSerializable(typeof(McpPlanRequiredValue))] +[JsonSerializable(typeof(McpPlanResourceIdentity))] +[JsonSerializable(typeof(McpPlanSecretPlaceholder))] +[JsonSerializable(typeof(McpPlanTarget))] +[JsonSerializable(typeof(McpPlanTransportChoice))] [JsonSerializable(typeof(McpRegisterExternalClientRequest))] [JsonSerializable(typeof(McpReloadWithConfigRequest))] [JsonSerializable(typeof(McpRemoveGitHubResult))] @@ -29208,6 +36719,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(McpRestartServerRequest))] [JsonSerializable(typeof(McpSamplingExecutionResult))] [JsonSerializable(typeof(McpServer))] +[JsonSerializable(typeof(McpServerCardReference))] [JsonSerializable(typeof(McpServerFailureInfo))] [JsonSerializable(typeof(McpServerList))] [JsonSerializable(typeof(McpServerNeedsAuthInfo))] @@ -29216,11 +36728,13 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(McpStartServerRequest))] [JsonSerializable(typeof(McpStartServersResult))] [JsonSerializable(typeof(McpStopServerRequest))] +[JsonSerializable(typeof(McpTaskMetadata))] [JsonSerializable(typeof(McpToolUi))] [JsonSerializable(typeof(McpTools))] [JsonSerializable(typeof(McpUnregisterExternalClientRequest))] [JsonSerializable(typeof(MetadataContextAttributionResult))] [JsonSerializable(typeof(MetadataContextAttributionResultContextAttribution))] +[JsonSerializable(typeof(MetadataContextAttributionResultContextAttributionCategories))] [JsonSerializable(typeof(MetadataContextAttributionResultContextAttributionCompactions))] [JsonSerializable(typeof(MetadataContextAttributionResultContextAttributionEntry))] [JsonSerializable(typeof(MetadataContextHeaviestMessagesRequest))] @@ -29238,7 +36752,9 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(MetadataSnapshotRemoteMetadata))] [JsonSerializable(typeof(MetadataSnapshotRemoteMetadataRepository))] [JsonSerializable(typeof(ModeSetRequest))] +[JsonSerializable(typeof(ModeSetResult))] [JsonSerializable(typeof(Model))] +[JsonSerializable(typeof(ModelApplyStartupOverlayRequest))] [JsonSerializable(typeof(ModelBilling))] [JsonSerializable(typeof(ModelBillingPromo))] [JsonSerializable(typeof(ModelBillingTokenPrices))] @@ -29252,12 +36768,19 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(ModelCapabilitiesOverrideSupports))] [JsonSerializable(typeof(ModelCapabilitiesSupports))] [JsonSerializable(typeof(ModelList))] +[JsonSerializable(typeof(ModelMessage))] +[JsonSerializable(typeof(ModelPickerPersistenceRequest))] +[JsonSerializable(typeof(ModelPickerSettingsContext))] +[JsonSerializable(typeof(ModelPickerSettingsContextEnvironment))] [JsonSerializable(typeof(ModelPolicy))] [JsonSerializable(typeof(ModelSetReasoningEffortRequest))] [JsonSerializable(typeof(ModelSetReasoningEffortResult))] +[JsonSerializable(typeof(ModelSwitchConfirmation))] [JsonSerializable(typeof(ModelSwitchToRequest))] [JsonSerializable(typeof(ModelSwitchToResult))] +[JsonSerializable(typeof(ModelWarningText))] [JsonSerializable(typeof(ModelsListRequest))] +[JsonSerializable(typeof(MoveMcpLoadingToBackgroundResult))] [JsonSerializable(typeof(NameGetResult))] [JsonSerializable(typeof(NameSetAutoRequest))] [JsonSerializable(typeof(NameSetAutoResult))] @@ -29272,6 +36795,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))] @@ -29297,7 +36821,8 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(PermissionsConfigureParams))] [JsonSerializable(typeof(PermissionsConfigureResult))] [JsonSerializable(typeof(PermissionsFolderTrustAddTrustedResult))] -[JsonSerializable(typeof(PermissionsGetAllowAllRequest))] +[JsonSerializable(typeof(PermissionsGetModeRequest))] +[JsonSerializable(typeof(PermissionsGetModeResult))] [JsonSerializable(typeof(PermissionsLocationsAddToolApprovalDetails))] [JsonSerializable(typeof(PermissionsLocationsAddToolApprovalResult))] [JsonSerializable(typeof(PermissionsModifyRulesParams))] @@ -29309,9 +36834,10 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(PermissionsPendingRequestsRequest))] [JsonSerializable(typeof(PermissionsResetSessionApprovalsRequest))] [JsonSerializable(typeof(PermissionsResetSessionApprovalsResult))] -[JsonSerializable(typeof(PermissionsSetAllowAllRequest))] [JsonSerializable(typeof(PermissionsSetApproveAllRequest))] [JsonSerializable(typeof(PermissionsSetApproveAllResult))] +[JsonSerializable(typeof(PermissionsSetModeRequest))] +[JsonSerializable(typeof(PermissionsSetModeResult))] [JsonSerializable(typeof(PermissionsSetRequiredRequest))] [JsonSerializable(typeof(PermissionsSetRequiredResult))] [JsonSerializable(typeof(PermissionsUrlsSetUnrestrictedModeResult))] @@ -29330,6 +36856,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(PluginUpdateAllEntry))] [JsonSerializable(typeof(PluginUpdateAllResult))] [JsonSerializable(typeof(PluginUpdateResult))] +[JsonSerializable(typeof(PluginsBuiltinSetRequest))] [JsonSerializable(typeof(PluginsDisableRequest))] [JsonSerializable(typeof(PluginsEnableRequest))] [JsonSerializable(typeof(PluginsInstallRequest))] @@ -29339,6 +36866,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(PluginsMarketplacesRemoveRequest))] [JsonSerializable(typeof(PluginsUninstallRequest))] [JsonSerializable(typeof(PluginsUpdateRequest))] +[JsonSerializable(typeof(ProtocolExternalToolDefinition))] [JsonSerializable(typeof(ProviderAddRequest))] [JsonSerializable(typeof(ProviderAddResult))] [JsonSerializable(typeof(ProviderConfig))] @@ -29403,6 +36931,7 @@ 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))] @@ -29410,6 +36939,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(SandboxConfigUserPolicyNetwork))] [JsonSerializable(typeof(SandboxConfigUserPolicyNetworkProxy))] [JsonSerializable(typeof(SandboxConfigUserPolicySeatbelt))] +[JsonSerializable(typeof(SandboxEnforcementStatus))] [JsonSerializable(typeof(ScheduleAddAtRequest))] [JsonSerializable(typeof(ScheduleAddCronRequest))] [JsonSerializable(typeof(ScheduleAddRequest))] @@ -29440,7 +36970,10 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(SessionAgentListRequest))] [JsonSerializable(typeof(SessionAgentListRequestWithSession))] [JsonSerializable(typeof(SessionAgentReloadRequest))] +[JsonSerializable(typeof(SessionAuthLoginRequest))] +[JsonSerializable(typeof(SessionAuthLogoutUserRequest))] [JsonSerializable(typeof(SessionAuthStatus))] +[JsonSerializable(typeof(SessionAuthSwitchRequest))] [JsonSerializable(typeof(SessionBulkDeleteResult))] [JsonSerializable(typeof(SessionCancelAllBackgroundAgentsRequest))] [JsonSerializable(typeof(SessionCanvasListOpenRequest))] @@ -29482,7 +37015,12 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(SessionFsStatRequest))] [JsonSerializable(typeof(SessionFsStatResult))] [JsonSerializable(typeof(SessionFsWriteFileRequest))] +[JsonSerializable(typeof(SessionGitHubAuthGetAllAuthAvailableRequest))] +[JsonSerializable(typeof(SessionGitHubAuthGetCurrentAuthInfoRequest))] [JsonSerializable(typeof(SessionGitHubAuthGetStatusRequest))] +[JsonSerializable(typeof(SessionGitHubAuthLastAuthErrorsRequest))] +[JsonSerializable(typeof(SessionGitHubAuthLogoutRequest))] +[JsonSerializable(typeof(SessionGitHubAuthRefreshCopilotUserRequest))] [JsonSerializable(typeof(SessionHistoryAbortManualCompactionRequest))] [JsonSerializable(typeof(SessionHistoryCancelBackgroundCompactionRequest))] [JsonSerializable(typeof(SessionHistoryCompactRequest))] @@ -29503,6 +37041,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(SessionLoadDeferredRepoHooksResult))] [JsonSerializable(typeof(SessionMcpAppsGetHostContextRequest))] [JsonSerializable(typeof(SessionMcpListRequest))] +[JsonSerializable(typeof(SessionMcpMoveLoadingToBackgroundRequest))] [JsonSerializable(typeof(SessionMcpReloadRequest))] [JsonSerializable(typeof(SessionMcpRemoveGitHubRequest))] [JsonSerializable(typeof(SessionMetadataActivityRequest))] @@ -29537,6 +37076,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(SessionQueueRemoveMostRecentRequest))] [JsonSerializable(typeof(SessionQueueSnapshotRequest))] [JsonSerializable(typeof(SessionRemoteDisableRequest))] +[JsonSerializable(typeof(SessionSandboxGetEnforcementStatusRequest))] [JsonSerializable(typeof(SessionScheduleHasSelfPacedRequest))] [JsonSerializable(typeof(SessionScheduleHydrateRequest))] [JsonSerializable(typeof(SessionScheduleListRequest))] @@ -29608,6 +37148,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(SessionsLoadDeferredRepoHooksRequest))] [JsonSerializable(typeof(SessionsOpenProgress))] [JsonSerializable(typeof(SessionsPruneOldRequest))] +[JsonSerializable(typeof(SessionsReadPersistedEventsRequest))] [JsonSerializable(typeof(SessionsRegisterExtensionToolsOnSessionOptions))] [JsonSerializable(typeof(SessionsReleaseLockRequest))] [JsonSerializable(typeof(SessionsReleaseLockResult))] @@ -29621,7 +37162,9 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(SessionsStartRemoteControlRequest))] [JsonSerializable(typeof(SessionsStopRemoteControlRequest))] [JsonSerializable(typeof(SessionsTransferRemoteControlRequest))] +[JsonSerializable(typeof(SettableAuthInfo))] [JsonSerializable(typeof(ShellCancelUserRequestedRequest))] +[JsonSerializable(typeof(ShellCredentials))] [JsonSerializable(typeof(ShellExecRequest))] [JsonSerializable(typeof(ShellExecResult))] [JsonSerializable(typeof(ShellExecuteUserRequestedRequest))] @@ -29635,6 +37178,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(SkillDiscoveryPathList))] [JsonSerializable(typeof(SkillList))] [JsonSerializable(typeof(SkillsConfigSetDisabledSkillsRequest))] +[JsonSerializable(typeof(SkillsConfigSetSkillDisabledRequest))] [JsonSerializable(typeof(SkillsDisableRequest))] [JsonSerializable(typeof(SkillsDiscoverRequest))] [JsonSerializable(typeof(SkillsEnableRequest))] @@ -29646,8 +37190,13 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(SlashCommandInput))] [JsonSerializable(typeof(SlashCommandInputChoice))] [JsonSerializable(typeof(SlashCommandInvocationResult))] +[JsonSerializable(typeof(SlashCommandInvocationResultSetModelRevertOnCancel))] +[JsonSerializable(typeof(SlashCommandModelPickerDialog))] [JsonSerializable(typeof(SlashCommandSelectSubcommandOption))] +[JsonSerializable(typeof(SlashCommandTimelineEntry))] [JsonSerializable(typeof(SubagentSettingsEntry))] +[JsonSerializable(typeof(TaskCompleteData))] +[JsonSerializable(typeof(TaskCompletionDecision))] [JsonSerializable(typeof(TaskInfo))] [JsonSerializable(typeof(TaskList))] [JsonSerializable(typeof(TaskProgressLine))] @@ -29671,12 +37220,23 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(TelemetrySetFeatureOverridesRequest))] [JsonSerializable(typeof(Tool))] [JsonSerializable(typeof(ToolList))] +[JsonSerializable(typeof(ToolResultExpanded))] +[JsonSerializable(typeof(ToolResultNewMessage))] +[JsonSerializable(typeof(ToolsExecuteRequest))] +[JsonSerializable(typeof(ToolsGetBuiltinDescriptorsRequest))] +[JsonSerializable(typeof(ToolsGetBuiltinDescriptorsResult))] [JsonSerializable(typeof(ToolsGetCurrentMetadataResult))] [JsonSerializable(typeof(ToolsInitializeAndValidateResult))] [JsonSerializable(typeof(ToolsListRequest))] +[JsonSerializable(typeof(ToolsSetRequest))] +[JsonSerializable(typeof(ToolsSetResult))] +[JsonSerializable(typeof(ToolsShellDescriptorConfig))] +[JsonSerializable(typeof(ToolsTaskCompleteEventDataRequest))] [JsonSerializable(typeof(ToolsUpdateSubagentSettingsResult))] [JsonSerializable(typeof(UIElicitationRequest))] +[JsonSerializable(typeof(UIElicitationRequestMeta))] [JsonSerializable(typeof(UIElicitationResponse))] +[JsonSerializable(typeof(UIElicitationResponseMeta))] [JsonSerializable(typeof(UIElicitationResult))] [JsonSerializable(typeof(UIElicitationSchema))] [JsonSerializable(typeof(UIEphemeralQueryRequest))] @@ -29698,6 +37258,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(UpdateSubagentSettingsRequest))] [JsonSerializable(typeof(UpdateSubagentSettingsRequestSubagents))] [JsonSerializable(typeof(UsageGetMetricsResult))] +[JsonSerializable(typeof(UsageMetricsAgentMetric))] [JsonSerializable(typeof(UsageMetricsCodeChanges))] [JsonSerializable(typeof(UsageMetricsModelMetric))] [JsonSerializable(typeof(UsageMetricsModelMetricRequests))] diff --git a/dotnet/src/Generated/SessionEvents.cs b/dotnet/src/Generated/SessionEvents.cs index 6b81a4770d..147e08e458 100644 --- a/dotnet/src/Generated/SessionEvents.cs +++ b/dotnet/src/Generated/SessionEvents.cs @@ -25,6 +25,11 @@ namespace GitHub.Copilot; TypeDiscriminatorPropertyName = "type", IgnoreUnrecognizedTypeDiscriminators = true)] [JsonDerivedType(typeof(AbortEvent), "abort")] +[JsonDerivedType(typeof(AgentInterruptedEvent), "agent.interrupted")] +[JsonDerivedType(typeof(AssistantFusionPhaseActivityEvent), "assistant.fusion_phase_activity")] +[JsonDerivedType(typeof(AssistantFusionPhaseCompletedEvent), "assistant.fusion_phase_completed")] +[JsonDerivedType(typeof(AssistantFusionPhaseFailedEvent), "assistant.fusion_phase_failed")] +[JsonDerivedType(typeof(AssistantFusionPhaseStartedEvent), "assistant.fusion_phase_started")] [JsonDerivedType(typeof(AssistantIdleEvent), "assistant.idle")] [JsonDerivedType(typeof(AssistantIntentEvent), "assistant.intent")] [JsonDerivedType(typeof(AssistantMessageEvent), "assistant.message")] @@ -52,6 +57,8 @@ namespace GitHub.Copilot; [JsonDerivedType(typeof(ExitPlanModeRequestedEvent), "exit_plan_mode.requested")] [JsonDerivedType(typeof(ExternalToolCompletedEvent), "external_tool.completed")] [JsonDerivedType(typeof(ExternalToolRequestedEvent), "external_tool.requested")] +[JsonDerivedType(typeof(FactoryRunSettledEvent), "factory.run_settled")] +[JsonDerivedType(typeof(FactoryRunStartedEvent), "factory.run_started")] [JsonDerivedType(typeof(FactoryRunUpdatedEvent), "factory.run_updated")] [JsonDerivedType(typeof(HookEndEvent), "hook.end")] [JsonDerivedType(typeof(HookProgressEvent), "hook.progress")] @@ -65,12 +72,15 @@ namespace GitHub.Copilot; [JsonDerivedType(typeof(McpResourcesListChangedEvent), "mcp.resources.list_changed")] [JsonDerivedType(typeof(McpToolsListChangedEvent), "mcp.tools.list_changed")] [JsonDerivedType(typeof(ModelCallFailureEvent), "model.call_failure")] +[JsonDerivedType(typeof(ModelCallFinishedEvent), "model.call_finished")] [JsonDerivedType(typeof(ModelCallStartEvent), "model.call_start")] [JsonDerivedType(typeof(PendingMessagesModifiedEvent), "pending_messages.modified")] [JsonDerivedType(typeof(PermissionCompletedEvent), "permission.completed")] [JsonDerivedType(typeof(PermissionRequestedEvent), "permission.requested")] +[JsonDerivedType(typeof(PromptCacheBreakEvent), "prompt_cache_break")] [JsonDerivedType(typeof(SamplingCompletedEvent), "sampling.completed")] [JsonDerivedType(typeof(SamplingRequestedEvent), "sampling.requested")] +[JsonDerivedType(typeof(SandboxDecisionEvent), "sandbox.decision")] [JsonDerivedType(typeof(SessionLimitsExhaustedCompletedEvent), "session_limits_exhausted.completed")] [JsonDerivedType(typeof(SessionLimitsExhaustedRequestedEvent), "session_limits_exhausted.requested")] [JsonDerivedType(typeof(SessionAutoModeResolvedEvent), "session.auto_mode_resolved")] @@ -85,6 +95,7 @@ namespace GitHub.Copilot; [JsonDerivedType(typeof(SessionCanvasUnavailableEvent), "session.canvas.unavailable")] [JsonDerivedType(typeof(SessionCompactionCompleteEvent), "session.compaction_complete")] [JsonDerivedType(typeof(SessionCompactionStartEvent), "session.compaction_start")] +[JsonDerivedType(typeof(SessionCompletionReceiptEvent), "session.completion_receipt")] [JsonDerivedType(typeof(SessionContextChangedEvent), "session.context_changed")] [JsonDerivedType(typeof(SessionContextClearedEvent), "session.context_cleared")] [JsonDerivedType(typeof(SessionCustomAgentsUpdatedEvent), "session.custom_agents_updated")] @@ -92,6 +103,10 @@ namespace GitHub.Copilot; [JsonDerivedType(typeof(SessionErrorEvent), "session.error")] [JsonDerivedType(typeof(SessionExtensionsLoadedEvent), "session.extensions_loaded")] [JsonDerivedType(typeof(SessionExtensionsAttachmentsPushedEvent), "session.extensions.attachments_pushed")] +[JsonDerivedType(typeof(SessionFusionCompletedEvent), "session.fusion_completed")] +[JsonDerivedType(typeof(SessionFusionResolvedEvent), "session.fusion_resolved")] +[JsonDerivedType(typeof(SessionFusionRouteFailedEvent), "session.fusion_route_failed")] +[JsonDerivedType(typeof(SessionFusionRouteStartedEvent), "session.fusion_route_started")] [JsonDerivedType(typeof(SessionHandoffEvent), "session.handoff")] [JsonDerivedType(typeof(SessionIdleEvent), "session.idle")] [JsonDerivedType(typeof(SessionInfoEvent), "session.info")] @@ -100,6 +115,7 @@ namespace GitHub.Copilot; [JsonDerivedType(typeof(SessionMcpServerStatusChangedEvent), "session.mcp_server_status_changed")] [JsonDerivedType(typeof(SessionMcpServersLoadedEvent), "session.mcp_servers_loaded")] [JsonDerivedType(typeof(SessionModeChangedEvent), "session.mode_changed")] +[JsonDerivedType(typeof(SessionModeNoticeDeliveredEvent), "session.mode_notice_delivered")] [JsonDerivedType(typeof(SessionModelChangeEvent), "session.model_change")] [JsonDerivedType(typeof(SessionPermissionsChangedEvent), "session.permissions_changed")] [JsonDerivedType(typeof(SessionPlanChangedEvent), "session.plan_changed")] @@ -124,6 +140,7 @@ namespace GitHub.Copilot; [JsonDerivedType(typeof(SessionWorkspaceFileChangedEvent), "session.workspace_file_changed")] [JsonDerivedType(typeof(SkillInvokedEvent), "skill.invoked")] [JsonDerivedType(typeof(SubagentCompletedEvent), "subagent.completed")] +[JsonDerivedType(typeof(SubagentConfiguredEvent), "subagent.configured")] [JsonDerivedType(typeof(SubagentDeselectedEvent), "subagent.deselected")] [JsonDerivedType(typeof(SubagentFailedEvent), "subagent.failed")] [JsonDerivedType(typeof(SubagentSelectedEvent), "subagent.selected")] @@ -136,6 +153,7 @@ namespace GitHub.Copilot; [JsonDerivedType(typeof(ToolExecutionProgressEvent), "tool.execution_progress")] [JsonDerivedType(typeof(ToolExecutionStartEvent), "tool.execution_start")] [JsonDerivedType(typeof(ToolUserRequestedEvent), "tool.user_requested")] +[JsonDerivedType(typeof(UiEphemeralQueryEvent), "ui.ephemeral_query")] [JsonDerivedType(typeof(UserInputCompletedEvent), "user_input.completed")] [JsonDerivedType(typeof(UserInputRequestedEvent), "user_input.requested")] [JsonDerivedType(typeof(UserMessageEvent), "user.message")] @@ -363,6 +381,19 @@ public sealed partial class SessionModeChangedEvent : SessionEvent public required SessionModeChangedData Data { get; set; } } +/// Records that a mode transition notice reached the model so cache-stable mode tools can remain offered across resume. +/// Represents the session.mode_notice_delivered event. +public sealed partial class SessionModeNoticeDeliveredEvent : SessionEvent +{ + /// + [JsonIgnore] + public override string Type => "session.mode_notice_delivered"; + + /// The session.mode_notice_delivered event payload. + [JsonPropertyName("data")] + public required SessionModeNoticeDeliveredData Data { get; set; } +} + /// Session limits update details. Null clears the limits. /// Represents the session.session_limits_changed event. public sealed partial class SessionSessionLimitsChangedEvent : SessionEvent @@ -376,8 +407,9 @@ public sealed partial class SessionSessionLimitsChangedEvent : SessionEvent public required SessionSessionLimitsChangedData Data { get; set; } } -/// Permissions change details carrying the aggregate allow-all transition. +/// Permission-mode transition details. /// Represents the session.permissions_changed event. +[Experimental(Diagnostics.Experimental)] public sealed partial class SessionPermissionsChangedEvent : SessionEvent { /// @@ -571,6 +603,76 @@ public sealed partial class SessionTaskCompleteEvent : SessionEvent public required SessionTaskCompleteData Data { get; set; } } +/// Behavior-neutral record of structured runtime facts present when an agent completion decision is accepted. +/// Represents the session.completion_receipt event. +[Experimental(Diagnostics.Experimental)] +public sealed partial class SessionCompletionReceiptEvent : SessionEvent +{ + /// + [JsonIgnore] + public override string Type => "session.completion_receipt"; + + /// The session.completion_receipt event payload. + [JsonPropertyName("data")] + public required SessionCompletionReceiptData Data { get; set; } +} + +/// Experimental transient signal that HydraFusion routing has started for an eligible turn. +/// Represents the session.fusion_route_started event. +[Experimental(Diagnostics.Experimental)] +public sealed partial class SessionFusionRouteStartedEvent : SessionEvent +{ + /// + [JsonIgnore] + public override string Type => "session.fusion_route_started"; + + /// The session.fusion_route_started event payload. + [JsonPropertyName("data")] + public required SessionFusionRouteStartedData Data { get; set; } +} + +/// Experimental durable HydraFusion routing failure and the deterministic concrete fallback selected for the turn. +/// Represents the session.fusion_route_failed event. +[Experimental(Diagnostics.Experimental)] +public sealed partial class SessionFusionRouteFailedEvent : SessionEvent +{ + /// + [JsonIgnore] + public override string Type => "session.fusion_route_failed"; + + /// The session.fusion_route_failed event payload. + [JsonPropertyName("data")] + public required SessionFusionRouteFailedData Data { get; set; } +} + +/// Experimental durable validated HydraFusion route and turn policy. +/// Represents the session.fusion_resolved event. +[Experimental(Diagnostics.Experimental)] +public sealed partial class SessionFusionResolvedEvent : SessionEvent +{ + /// + [JsonIgnore] + public override string Type => "session.fusion_resolved"; + + /// The session.fusion_resolved event payload. + [JsonPropertyName("data")] + public required SessionFusionResolvedData Data { get; set; } +} + +/// Experimental durable aggregate outcome of a HydraFusion turn. +/// Represents the session.fusion_completed event. +[Experimental(Diagnostics.Experimental)] +public sealed partial class SessionFusionCompletedEvent : SessionEvent +{ + /// + [JsonIgnore] + public override string Type => "session.fusion_completed"; + + /// The session.fusion_completed event payload. + [JsonPropertyName("data")] + public required SessionFusionCompletedData Data { get; set; } +} + /// Payload of `user.message` with displayed and model-transformed content, attachments, source/delivery metadata, mode, and telemetry IDs. /// Represents the user.message event. public sealed partial class UserMessageEvent : SessionEvent @@ -623,6 +725,19 @@ public sealed partial class AssistantTurnRetryEvent : SessionEvent public required AssistantTurnRetryData Data { get; set; } } +/// Metadata for work the user interrupted while the agent was running. +/// Represents the agent.interrupted event. +public sealed partial class AgentInterruptedEvent : SessionEvent +{ + /// + [JsonIgnore] + public override string Type => "agent.interrupted"; + + /// The agent.interrupted event payload. + [JsonPropertyName("data")] + public required AgentInterruptedData Data { get; set; } +} + /// Agent intent description for current activity or plan. /// Represents the assistant.intent event. public sealed partial class AssistantIntentEvent : SessionEvent @@ -636,6 +751,62 @@ public sealed partial class AssistantIntentEvent : SessionEvent public required AssistantIntentData Data { get; set; } } +/// Experimental transient HydraFusion phase/model/role signal. +/// Represents the assistant.fusion_phase_started event. +[Experimental(Diagnostics.Experimental)] +public sealed partial class AssistantFusionPhaseStartedEvent : SessionEvent +{ + /// + [JsonIgnore] + public override string Type => "assistant.fusion_phase_started"; + + /// The assistant.fusion_phase_started event payload. + [JsonPropertyName("data")] + public required AssistantFusionPhaseStartedData Data { get; set; } +} + +/// Experimental content-safe activity signal for a running HydraFusion phase. +/// Represents the assistant.fusion_phase_activity event. +[Experimental(Diagnostics.Experimental)] +public sealed partial class AssistantFusionPhaseActivityEvent : SessionEvent +{ + /// + [JsonIgnore] + public override string Type => "assistant.fusion_phase_activity"; + + /// The assistant.fusion_phase_activity event payload. + [JsonPropertyName("data")] + public required AssistantFusionPhaseActivityData Data { get; set; } +} + +/// Experimental durable HydraFusion phase output and lossless replay checkpoint. +/// Represents the assistant.fusion_phase_completed event. +[Experimental(Diagnostics.Experimental)] +public sealed partial class AssistantFusionPhaseCompletedEvent : SessionEvent +{ + /// + [JsonIgnore] + public override string Type => "assistant.fusion_phase_completed"; + + /// The assistant.fusion_phase_completed event payload. + [JsonPropertyName("data")] + public required AssistantFusionPhaseCompletedData Data { get; set; } +} + +/// Experimental durable typed HydraFusion phase failure and degradation transition. +/// Represents the assistant.fusion_phase_failed event. +[Experimental(Diagnostics.Experimental)] +public sealed partial class AssistantFusionPhaseFailedEvent : SessionEvent +{ + /// + [JsonIgnore] + public override string Type => "assistant.fusion_phase_failed"; + + /// The assistant.fusion_phase_failed event payload. + [JsonPropertyName("data")] + public required AssistantFusionPhaseFailedData Data { get; set; } +} + /// Live progress signal for a provider-hosted server tool (e.g. hosted web search) while it runs, before the finalized serverTools envelope lands on the terminal assistant.message. /// Represents the assistant.server_tool_progress event. public sealed partial class AssistantServerToolProgressEvent : SessionEvent @@ -779,6 +950,19 @@ public sealed partial class AssistantUsageEvent : SessionEvent public required AssistantUsageData Data { get; set; } } +/// A detected loss of a previously cached prompt prefix. +/// Represents the prompt_cache_break event. +public sealed partial class PromptCacheBreakEvent : SessionEvent +{ + /// + [JsonIgnore] + public override string Type => "prompt_cache_break"; + + /// The prompt_cache_break event payload. + [JsonPropertyName("data")] + public required PromptCacheBreakData Data { get; set; } +} + /// Failed LLM API call metadata for telemetry. /// Represents the model.call_failure event. public sealed partial class ModelCallFailureEvent : SessionEvent @@ -792,6 +976,19 @@ public sealed partial class ModelCallFailureEvent : SessionEvent public required ModelCallFailureData Data { get; set; } } +/// Final lifecycle outcome for one logical model dispatch. A logical dispatch may include internal reconnect or fallback work, so event count is not provider HTTP-request count. +/// Represents the model.call_finished event. +public sealed partial class ModelCallFinishedEvent : SessionEvent +{ + /// + [JsonIgnore] + public override string Type => "model.call_finished"; + + /// The model.call_finished event payload. + [JsonPropertyName("data")] + public required ModelCallFinishedData Data { get; set; } +} + /// Model API dispatch metadata for internal telemetry. /// Represents the model.call_start event. public sealed partial class ModelCallStartEvent : SessionEvent @@ -909,6 +1106,19 @@ public sealed partial class SkillInvokedEvent : SessionEvent public required SkillInvokedData Data { get; set; } } +/// Payload of `sandbox.decision`, a bounded governance record of what the process sandbox was configured to do and whether it took effect. Discriminated by `kind`. +/// Represents the sandbox.decision event. +public sealed partial class SandboxDecisionEvent : SessionEvent +{ + /// + [JsonIgnore] + public override string Type => "sandbox.decision"; + + /// The sandbox.decision event payload. + [JsonPropertyName("data")] + public required SandboxDecisionData Data { get; set; } +} + /// Sub-agent startup details including parent tool call and agent information. /// Represents the subagent.started event. public sealed partial class SubagentStartedEvent : SessionEvent @@ -922,6 +1132,19 @@ public sealed partial class SubagentStartedEvent : SessionEvent public required SubagentStartedData Data { get; set; } } +/// Resolved runtime configuration for a configured sub-agent. +/// Represents the subagent.configured event. +public sealed partial class SubagentConfiguredEvent : SessionEvent +{ + /// + [JsonIgnore] + public override string Type => "subagent.configured"; + + /// The subagent.configured event payload. + [JsonPropertyName("data")] + public required SubagentConfiguredData Data { get; set; } +} + /// Sub-agent completion details for successful execution. /// Represents the subagent.completed event. public sealed partial class SubagentCompletedEvent : SessionEvent @@ -1222,6 +1445,20 @@ public sealed partial class SessionCustomNotificationEvent : SessionEvent public required SessionCustomNotificationData Data { get; set; } } +/// Ordered output and terminal state for a transient query that does not modify conversation history. +/// Represents the ui.ephemeral_query event. +[Experimental(Diagnostics.Experimental)] +public sealed partial class UiEphemeralQueryEvent : SessionEvent +{ + /// + [JsonIgnore] + public override string Type => "ui.ephemeral_query"; + + /// The ui.ephemeral_query event payload. + [JsonPropertyName("data")] + public required UiEphemeralQueryData Data { get; set; } +} + /// External tool invocation request for client-side tool execution. /// Represents the external_tool.requested event. public sealed partial class ExternalToolRequestedEvent : SessionEvent @@ -1353,7 +1590,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, then the policy helper, per ordinary key, while permissions compose restrictively across device, server, policy-helper, 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 @@ -1473,6 +1710,34 @@ public sealed partial class FactoryRunUpdatedEvent : SessionEvent public required FactoryRunUpdatedData Data { get; set; } } +/// Ephemeral signal that a factory run attempt began executing. +/// Represents the factory.run_started event. +[Experimental(Diagnostics.Experimental)] +public sealed partial class FactoryRunStartedEvent : SessionEvent +{ + /// + [JsonIgnore] + public override string Type => "factory.run_started"; + + /// The factory.run_started event payload. + [JsonPropertyName("data")] + public required FactoryRunStartedData Data { get; set; } +} + +/// Ephemeral signal that a factory run reached a terminal status. +/// Represents the factory.run_settled event. +[Experimental(Diagnostics.Experimental)] +public sealed partial class FactoryRunSettledEvent : SessionEvent +{ + /// + [JsonIgnore] + public override string Type => "factory.run_settled"; + + /// The factory.run_settled event payload. + [JsonPropertyName("data")] + public required FactoryRunSettledData Data { get; set; } +} + /// Payload of `session.skills_loaded` listing resolved skill metadata. /// Represents the session.skills_loaded event. public sealed partial class SessionSkillsLoadedEvent : SessionEvent @@ -1695,6 +1960,11 @@ public sealed partial class SessionStartData [JsonPropertyName("alreadyInUse")] public bool? AlreadyInUse { get; set; } + /// Auto routing preference selected at session creation time. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("autoTier")] + public AutoTier? AutoTier { get; set; } + /// Working directory and git context at session start. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("context")] @@ -1774,6 +2044,11 @@ public sealed partial class SessionResumeData [JsonPropertyName("alreadyInUse")] public bool? AlreadyInUse { get; set; } + /// Auto routing preference active at resume time. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("autoTier")] + public AutoTier? AutoTier { get; set; } + /// Updated working directory and git context at resume time. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("context")] @@ -1900,6 +2175,11 @@ public sealed partial class SessionIdleData [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("aborted")] public bool? Aborted { get; set; } + + /// The session mode the agent was operating in when it went idle, when the mode is known. Lets turn-scoped consumers distinguish an autopilot continuation boundary (where the agent keeps working after this idle) from a genuine turn completion. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("mode")] + public SessionMode? Mode { get; set; } } /// Session title change payload containing the new display title. @@ -2087,6 +2367,11 @@ public sealed partial class SessionModelChangeData [JsonPropertyName("reasoningSummary")] public ReasoningSummary? ReasoningSummary { get; set; } + /// Origin of the effective model change, when known. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("source")] + public ModelChangeSource? Source { get; set; } + /// Output verbosity level after the model change, if applicable. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("verbosity")] @@ -2105,6 +2390,19 @@ public sealed partial class SessionModeChangedData public required SessionMode PreviousMode { get; set; } } +/// Records that a mode transition notice reached the model so cache-stable mode tools can remain offered across resume. +public sealed partial class SessionModeNoticeDeliveredData +{ + /// Model-visible transition notice persisted for a mid-turn delivery. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("content")] + public string? Content { get; set; } + + /// Mode established by the delivered transition notice. + [JsonPropertyName("mode")] + public required SessionMode Mode { get; set; } +} + /// Session limits update details. Null clears the limits. public sealed partial class SessionSessionLimitsChangedData { @@ -2113,28 +2411,25 @@ public sealed partial class SessionSessionLimitsChangedData public SessionLimitsConfig? SessionLimits { get; set; } } -/// Permissions change details carrying the aggregate allow-all transition. +/// Permission-mode transition details. +[Experimental(Diagnostics.Experimental)] public sealed partial class SessionPermissionsChangedData { - /// Allow-all mode after the change. + /// Explicit LLM judge model override used by assisted mode; omitted when the provider default applies. [Experimental(Diagnostics.Experimental)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("allowAllPermissionMode")] - public PermissionAllowAllMode? AllowAllPermissionMode { get; set; } - - /// Aggregate allow-all flag after the change. - [JsonPropertyName("allowAllPermissions")] - public required bool AllowAllPermissions { get; set; } + [JsonPropertyName("assistedApprovalModel")] + public string? AssistedApprovalModel { get; set; } - /// Allow-all mode before the change. + /// Permission mode after the change. [Experimental(Diagnostics.Experimental)] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("previousAllowAllPermissionMode")] - public PermissionAllowAllMode? PreviousAllowAllPermissionMode { get; set; } + [JsonPropertyName("mode")] + public required PermissionMode Mode { get; set; } - /// Aggregate allow-all flag before the change. - [JsonPropertyName("previousAllowAllPermissions")] - public required bool PreviousAllowAllPermissions { get; set; } + /// Permission mode before the change. + [Experimental(Diagnostics.Experimental)] + [JsonPropertyName("previousMode")] + public required PermissionMode PreviousMode { get; set; } } /// Plan file operation details indicating what changed. @@ -2250,6 +2545,11 @@ public sealed partial class SessionSnapshotRewindData /// Session termination metrics including usage statistics, code changes, and shutdown reason. public sealed partial class SessionShutdownData { + /// Per-agent usage breakdown, keyed by agent instance identifier. The main conversation uses the stable key `main`. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("agentMetrics")] + public IDictionary? AgentMetrics { get; set; } + /// Aggregate code change metrics for the session. [JsonPropertyName("codeChanges")] public required ShutdownCodeChanges CodeChanges { get; set; } @@ -2333,6 +2633,12 @@ public sealed partial class SessionUsageCheckpointData [JsonPropertyName("modelCacheState")] internal UsageCheckpointModelCacheState[]? ModelCacheState { get; set; } + /// Internal per-conversation prompt-cache-break detector baselines restored on resume. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonInclude] + [JsonPropertyName("promptCacheBreakState")] + internal JsonElement[]? PromptCacheBreakState { get; set; } + /// Session-wide accumulated nano-AI units cost at checkpoint time. [JsonPropertyName("totalNanoAiu")] public required double TotalNanoAiu { get; set; } @@ -2483,6 +2789,11 @@ public sealed partial class SessionCompactionStartData /// Conversation compaction results including success status, metrics, and optional error details. public sealed partial class SessionCompactionCompleteData { + /// Canonical model identifier used for model-specific behavior when replaying compaction. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("behaviorModelId")] + public string? BehaviorModelId { get; set; } + /// Checkpoint snapshot number created for recovery. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("checkpointNumber")] @@ -2612,800 +2923,802 @@ public sealed partial class SessionTaskCompleteData public string? Summary { get; set; } } -/// Payload of `user.message` with displayed and model-transformed content, attachments, source/delivery metadata, mode, and telemetry IDs. -public sealed partial class UserMessageData +/// Behavior-neutral record of structured runtime facts present when an agent completion decision is accepted. +[Experimental(Diagnostics.Experimental)] +public sealed partial class SessionCompletionReceiptData { - /// The agent mode that was active when this message was sent. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("agentMode")] - public UserMessageAgentMode? AgentMode { get; set; } + /// One-based accepted completion receipt ordinal in the durable session history. + [JsonPropertyName("attempt")] + public required long Attempt { get; set; } - /// Files, selections, or GitHub references attached to the message. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("attachments")] - public Attachment[]? Attachments { get; set; } + /// Inclusive durable event range summarized by this receipt. + [JsonPropertyName("eventRange")] + public required CompletionReceiptEventRange EventRange { get; set; } - /// The user's message text as displayed in the timeline. - [JsonPropertyName("content")] - public required string Content { get; set; } + /// Number of failed structured tool completions in the covered range. + [JsonPropertyName("failedToolCount")] + public required long FailedToolCount { get; set; } - /// 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. + /// Final structured tool completion in the covered range, when one exists. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("delivery")] - public UserMessageDelivery? Delivery { get; set; } + [JsonPropertyName("finalTool")] + public CompletionReceiptFinalTool? FinalTool { get; set; } - /// CAPI interaction ID for correlating this user message with its turn. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("interactionId")] - public string? InteractionId { get; set; } + /// Version of the completion receipt payload. + [JsonPropertyName("schemaVersion")] + public required long SchemaVersion { get; set; } - /// True when this user message was auto-injected by autopilot's continuation loop rather than typed by the user; used to distinguish autopilot-driven turns in telemetry. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("isAutopilotContinuation")] - public bool? IsAutopilotContinuation { get; set; } + /// Identifier of the assistant turn-end event that supplied the accepted completion boundary. This is the receipt's idempotency key, and always equals eventRange.endEventId. + [JsonPropertyName("sourceEventId")] + public required string SourceEventId { get; set; } - /// 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. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("nativeDocumentPathFallbackPaths")] - public string[]? NativeDocumentPathFallbackPaths { get; set; } + /// Runtime reason the completion decision was accepted. + [JsonPropertyName("stopReason")] + public required CompletionReceiptStopReason StopReason { get; set; } - /// Parent agent task ID for background telemetry correlated to this user turn. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("parentAgentTaskId")] - public string? ParentAgentTaskId { get; set; } + /// Number of successful structured tool completions in the covered range. + [JsonPropertyName("successfulToolCount")] + public required long SuccessfulToolCount { get; set; } +} - /// 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; } +/// Experimental transient signal that HydraFusion routing has started for an eligible turn. +[Experimental(Diagnostics.Experimental)] +public sealed partial class SessionFusionRouteStartedData +{ + /// Identifier for this routing attempt before a durable Fusion turn exists. + [JsonPropertyName("attemptId")] + public required string AttemptId { get; set; } - /// Normalized document MIME types that were sent natively instead of through tagged_files XML. + /// HydraFusion routing policy requested for the turn. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("supportedNativeDocumentMimeTypes")] - public string[]? SupportedNativeDocumentMimeTypes { get; set; } + [JsonPropertyName("policy")] + public string? Policy { get; set; } - /// Transformed version of the message sent to the model, with XML wrapping, timestamps, and other augmentations for prompt caching. + /// Synthetic HydraFusion model selected for the session. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("transformedContent")] - public string? TransformedContent { get; set; } -} + [JsonPropertyName("syntheticModel")] + public string? SyntheticModel { get; set; } -/// Empty payload; the event signals that the pending message queue has changed. -public sealed partial class PendingMessagesModifiedData -{ + /// Kind of turn being routed. + [JsonPropertyName("turnKind")] + public required FusionTurnKind TurnKind { get; set; } } -/// Turn initialization metadata including identifier and interaction tracking. -public sealed partial class AssistantTurnStartData +/// Experimental durable HydraFusion routing failure and the deterministic concrete fallback selected for the turn. +[Experimental(Diagnostics.Experimental)] +public sealed partial class SessionFusionRouteFailedData { - /// CAPI interaction ID for correlating this turn with upstream telemetry. + /// Identifier of the routing attempt that failed. + [JsonPropertyName("attemptId")] + public required string AttemptId { get; set; } + + /// Provider or validation error detail, when available. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("interactionId")] - public string? InteractionId { get; set; } + [JsonPropertyName("errorMessage")] + public string? ErrorMessage { get; set; } - /// Model identifier used for this turn, when known. + /// Concrete model selected as the deterministic fallback. + [JsonPropertyName("fallbackModel")] + public required string FallbackModel { get; set; } + + /// HydraFusion routing policy requested for the turn. + [JsonPropertyName("policy")] + public required string Policy { get; set; } + + /// Stable machine-readable reason for the routing failure. + [JsonPropertyName("reason")] + public required string Reason { get; set; } + + /// Elapsed routing time in milliseconds before the failure. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("model")] - public string? Model { get; set; } + [JsonPropertyName("routingLatencyMs")] + public double? RoutingLatencyMs { get; set; } - /// Identifier for this turn within the agentic loop, typically a stringified turn number. - [JsonPropertyName("turnId")] - public required string TurnId { get; set; } + /// Synthetic HydraFusion model selected for the session. + [JsonPropertyName("syntheticModel")] + public required string SyntheticModel { get; set; } } -/// Metadata for an additional model inference attempt within an existing assistant turn. -public sealed partial class AssistantTurnRetryData +/// Experimental durable validated HydraFusion route and turn policy. +[Experimental(Diagnostics.Experimental)] +public sealed partial class SessionFusionResolvedData { - /// Model identifier used for this retry, when known. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("model")] - public string? Model { get; set; } + /// Version of the validated HydraFusion event contract. + [JsonPropertyName("contractVersion")] + public required long ContractVersion { get; set; } - /// Provider or runtime classification that caused the retry, when known. + /// Concrete model used when the planned primary model cannot execute. + [JsonPropertyName("fallbackModel")] + public required string FallbackModel { get; set; } + + /// Router recommendation controlling reuse or rerouting on later turns. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("reason")] - public string? Reason { get; set; } + [JsonPropertyName("followUp")] + public FusionFollowUpRecommendation? FollowUp { get; set; } - /// Identifier of the turn whose model inference is being retried. - [JsonPropertyName("turnId")] - public required string TurnId { get; set; } -} + /// Concrete model recommended for eligible follow-up turns. + [JsonPropertyName("followUpModel")] + public required string FollowUpModel { get; set; } -/// Agent intent description for current activity or plan. -public sealed partial class AssistantIntentData -{ - /// Short description of what the agent is currently doing or planning to do. - [JsonPropertyName("intent")] - public required string Intent { get; set; } -} + /// Stable identifier for the resolved HydraFusion turn. + [JsonPropertyName("fusionId")] + public required string FusionId { get; set; } -/// Live progress signal for a provider-hosted server tool (e.g. hosted web search) while it runs, before the finalized serverTools envelope lands on the terminal assistant.message. -public sealed partial class AssistantServerToolProgressData -{ - /// Kind of hosted server tool that is running. Only `web_search` is emitted today. - [JsonPropertyName("kind")] - public required string Kind { get; set; } + /// Version of the executable model universe used for selection. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("modelUniverseVersion")] + public string? ModelUniverseVersion { get; set; } - /// Position of the hosted tool call in the response output. Stable across the call's lifecycle events (unlike the provider's per-event item id, which CAPI rotates), so the host keys the live in-progress row on it. - [JsonPropertyName("outputIndex")] - public required long OutputIndex { get; set; } + /// Validated orchestration pattern selected for the turn. + [JsonPropertyName("pattern")] + public required FusionPattern Pattern { get; set; } - /// Lifecycle status of the hosted call: `in_progress`, `searching`, or `completed`. - [JsonPropertyName("status")] - public required string Status { get; set; } -} + /// Presentation-neutral phase plan for clients that render workflow progress. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("phasePlan")] + public FusionPhasePlanStep[]? PhasePlan { get; set; } -/// Assistant reasoning content for timeline display with complete thinking text. -public sealed partial class AssistantReasoningData -{ - /// The complete extended thinking text from the model. - [JsonPropertyName("content")] - public required string Content { get; set; } + /// Version of the validated execution-plan format. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("planVersion")] + public string? PlanVersion { get; set; } - /// Unique identifier for this reasoning block. - [JsonPropertyName("reasoningId")] - public required string ReasoningId { get; set; } + /// HydraFusion routing policy used to resolve the plan. + [JsonPropertyName("policy")] + public required string Policy { get; set; } - /// Gets or sets the rte value. + /// Version of the local routing policy. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("rte")] - public bool? Rte { get; set; } -} + [JsonPropertyName("policyVersion")] + public string? PolicyVersion { get; set; } -/// Streaming reasoning delta for incremental extended thinking updates. -public sealed partial class AssistantReasoningDeltaData -{ - /// Incremental text chunk to append to the reasoning content. - [JsonPropertyName("deltaContent")] - public required string DeltaContent { get; set; } + /// Concrete model selected for the primary solver phase. + [JsonPropertyName("primaryModel")] + public required string PrimaryModel { get; set; } - /// Reasoning block ID this delta belongs to, matching the corresponding assistant.reasoning event. - [JsonPropertyName("reasoningId")] - public required string ReasoningId { get; set; } -} + /// Router implementation that supplied the plan. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("routeSource")] + public string? RouteSource { get; set; } -/// Streaming tool-call input delta for incremental tool-call updates. -public sealed partial class AssistantToolCallDeltaData -{ - /// Raw provider tool input fragment to append for this tool call. Function/tool-use providers stream serialized JSON argument text (so newlines inside JSON string values may appear as escaped `\n` until the accumulated JSON is parsed); custom tool calls stream raw custom input. - [JsonPropertyName("inputDelta")] - public required string InputDelta { get; set; } + /// Elapsed time in milliseconds required to resolve and validate the route. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("routingLatencyMs")] + public double? RoutingLatencyMs { get; set; } - /// Tool call ID this delta belongs to, matching the corresponding assistant.message tool request. - [JsonPropertyName("toolCallId")] - public required string ToolCallId { get; set; } + /// Identifier of the local policy rule that matched. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("ruleId")] + public string? RuleId { get; set; } - /// Name of the tool being invoked, when known from the stream. + /// Zero-based index of the local policy rule that matched. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolName")] - public string? ToolName { get; set; } + [JsonPropertyName("ruleIndex")] + public long? RuleIndex { get; set; } - /// Tool call type, when known from the stream. + /// Human-readable name of the local policy rule that matched. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolType")] - public AssistantMessageToolRequestType? ToolType { get; set; } -} + [JsonPropertyName("ruleName")] + public string? RuleName { get; set; } -/// Streaming response progress with cumulative byte count. -public sealed partial class AssistantStreamingDeltaData -{ - /// Cumulative total bytes received from the streaming response so far. - [JsonPropertyName("totalResponseSizeBytes")] - public required long TotalResponseSizeBytes { get; set; } + /// Validated capability scores used to select the route. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("scores")] + public FusionScores? Scores { get; set; } + + /// Concrete model selected for the review or judge phase, when required. + [JsonPropertyName("secondaryModel")] + public string? SecondaryModel { get; set; } + + /// Synthetic HydraFusion model selected for the session. + [JsonPropertyName("syntheticModel")] + public required string SyntheticModel { get; set; } + + /// Identifier of the session turn associated with the route. + [JsonPropertyName("turnId")] + public required string TurnId { get; set; } } -/// Assistant response containing text content, optional tool requests, and interaction metadata. -public sealed partial class AssistantMessageData +/// Experimental durable aggregate outcome of a HydraFusion turn. +[Experimental(Diagnostics.Experimental)] +public sealed partial class SessionFusionCompletedData { - /// Provider's completion / response identifier; shared across all chunks of a single API call. Used to group multi-chunk assistant utterances. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("apiCallId")] - public string? ApiCallId { get; set; } + /// Total cached input tokens reported across all phases. + [JsonPropertyName("cachedTokens")] + public required long CachedTokens { 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. + /// Total tokens written to prompt cache across all phases. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("chunkCount")] - public long? ChunkCount { get; set; } + [JsonPropertyName("cacheWriteTokens")] + public long? CacheWriteTokens { 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; } + /// Idempotency identifier for the authoritative final commit. + [JsonPropertyName("commitId")] + public required string CommitId { 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)] - [JsonPropertyName("citations")] - public Citations? Citations { get; set; } + /// Reason the turn used a degraded route, when applicable. + [JsonPropertyName("degradedReason")] + public string? DegradedReason { get; set; } - /// Client-minted request id (x-request-id header) echoed by the server. Distinct from requestId (x-github-request-id) and serviceRequestId (x-copilot-service-request-id). - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("clientRequestId")] - public string? ClientRequestId { get; set; } + /// Total elapsed execution time for the HydraFusion turn in milliseconds. + [JsonPropertyName("durationMs")] + public required double DurationMs { get; set; } - /// The assistant's text response content. - [JsonPropertyName("content")] - public required string Content { get; set; } + /// Concrete model that supplied the authoritative final content. + [JsonPropertyName("finalSourceModel")] + public string? FinalSourceModel { get; set; } - /// Encrypted reasoning content from OpenAI models. Session-bound and stripped on resume. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("encryptedContent")] - public string? EncryptedContent { get; set; } + /// Phase whose output supplied the authoritative final content. + [JsonPropertyName("finalSourcePhaseId")] + public string? FinalSourcePhaseId { get; set; } - /// CAPI interaction ID for correlating this message with upstream telemetry. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("interactionId")] - public string? InteractionId { get; set; } + /// Concrete model recommended for eligible follow-up turns. + [JsonPropertyName("followUpModel")] + public required string FollowUpModel { get; set; } - /// Unique identifier for this assistant message. - [JsonPropertyName("messageId")] - public required string MessageId { get; set; } + /// Stable identifier for the completed HydraFusion turn. + [JsonPropertyName("fusionId")] + public required string FusionId { get; set; } - /// Model that produced this assistant message, if known. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("model")] - public string? Model { get; set; } + /// Total input tokens consumed across all phases. + [JsonPropertyName("inputTokens")] + public required long InputTokens { get; set; } - /// Actual output token count from the API response (completion_tokens), used for accurate token accounting. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + /// Stable aggregate outcome of the HydraFusion turn. + [JsonPropertyName("outcome")] + public required string Outcome { get; set; } + + /// Total output tokens produced across all phases. [JsonPropertyName("outputTokens")] - public long? OutputTokens { get; set; } + public required long OutputTokens { get; set; } - /// Tool call ID of the parent tool invocation when this event originates from a sub-agent. - [EditorBrowsable(EditorBrowsableState.Never)] -#if NET5_0_OR_GREATER - [Obsolete("This member is deprecated and will be removed in a future version.", DiagnosticId = "GHCP001")] -#endif + /// HydraFusion orchestration pattern executed for the turn. + [JsonPropertyName("pattern")] + public required FusionPattern Pattern { get; set; } + + /// Number of concrete phases attempted by the turn. + [JsonPropertyName("phaseCount")] + public required long PhaseCount { get; set; } + + /// Total concrete model requests made across all phases. + [JsonPropertyName("requestCount")] + public required long RequestCount { get; set; } + + /// Synthetic HydraFusion model selected for the session. + [JsonPropertyName("syntheticModel")] + public required string SyntheticModel { get; set; } + + /// Total normalized AI-unit cost reported across all phases, in nano-AIU. + [JsonPropertyName("totalNanoAiu")] + public required double TotalNanoAiu { get; set; } + + /// Identifier of the session turn associated with the completion. + [JsonPropertyName("turnId")] + public required string TurnId { get; set; } +} + +/// Payload of `user.message` with displayed and model-transformed content, attachments, source/delivery metadata, mode, and telemetry IDs. +public sealed partial class UserMessageData +{ + /// The agent mode that was active when this message was sent. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("parentToolCallId")] - public string? ParentToolCallId { get; set; } + [JsonPropertyName("agentMode")] + public UserMessageAgentMode? AgentMode { get; set; } - /// Generation phase for phased-output models (e.g., thinking vs. response phases). + /// Files, selections, or GitHub references attached to the message. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("phase")] - public string? Phase { get; set; } + [JsonPropertyName("attachments")] + public Attachment[]? Attachments { get; set; } - /// Opaque/encrypted extended thinking data from Anthropic models. Session-bound and stripped on resume. + /// The user's message text as displayed in the timeline. + [JsonPropertyName("content")] + public required string Content { get; set; } + + /// 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. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("reasoningOpaque")] - public string? ReasoningOpaque { get; set; } + [JsonPropertyName("delivery")] + public UserMessageDelivery? Delivery { get; set; } - /// Readable reasoning text from the model's extended thinking. + /// CAPI interaction ID for correlating this user message with its turn. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("reasoningText")] - public string? ReasoningText { get; set; } + [JsonPropertyName("interactionId")] + public string? InteractionId { get; set; } - /// OpenAI-compatible wire field the provider used for reasoning (e.g. reasoning_content/reasoning). Populated only when non-canonical, so the dialect round-trips across turns. + /// True when this user message was auto-injected by autopilot's continuation loop rather than typed by the user; used to distinguish autopilot-driven turns in telemetry. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("reasoningWireField")] - public string? ReasoningWireField { get; set; } + [JsonPropertyName("isAutopilotContinuation")] + public bool? IsAutopilotContinuation { get; set; } - /// GitHub request tracing ID (x-github-request-id header) for correlating with server-side logs. + /// Stable identity of the logical user message, matching the ID returned by send and retained by pending queue snapshots. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("requestId")] - public string? RequestId { get; set; } + [JsonPropertyName("messageId")] + public string? MessageId { get; set; } - /// Gets or sets the rte value. + /// 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. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("rte")] - public bool? Rte { get; set; } + [JsonPropertyName("nativeDocumentPathFallbackPaths")] + public string[]? NativeDocumentPathFallbackPaths { get; set; } - /// Neutral provider-tagged server-side tool-use payload (tool search, advisor) for verbatim round-tripping. + /// Parent agent task ID for background telemetry correlated to this user turn. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("serverTools")] - public AssistantMessageServerTools? ServerTools { get; set; } + [JsonPropertyName("parentAgentTaskId")] + public string? ParentAgentTaskId { get; set; } - /// Copilot service request ID (x-copilot-service-request-id header) for CAPI log correlation. + /// 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("serviceRequestId")] - public string? ServiceRequestId { get; set; } + [JsonPropertyName("source")] + public string? Source { get; set; } - /// Tool invocations requested by the assistant in this message. + /// Normalized document MIME types that were sent natively instead of through tagged_files XML. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolRequests")] - public AssistantMessageToolRequest[]? ToolRequests { get; set; } + [JsonPropertyName("supportedNativeDocumentMimeTypes")] + public string[]? SupportedNativeDocumentMimeTypes { get; set; } - /// Identifier for the agent loop turn that produced this message, matching the corresponding assistant.turn_start event. + /// Transformed version of the message sent to the model, with XML wrapping, timestamps, and other augmentations for prompt caching. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("transformedContent")] + public string? TransformedContent { get; set; } + + /// The agent-loop turn ID that consumed this message; absent when no agent-loop turn consumed it. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("turnId")] public string? TurnId { get; set; } } -/// Streaming assistant message start metadata. -public sealed partial class AssistantMessageStartData +/// Empty payload; the event signals that the pending message queue has changed. +public sealed partial class PendingMessagesModifiedData { - /// Message ID this start event belongs to, matching subsequent deltas and assistant.message. - [JsonPropertyName("messageId")] - public required string MessageId { get; set; } - - /// Generation phase this message belongs to for phased-output models. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("phase")] - public string? Phase { get; set; } } -/// Streaming assistant message delta for incremental response updates. -public sealed partial class AssistantMessageDeltaData +/// Turn initialization metadata including identifier and interaction tracking. +public sealed partial class AssistantTurnStartData { - /// Incremental text chunk to append to the message content. - [JsonPropertyName("deltaContent")] - public required string DeltaContent { get; set; } - - /// Message ID this delta belongs to, matching the corresponding assistant.message event. - [JsonPropertyName("messageId")] - public required string MessageId { get; set; } - - /// Tool call ID of the parent tool invocation when this event originates from a sub-agent. - [EditorBrowsable(EditorBrowsableState.Never)] -#if NET5_0_OR_GREATER - [Obsolete("This member is deprecated and will be removed in a future version.", DiagnosticId = "GHCP001")] -#endif + /// CAPI interaction ID for correlating this turn with upstream telemetry. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("parentToolCallId")] - public string? ParentToolCallId { get; set; } -} + [JsonPropertyName("interactionId")] + public string? InteractionId { get; set; } -/// Turn completion metadata including the turn identifier. -public sealed partial class AssistantTurnEndData -{ /// Model identifier used for this turn, when known. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("model")] public string? Model { get; set; } - /// Identifier of the turn that has ended, matching the corresponding assistant.turn_start event. + /// Identifier for this turn within the agentic loop, typically a stringified turn number. [JsonPropertyName("turnId")] public required string TurnId { get; set; } } -/// Payload emitted whenever the main agent's processing loop goes idle, including while related background work (running agents or in-flight attached shell commands) is still pending and the session-level idle event is therefore deferred. -public sealed partial class AssistantIdleData +/// Metadata for an additional model inference attempt within an existing assistant turn. +public sealed partial class AssistantTurnRetryData { - /// True when the preceding agentic loop was cancelled via abort signal. + /// Model identifier used for this retry, when known. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("aborted")] - public bool? Aborted { get; set; } + [JsonPropertyName("model")] + public string? Model { get; set; } + + /// Provider or runtime classification that caused the retry, when known. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("reason")] + public string? Reason { get; set; } + + /// Identifier of the turn whose model inference is being retried. + [JsonPropertyName("turnId")] + public required string TurnId { get; set; } } -/// LLM API call usage metrics including tokens, costs, quotas, and billing information. -public sealed partial class AssistantUsageData +/// Metadata for work the user interrupted while the agent was running. +public sealed partial class AgentInterruptedData { - /// Completion ID from the model provider (e.g., chatcmpl-abc123). - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("apiCallId")] - public string? ApiCallId { get; set; } + /// What the agent was doing when the user interrupted it. + [JsonPropertyName("activity")] + public required AgentInterruptedActivity Activity { get; set; } - /// API endpoint used for this model call, matching CAPI supported_endpoints vocabulary. + /// For an interrupted model call: the provider endpoint the request targeted. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("apiEndpoint")] - public AssistantUsageApiEndpoint? ApiEndpoint { get; set; } + public string? ApiEndpoint { get; set; } - /// Number of tools available to the model for this call. + /// For an interrupted model call: whether the user interrupted before any token arrived or while the response was streaming. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonInclude] - [JsonPropertyName("availableToolCount")] - internal long? AvailableToolCount { get; set; } + [JsonPropertyName("cancelPhase")] + public AgentInterruptedCancelPhase? CancelPhase { 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")] - public DateTimeOffset? CacheExpiresAt { get; set; } + /// How long the interrupted work had been running, in milliseconds. + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] + [JsonPropertyName("elapsedMs")] + public required TimeSpan Elapsed { get; set; } - /// Number of tokens read from prompt cache. + /// For an interrupted background-agent batch: how many background sub-agents the stop swept. Counts accepted cancellations, so an agent cancelled as a cascade of its interrupted parent is covered by that parent rather than counted again. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("cacheReadTokens")] - public long? CacheReadTokens { get; set; } + [JsonPropertyName("interruptedAgentCount")] + public long? InterruptedAgentCount { get; set; } - /// Number of tokens written to prompt cache. + /// For an interrupted model call: the model the request targeted. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("cacheWriteTokens")] - public long? CacheWriteTokens { get; set; } + [JsonPropertyName("model")] + public string? Model { get; set; } - /// 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. + /// For a mid-stream interrupt: the observed time to first observable output, in milliseconds. Deliberately distinct from the `ttftMs` reported on a successful model call, which measures time to first stream event. + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("contentFilterTriggered")] - public bool? ContentFilterTriggered { get; set; } + [JsonPropertyName("outputTtftMs")] + public TimeSpan? OutputTtft { get; set; } - /// Per-request cost and usage data from the CAPI copilot_usage response field. + /// For an interrupted model call: the reasoning effort the request asked for. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("copilotUsage")] - public AssistantUsageCopilotUsage? CopilotUsage { get; set; } + [JsonPropertyName("reasoningEffort")] + public string? ReasoningEffort { get; set; } - /// Model multiplier cost for billing purposes. - [Experimental(Diagnostics.Experimental)] + /// Subset of `toolNames` whose tool metadata marks the tool name as safe to record unhashed in telemetry. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("cost")] - public double? Cost { get; set; } + [JsonPropertyName("safeToolNames")] + public string[]? SafeToolNames { get; set; } - /// Duration of the API call in milliseconds. - [JsonConverter(typeof(MillisecondsTimeSpanConverter))] + /// Tool call identifiers that were still running. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("duration")] - public TimeSpan? Duration { get; set; } + [JsonPropertyName("toolCallIds")] + public string[]? ToolCallIds { get; set; } - /// 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". + /// Names of the tools that were still running. More than one when the model requested a parallel fan-out. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("finishReason")] - public string? FinishReason { get; set; } + [JsonPropertyName("toolNames")] + public string[]? ToolNames { get; set; } - /// What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls. + /// For an interrupted model call: the transport the request used. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("initiator")] - public string? Initiator { get; set; } + [JsonPropertyName("transport")] + public ModelCallFailureTransport? Transport { get; set; } - /// Number of input tokens consumed. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("inputTokens")] - public long? InputTokens { get; set; } + /// Zero-based agentic-loop iteration the interrupt landed in. + [JsonPropertyName("turn")] + public required long Turn { 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; } +/// Agent intent description for current activity or plan. +public sealed partial class AssistantIntentData +{ + /// Short description of what the agent is currently doing or planning to do. + [JsonPropertyName("intent")] + public required string Intent { get; set; } +} - /// Average inter-token latency in milliseconds. Only available for streaming requests. - [JsonConverter(typeof(MillisecondsTimeSpanConverter))] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("interTokenLatencyMs")] - public TimeSpan? InterTokenLatency { get; set; } +/// Experimental transient HydraFusion phase/model/role signal. +[Experimental(Diagnostics.Experimental)] +public sealed partial class AssistantFusionPhaseStartedData +{ + /// Conversation scope in which the phase executes. + [JsonPropertyName("conversationScope")] + public required FusionConversationScope ConversationScope { get; set; } - /// Model identifier used for this API call. + /// Identifier of the HydraFusion turn containing the phase. + [JsonPropertyName("fusionId")] + public required string FusionId { get; set; } + + /// Concrete model executing the phase. [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; } + /// HydraFusion orchestration pattern containing the phase. + [JsonPropertyName("pattern")] + public required FusionPattern Pattern { get; set; } - /// Number of output tokens produced. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("outputTokens")] - public long? OutputTokens { get; set; } + /// Stable identifier for the concrete phase. + [JsonPropertyName("phaseId")] + public required string PhaseId { get; set; } - /// Parent tool call ID when this usage originates from a sub-agent. - [EditorBrowsable(EditorBrowsableState.Never)] -#if NET5_0_OR_GREATER - [Obsolete("This member is deprecated and will be removed in a future version.", DiagnosticId = "GHCP001")] -#endif - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("parentToolCallId")] - public string? ParentToolCallId { get; set; } + /// Kind of phase being executed. + [JsonPropertyName("phaseKind")] + public required FusionPhaseKind PhaseKind { get; set; } - /// GitHub request tracing ID (x-github-request-id header) for server-side log correlation. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("providerCallId")] - public string? ProviderCallId { get; set; } + /// Semantic role assigned to the phase. + [JsonPropertyName("role")] + public required string Role { get; set; } +} - /// Per-quota resource usage snapshots, keyed by quota identifier. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonInclude] - [JsonPropertyName("quotaSnapshots")] - internal IDictionary? QuotaSnapshots { get; set; } +/// Experimental content-safe activity signal for a running HydraFusion phase. +[Experimental(Diagnostics.Experimental)] +public sealed partial class AssistantFusionPhaseActivityData +{ + /// Kind of real activity observed. + [JsonPropertyName("activity")] + public required FusionPhaseActivityKind Activity { get; set; } - /// Reasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh", "max"). - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("reasoningEffort")] - public string? ReasoningEffort { get; set; } + /// Conversation scope in which the phase executes. + [JsonPropertyName("conversationScope")] + public required FusionConversationScope ConversationScope { get; set; } - /// Number of output tokens used for reasoning (e.g., chain-of-thought). - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("reasoningTokens")] - public long? ReasoningTokens { get; set; } + /// Identifier of the HydraFusion turn containing the phase. + [JsonPropertyName("fusionId")] + public required string FusionId { get; set; } - /// Gets or sets the rte value. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("rte")] - public bool? Rte { get; set; } + /// HydraFusion orchestration pattern containing the phase. + [JsonPropertyName("pattern")] + public required FusionPattern Pattern { get; set; } - /// Copilot service request ID (x-copilot-service-request-id header) for CAPI log correlation. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("serviceRequestId")] - public string? ServiceRequestId { get; set; } + /// Stable identifier for the concrete phase. + [JsonPropertyName("phaseId")] + public required string PhaseId { get; set; } - /// Time to first token in milliseconds. Only available for streaming requests. - [JsonConverter(typeof(MillisecondsTimeSpanConverter))] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("timeToFirstTokenMs")] - public TimeSpan? TimeToFirstToken { get; set; } + /// Kind of phase currently executing. + [JsonPropertyName("phaseKind")] + public required FusionPhaseKind PhaseKind { get; set; } - /// Tool-call counts keyed by tool name. + /// Semantic role assigned to the phase. + [JsonPropertyName("role")] + public required string Role { get; set; } + + /// Opaque hashed correlation token for matching tool-started and tool-completed activity within this Fusion activity stream. It is not the tool call identifier exposed by tool lifecycle events. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonInclude] - [JsonPropertyName("toolCounts")] - internal IDictionary? ToolCounts { get; set; } + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } - /// Number of tokens used by tool definitions for this call. + /// Cumulative private response bytes observed for this model call. The event never includes response text. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonInclude] - [JsonPropertyName("toolTokenCount")] - internal long? ToolTokenCount { get; set; } + [JsonPropertyName("totalResponseSizeBytes")] + public long? TotalResponseSizeBytes { get; set; } } -/// Failed LLM API call metadata for telemetry. -public sealed partial class ModelCallFailureData +/// Experimental durable HydraFusion phase output and lossless replay checkpoint. +[Experimental(Diagnostics.Experimental)] +public sealed partial class AssistantFusionPhaseCompletedData { - /// Completion ID from the model provider (e.g., chatcmpl-abc123). - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("apiCallId")] - public string? ApiCallId { get; set; } - - /// API endpoint used for this model call, matching CAPI supported_endpoints vocabulary. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("apiEndpoint")] - public AssistantUsageApiEndpoint? ApiEndpoint { get; set; } + /// Provider-normalized textual output produced by the phase. + [JsonPropertyName("content")] + public required string Content { get; set; } - /// For HTTP 400 failures only: whether the response carried a structured CAPI error envelope (structured_error, a deterministic validation failure) or no error body (bodyless, the transient gateway/proxy signature). Absent for non-400 failures. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("badRequestKind")] - public ModelCallFailureBadRequestKind? BadRequestKind { get; set; } + /// Conversation scope in which the phase executed. + [JsonPropertyName("conversationScope")] + public required FusionConversationScope ConversationScope { get; set; } - /// Duration of the failed API call in milliseconds. - [JsonConverter(typeof(MillisecondsTimeSpanConverter))] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + /// Elapsed execution time for the phase in milliseconds. [JsonPropertyName("durationMs")] - public TimeSpan? Duration { get; set; } + public required double DurationMs { get; set; } - /// For HTTP 400 failures only: the `code` from the CAPI error envelope (e.g. 'model_max_prompt_tokens_exceeded') identifying which deterministic validation failure occurred. Raw server-controlled string, emitted only through restricted telemetry. Absent for bodyless or non-400 failures. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("errorCode")] - public string? ErrorCode { get; set; } + /// Identifier of the HydraFusion turn containing the phase. + [JsonPropertyName("fusionId")] + public required string FusionId { get; set; } - /// Raw provider/runtime error message for restricted telemetry. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("errorMessage")] - public string? ErrorMessage { get; set; } + /// Concrete model that executed the phase. + [JsonPropertyName("model")] + public required string Model { get; set; } - /// For HTTP 400 failures only: the `type` from the CAPI error envelope (e.g. 'websocket_error'), a coarser companion to errorCode for envelopes that carry no code. Raw server-controlled string, emitted only through restricted telemetry. Absent for bodyless or non-400 failures. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("errorType")] - public string? ErrorType { get; set; } + /// Stable identifier for the completed phase. + [JsonPropertyName("phaseId")] + public required string PhaseId { get; set; } - /// Whether the failure originated from an API response or the request transport. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("failureKind")] - public ModelCallFailureKind? FailureKind { get; set; } + /// Kind of phase that completed. + [JsonPropertyName("phaseKind")] + public required FusionPhaseKind PhaseKind { get; set; } - /// What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls. + /// Exact provider-normalized message used to reconstruct canonical model history. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("initiator")] - public string? Initiator { get; set; } + [JsonInclude] + [JsonPropertyName("projectionMessage")] + internal JsonElement? ProjectionMessage { get; set; } - /// Whether the session selected Auto mode for the failed call. + /// Projection action for the exact internal message. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("isAuto")] - public bool? IsAuto { get; set; } + [JsonInclude] + [JsonPropertyName("projectionMode")] + internal FusionProjectionMode? ProjectionMode { get; set; } - /// Whether the failed call used a bring-your-own-key provider. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("isByok")] - public bool? IsByok { get; set; } + /// Semantic role assigned to the completed phase. + [JsonPropertyName("role")] + public required string Role { get; set; } - /// Effective maximum output-token limit for the failed call. + /// Terminal request held outside canonical state until selected by the final commit. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("maxOutputTokens")] - public long? MaxOutputTokens { get; set; } + [JsonInclude] + [JsonPropertyName("stagedTerminal")] + internal FusionStagedTerminal? StagedTerminal { get; set; } - /// Effective maximum prompt-token limit for the failed call. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("maxPromptTokens")] - public long? MaxPromptTokens { get; set; } + /// Durable outcome status of the phase. + [JsonPropertyName("status")] + public required FusionPhaseStatus Status { get; set; } - /// Model identifier used for the failed API call. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("model")] - public string? Model { get; set; } + /// Aggregate concrete-model usage consumed by the phase. + [JsonPropertyName("usage")] + public required FusionPhaseUsage Usage { get; set; } - /// GitHub request tracing ID (x-github-request-id header) for server-side log correlation. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("providerCallId")] - public string? ProviderCallId { get; set; } + /// Structured judge or critic verdict, when the phase produces one. + [JsonPropertyName("verdict")] + public string? Verdict { get; set; } +} - /// Per-quota usage snapshots parsed from the failed response's quota headers, keyed by quota identifier. Present when the error response carried quota headers (e.g. a 402 once the additional spend limit is reached) so the UI can refresh the quota display on failure. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonInclude] - [JsonPropertyName("quotaSnapshots")] - internal IDictionary? QuotaSnapshots { get; set; } +/// Experimental durable typed HydraFusion phase failure and degradation transition. +[Experimental(Diagnostics.Experimental)] +public sealed partial class AssistantFusionPhaseFailedData +{ + /// Conversation scope in which the phase executed. + [JsonPropertyName("conversationScope")] + public required FusionConversationScope ConversationScope { get; set; } - /// Reasoning effort level used for the failed model call, if applicable. + /// Identifier of the fallback phase used to continue the turn after degradation. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("reasoningEffort")] - public string? ReasoningEffort { get; set; } + [JsonPropertyName("degradedToPhaseId")] + public string? DegradedToPhaseId { get; set; } - /// 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. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("requestFingerprint")] - public ModelCallFailureRequestFingerprint? RequestFingerprint { get; set; } + /// Elapsed execution time before the phase failed, in milliseconds. + [JsonPropertyName("durationMs")] + public required double DurationMs { get; set; } - /// Gets or sets the rte value. + /// Provider or execution error detail, when available. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("rte")] - public bool? Rte { get; set; } + [JsonPropertyName("errorMessage")] + public string? ErrorMessage { get; set; } - /// Copilot service request ID (x-copilot-service-request-id header) for CAPI log correlation. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("serviceRequestId")] - public string? ServiceRequestId { get; set; } + /// Identifier of the HydraFusion turn containing the phase. + [JsonPropertyName("fusionId")] + public required string FusionId { get; set; } - /// Where the failed model call originated. - [JsonPropertyName("source")] - public required ModelCallFailureSource Source { get; set; } - - /// HTTP status code from the failed request. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("statusCode")] - public int? StatusCode { get; set; } - - /// Transport used for the failed model call (http or websocket). - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("transport")] - public ModelCallFailureTransport? Transport { get; set; } -} - -/// Model API dispatch metadata for internal telemetry. -public sealed partial class ModelCallStartData -{ - /// Model identifier used for this API call, when known. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + /// Concrete model that attempted the phase. [JsonPropertyName("model")] - public string? Model { get; set; } + public required 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; } + /// Stable identifier for the failed phase. + [JsonPropertyName("phaseId")] + public required string PhaseId { get; set; } - /// Identifier of the assistant turn that initiated the model call. - [JsonPropertyName("turnId")] - public required string TurnId { get; set; } -} + /// Kind of phase that failed. + [JsonPropertyName("phaseKind")] + public required FusionPhaseKind PhaseKind { get; set; } -/// Turn abort information including the reason for termination. -public sealed partial class AbortData -{ - /// Finite reason code describing why the current turn was aborted. + /// Stable machine-readable reason for the phase failure. [JsonPropertyName("reason")] - public required AbortReason Reason { get; set; } -} + public required string Reason { get; set; } -/// User-initiated tool invocation request with tool name and arguments. -public sealed partial class ToolUserRequestedData -{ - /// Arguments for the tool invocation. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("arguments")] - public JsonElement? Arguments { get; set; } + /// Semantic role assigned to the failed phase. + [JsonPropertyName("role")] + public required string Role { get; set; } - /// Unique identifier for this tool call. - [JsonPropertyName("toolCallId")] - public required string ToolCallId { get; set; } + /// Durable outcome status of the phase. + [JsonPropertyName("status")] + public required FusionPhaseStatus Status { get; set; } - /// Name of the tool the user wants to invoke. - [JsonPropertyName("toolName")] - public required string ToolName { get; set; } + /// Aggregate concrete-model usage consumed before the failure. + [JsonPropertyName("usage")] + public required FusionPhaseUsage Usage { get; set; } } -/// Tool execution startup details including MCP server information when applicable. -public sealed partial class ToolExecutionStartData +/// Live progress signal for a provider-hosted server tool (e.g. hosted web search) while it runs, before the finalized serverTools envelope lands on the terminal assistant.message. +public sealed partial class AssistantServerToolProgressData { - /// Arguments passed to the tool. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("arguments")] - public JsonElement? Arguments { get; set; } - - /// When true, the tool output should be displayed expanded (verbatim) in the CLI timeline. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("displayVerbatim")] - public bool? DisplayVerbatim { get; set; } + /// Kind of hosted server tool that is running. Only `web_search` is emitted today. + [JsonPropertyName("kind")] + public required string Kind { get; set; } - /// Name of the MCP server hosting this tool, when the tool is an MCP tool. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("mcpServerName")] - public string? McpServerName { get; set; } + /// Position of the hosted tool call in the response output. Stable across the call's lifecycle events (unlike the provider's per-event item id, which CAPI rotates), so the host keys the live in-progress row on it. + [JsonPropertyName("outputIndex")] + public required long OutputIndex { get; set; } - /// Original tool name on the MCP server, when the tool is an MCP tool. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("mcpToolName")] - public string? McpToolName { get; set; } + /// Lifecycle status of the hosted call: `in_progress`, `searching`, or `completed`. + [JsonPropertyName("status")] + public required string Status { get; set; } +} - /// Model identifier that generated this tool call. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("model")] - public string? Model { get; set; } +/// Assistant reasoning content for timeline display with complete thinking text. +public sealed partial class AssistantReasoningData +{ + /// The complete extended thinking text from the model. + [JsonPropertyName("content")] + public required string Content { get; set; } - /// Tool call ID of the parent tool invocation when this event originates from a sub-agent. - [EditorBrowsable(EditorBrowsableState.Never)] -#if NET5_0_OR_GREATER - [Obsolete("This member is deprecated and will be removed in a future version.", DiagnosticId = "GHCP001")] -#endif - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("parentToolCallId")] - public string? ParentToolCallId { get; set; } + /// Unique identifier for this reasoning block. + [JsonPropertyName("reasoningId")] + public required string ReasoningId { get; set; } - /// Gets or sets the rte value. + /// Per-request treatment/eligibility signal returned by the Copilot API in the `X-GitHub-Copilot-Request-TE` response header for the associated model call; `false` when the header was absent or unparseable. [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")] - public ToolExecutionStartShellToolInfo? ShellToolInfo { get; set; } +/// Streaming reasoning delta for incremental extended thinking updates. +public sealed partial class AssistantReasoningDeltaData +{ + /// Incremental text chunk to append to the reasoning content. + [JsonPropertyName("deltaContent")] + public required string DeltaContent { get; set; } - /// Unique identifier for this tool call. + /// Reasoning block ID this delta belongs to, matching the corresponding assistant.reasoning event. + [JsonPropertyName("reasoningId")] + public required string ReasoningId { get; set; } +} + +/// Streaming tool-call input delta for incremental tool-call updates. +public sealed partial class AssistantToolCallDeltaData +{ + /// Raw provider tool input fragment to append for this tool call. Function/tool-use providers stream serialized JSON argument text (so newlines inside JSON string values may appear as escaped `\n` until the accumulated JSON is parsed); custom tool calls stream raw custom input. + [JsonPropertyName("inputDelta")] + public required string InputDelta { get; set; } + + /// Tool call ID this delta belongs to, matching the corresponding assistant.message tool request. [JsonPropertyName("toolCallId")] public required string ToolCallId { get; set; } - /// Tool definition metadata, present for MCP tools with MCP Apps support. + /// Name of the tool being invoked, when known from the stream. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolDescription")] - public ToolExecutionStartToolDescription? ToolDescription { get; set; } - - /// Name of the tool being executed. [JsonPropertyName("toolName")] - public required string ToolName { get; set; } + public string? ToolName { get; set; } - /// Identifier for the agent loop turn this tool was invoked in, matching the corresponding assistant.turn_start event. + /// Tool call type, when known from the stream. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("turnId")] - public string? TurnId { get; set; } + [JsonPropertyName("toolType")] + public AssistantMessageToolRequestType? ToolType { get; set; } } -/// Streaming tool execution output for incremental result display. -public sealed partial class ToolExecutionPartialResultData +/// Streaming response progress with cumulative byte count. +public sealed partial class AssistantStreamingDeltaData { - /// Incremental output chunk from the running tool. - [JsonPropertyName("partialOutput")] - public required string PartialOutput { get; set; } - - /// Tool call ID this partial result belongs to. - [JsonPropertyName("toolCallId")] - public required string ToolCallId { get; set; } + /// Cumulative total bytes received from the streaming response so far. + [JsonPropertyName("totalResponseSizeBytes")] + public required long TotalResponseSizeBytes { get; set; } } -/// Tool execution progress notification with status message. -public sealed partial class ToolExecutionProgressData +/// Assistant response containing text content, optional tool requests, and interaction metadata. +public sealed partial class AssistantMessageData { - /// Human-readable progress status message (e.g., from an MCP server). - [JsonPropertyName("progressMessage")] - public required string ProgressMessage { get; set; } + /// Provider's completion / response identifier; shared across all chunks of a single API call. Used to group multi-chunk assistant utterances. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("apiCallId")] + public string? ApiCallId { get; set; } - /// Tool call ID this progress notification belongs to. - [JsonPropertyName("toolCallId")] - public required string ToolCallId { 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; } -/// Tool execution completion results including success status, detailed output, and error information. -public sealed partial class ToolExecutionCompleteData -{ - /// Error details when the tool execution failed. + /// 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("error")] - public ToolExecutionCompleteError? Error { get; set; } + [JsonPropertyName("chunkIndex")] + public long? ChunkIndex { get; set; } - /// CAPI interaction ID for correlating this tool execution with upstream telemetry. + /// 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)] - [JsonPropertyName("interactionId")] - public string? InteractionId { get; set; } + [JsonPropertyName("citations")] + public Citations? Citations { get; set; } - /// Whether this tool call was explicitly requested by the user rather than the assistant. + /// Client-minted request id (x-request-id header) echoed by the server. Distinct from requestId (x-github-request-id) and serviceRequestId (x-copilot-service-request-id). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("isUserRequested")] - public bool? IsUserRequested { get; set; } + [JsonPropertyName("clientRequestId")] + public string? ClientRequestId { get; set; } - /// FIDES IFC label projected from tool ingress metadata (MCP `CallToolResult._meta` or synthesized built-in ingress labels). Persisted as `{ ifc: ... }` so the label survives session resume, including model-visible failure results. Experimental. + /// The assistant's text response content. + [JsonPropertyName("content")] + public required string Content { get; set; } + + /// Encrypted reasoning content from OpenAI models. Session-bound and stripped on resume. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("encryptedContent")] + public string? EncryptedContent { get; set; } + + /// Experimental HydraFusion source attribution for this ordinary authoritative assistant message. [Experimental(Diagnostics.Experimental)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("mcpMeta")] - public JsonElement? McpMeta { get; set; } + [JsonPropertyName("fusion")] + public FusionAttribution? Fusion { get; set; } - /// Model identifier that generated this tool call. + /// CAPI interaction ID for correlating this message with upstream telemetry. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("interactionId")] + public string? InteractionId { get; set; } + + /// Unique identifier for this assistant message. + [JsonPropertyName("messageId")] + public required string MessageId { get; set; } + + /// Model that produced this assistant message, if known. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("model")] public string? Model { get; set; } + /// Actual output token count from the API response (completion_tokens), used for accurate token accounting. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("outputTokens")] + public long? OutputTokens { get; set; } + /// Tool call ID of the parent tool invocation when this event originates from a sub-agent. [EditorBrowsable(EditorBrowsableState.Never)] #if NET5_0_OR_GREATER @@ -3415,3599 +3728,3295 @@ public sealed partial class ToolExecutionCompleteData [JsonPropertyName("parentToolCallId")] public string? ParentToolCallId { get; set; } - /// Tool execution result on success. + /// Generation phase for phased-output models (e.g., thinking vs. response phases). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("result")] - public ToolExecutionCompleteResult? Result { get; set; } + [JsonPropertyName("phase")] + public string? Phase { get; set; } - /// Gets or sets the rte value. + /// Neutral provider-tagged reasoning content blocks preserved verbatim for round-tripping. `reasoningText` and `reasoningOpaque` are a lossy derived view of these blocks, retained for display. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("rte")] - public bool? Rte { get; set; } + [JsonPropertyName("reasoningBlocks")] + public AssistantMessageReasoningBlocks? ReasoningBlocks { get; set; } - /// Whether this tool execution ran inside a sandbox container. + /// Opaque/encrypted extended thinking data from Anthropic models. Session-bound and stripped on resume. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("sandboxed")] - public bool? Sandboxed { get; set; } - - /// Whether the tool execution completed successfully. - [JsonPropertyName("success")] - public required bool Success { get; set; } - - /// Unique identifier for the completed tool call. - [JsonPropertyName("toolCallId")] - public required string ToolCallId { get; set; } + [JsonPropertyName("reasoningOpaque")] + public string? ReasoningOpaque { get; set; } - /// Tool definition metadata, present for MCP tools with MCP Apps support. + /// Readable reasoning text from the model's extended thinking. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolDescription")] - public ToolExecutionCompleteToolDescription? ToolDescription { get; set; } + [JsonPropertyName("reasoningText")] + public string? ReasoningText { get; set; } - /// Tool-specific telemetry data (e.g., CodeQL check counts, grep match counts). + /// OpenAI-compatible wire field the provider used for reasoning (e.g. reasoning_content/reasoning). Populated only when non-canonical, so the dialect round-trips across turns. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolTelemetry")] - public IDictionary? ToolTelemetry { get; set; } + [JsonPropertyName("reasoningWireField")] + public string? ReasoningWireField { get; set; } - /// Identifier for the agent loop turn this tool was invoked in, matching the corresponding assistant.turn_start event. + /// GitHub request tracing ID (x-github-request-id header) for correlating with server-side logs. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("requestId")] + public string? RequestId { get; set; } + + /// Per-request treatment/eligibility signal returned by the Copilot API in the `X-GitHub-Copilot-Request-TE` response header for the associated model call; `false` when the header was absent or unparseable. + [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")] + public AssistantMessageServerTools? ServerTools { get; set; } + + /// Copilot service request ID (x-copilot-service-request-id header) for CAPI log correlation. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("serviceRequestId")] + public string? ServiceRequestId { get; set; } + + /// Tool invocations requested by the assistant in this message. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolRequests")] + public AssistantMessageToolRequest[]? ToolRequests { get; set; } + + /// Identifier for the agent loop turn that produced this message, matching the corresponding assistant.turn_start event. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("turnId")] public string? TurnId { get; set; } } -/// Persisted generic client-side tool activations restored when a session resumes. -public sealed partial class ToolSearchActivatedData +/// Streaming assistant message start metadata. +public sealed partial class AssistantMessageStartData { - /// Tool-search strategy that activated the definitions. - [JsonPropertyName("strategy")] - public required string Strategy { get; set; } + /// Message ID this start event belongs to, matching subsequent deltas and assistant.message. + [JsonPropertyName("messageId")] + public required string MessageId { get; set; } - /// Names of tool definitions activated by this search invocation. - [JsonPropertyName("toolNames")] - public required string[] ToolNames { get; set; } + /// Generation phase this message belongs to for phased-output models. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("phase")] + public string? Phase { get; set; } } -/// Skill invocation details including content, allowed tools, and plugin metadata. -public sealed partial class SkillInvokedData +/// Streaming assistant message delta for incremental response updates. +public sealed partial class AssistantMessageDeltaData { - /// Tool names that should be auto-approved when this skill is active. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("allowedTools")] - public string[]? AllowedTools { get; set; } + /// Incremental text chunk to append to the message content. + [JsonPropertyName("deltaContent")] + public required string DeltaContent { get; set; } - /// Full content of the skill file, injected into the conversation for the model. - [JsonPropertyName("content")] - public required string Content { get; set; } + /// Message ID this delta belongs to, matching the corresponding assistant.message event. + [JsonPropertyName("messageId")] + public required string MessageId { get; set; } - /// Description of the skill from its SKILL.md frontmatter. + /// Tool call ID of the parent tool invocation when this event originates from a sub-agent. + [EditorBrowsable(EditorBrowsableState.Never)] +#if NET5_0_OR_GREATER + [Obsolete("This member is deprecated and will be removed in a future version.", DiagnosticId = "GHCP001")] +#endif [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("description")] - public string? Description { get; set; } + [JsonPropertyName("parentToolCallId")] + public string? ParentToolCallId { get; set; } +} - /// Model identifier active when the skill was invoked, when known. +/// Turn completion metadata including the turn identifier. +public sealed partial class AssistantTurnEndData +{ + /// Model identifier used for this turn, when known. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("model")] public string? Model { get; set; } - /// Name of the invoked skill. - [JsonPropertyName("name")] - public required string Name { get; set; } + /// Identifier of the turn that has ended, matching the corresponding assistant.turn_start event. + [JsonPropertyName("turnId")] + public required string TurnId { get; set; } +} - /// File path to the SKILL.md definition. - [JsonPropertyName("path")] - public required string Path { get; set; } +/// Payload emitted whenever the main agent's processing loop goes idle, including while related background work (running agents or in-flight attached shell commands) is still pending and the session-level idle event is therefore deferred. +public sealed partial class AssistantIdleData +{ + /// True when the preceding agentic loop was cancelled via abort signal. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("aborted")] + public bool? Aborted { get; set; } +} - /// Name of the plugin this skill originated from, when applicable. +/// LLM API call usage metrics including tokens, costs, quotas, and billing information. +public sealed partial class AssistantUsageData +{ + /// Number of accepted speculative prediction tokens. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("pluginName")] - public string? PluginName { get; set; } + [JsonPropertyName("acceptedPredictionTokens")] + public long? AcceptedPredictionTokens { get; set; } - /// Version of the plugin this skill originated from, when applicable. + /// Completion ID from the model provider (e.g., chatcmpl-abc123). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("pluginVersion")] - public string? PluginVersion { get; set; } + [JsonPropertyName("apiCallId")] + public string? ApiCallId { get; set; } - /// Source identifier for where the skill was discovered. Known values include: project (workspace skill), inherited (parent-directory skill), personal-copilot (~/.copilot/skills), personal-agents (~/.agents/skills), custom (configured directory), plugin (installed plugin), builtin (bundled runtime skill), and remote (org/enterprise skill). + /// API endpoint used for this model call, matching CAPI supported_endpoints vocabulary. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("source")] - public string? Source { get; set; } + [JsonPropertyName("apiEndpoint")] + public AssistantUsageApiEndpoint? ApiEndpoint { get; set; } - /// What triggered the skill invocation: `user-invoked` (explicit user action, such as via a slash command or UI affordance), `agent-invoked` (agent requested the skill), or `context-load` (loaded as part of another context, such as preloading skills configured on a custom agent or subagent). + /// Number of tools available to the model for this call. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("trigger")] - public SkillInvokedTrigger? Trigger { get; set; } -} + [JsonInclude] + [JsonPropertyName("availableToolCount")] + internal long? AvailableToolCount { get; set; } -/// Sub-agent startup details including parent tool call and agent information. -public sealed partial class SubagentStartedData -{ - /// Description of what the sub-agent does. - [JsonPropertyName("agentDescription")] - public required string AgentDescription { get; set; } + /// Whether the provider reported prompt-cache usage details for this call. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonInclude] + [JsonPropertyName("cacheDetailsReported")] + internal bool? CacheDetailsReported { get; set; } - /// Human-readable display name of the sub-agent. - [JsonPropertyName("agentDisplayName")] - public required string AgentDisplayName { 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")] + public DateTimeOffset? CacheExpiresAt { get; set; } - /// Internal name of the sub-agent. - [JsonPropertyName("agentName")] - public required string AgentName { get; set; } + /// Number of tokens read from prompt cache. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("cacheReadTokens")] + public long? CacheReadTokens { get; set; } - /// Model the sub-agent will run with, when known at start. + /// Effective prompt-cache lifetime in seconds for this call. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("model")] - public string? Model { get; set; } + [JsonInclude] + [JsonPropertyName("cacheTtlSeconds")] + internal long? CacheTtlSeconds { get; set; } - /// Tool call ID of the parent tool invocation that spawned this sub-agent. - [JsonPropertyName("toolCallId")] - public required string ToolCallId { get; set; } -} + /// Number of tokens written to prompt cache. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("cacheWriteTokens")] + public long? CacheWriteTokens { get; set; } -/// Sub-agent completion details for successful execution. -public sealed partial class SubagentCompletedData -{ - /// Human-readable display name of the sub-agent. - [JsonPropertyName("agentDisplayName")] - public required string AgentDisplayName { get; set; } + /// 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. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("contentFilterTriggered")] + public bool? ContentFilterTriggered { get; set; } - /// Internal name of the sub-agent. - [JsonPropertyName("agentName")] - public required string AgentName { get; set; } + /// Per-request cost and usage data from the CAPI copilot_usage response field. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("copilotUsage")] + public AssistantUsageCopilotUsage? CopilotUsage { get; set; } - /// Wall-clock duration of the sub-agent execution in milliseconds. + /// Model multiplier cost for billing purposes. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("cost")] + public double? Cost { get; set; } + + /// Duration of the API call in milliseconds. [JsonConverter(typeof(MillisecondsTimeSpanConverter))] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("durationMs")] + [JsonPropertyName("duration")] public TimeSpan? Duration { get; set; } - /// Model used by the sub-agent. + /// 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". [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("model")] - public string? Model { get; set; } + [JsonPropertyName("finishReason")] + public string? FinishReason { get; set; } - /// Tool call ID of the parent tool invocation that spawned this sub-agent. - [JsonPropertyName("toolCallId")] - public required string ToolCallId { get; set; } + /// How the prompt-cache frontier was determined for this call. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonInclude] + [JsonPropertyName("frontierSource")] + internal string? FrontierSource { get; set; } - /// Total tokens (input + output) consumed by the sub-agent. + /// Experimental HydraFusion attribution for this concrete model call's usage. + [Experimental(Diagnostics.Experimental)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("totalTokens")] - public long? TotalTokens { get; set; } + [JsonPropertyName("fusion")] + public FusionAttribution? Fusion { get; set; } - /// Total number of tool calls made by the sub-agent. + /// What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("totalToolCalls")] - public long? TotalToolCalls { get; set; } -} + [JsonPropertyName("initiator")] + public string? Initiator { get; set; } -/// Sub-agent failure details including error message and agent information. -public sealed partial class SubagentFailedData -{ - /// Human-readable display name of the sub-agent. - [JsonPropertyName("agentDisplayName")] - public required string AgentDisplayName { get; set; } + /// Number of input tokens consumed. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("inputTokens")] + public long? InputTokens { get; set; } - /// Internal name of the sub-agent. - [JsonPropertyName("agentName")] - public required string AgentName { 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; } - /// Wall-clock duration of the sub-agent execution in milliseconds. + /// Average inter-token latency in milliseconds. Only available for streaming requests. [JsonConverter(typeof(MillisecondsTimeSpanConverter))] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("durationMs")] - public TimeSpan? Duration { get; set; } - - /// Error message describing why the sub-agent failed. - [JsonPropertyName("error")] - public required string Error { get; set; } + [JsonPropertyName("interTokenLatencyMs")] + public TimeSpan? InterTokenLatency { get; set; } - /// Model selected for the sub-agent, when known. + /// Whether Auto mode was selected for this model call. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("model")] - public string? Model { get; set; } + [JsonPropertyName("isAuto")] + public bool? IsAuto { get; set; } - /// Tool call ID of the parent tool invocation that spawned this sub-agent. - [JsonPropertyName("toolCallId")] - public required string ToolCallId { get; set; } + /// Whether this model call used a bring-your-own-key provider. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("isByok")] + public bool? IsByok { get; set; } - /// Total tokens (input + output) consumed before the sub-agent failed. + /// Requested maximum output tokens used for this model call. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("totalTokens")] - public long? TotalTokens { get; set; } + [JsonPropertyName("maxOutputTokens")] + public long? MaxOutputTokens { get; set; } - /// Total number of tool calls made before the sub-agent failed. + /// Effective maximum prompt-token limit used for this model call. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("totalToolCalls")] - public long? TotalToolCalls { get; set; } -} + [JsonPropertyName("maxPromptTokens")] + public long? MaxPromptTokens { get; set; } -/// Custom agent selection details including name and available tools. -public sealed partial class SubagentSelectedData -{ - /// Human-readable display name of the selected custom agent. - [JsonPropertyName("agentDisplayName")] - public required string AgentDisplayName { get; set; } - - /// Internal name of the selected custom agent. - [JsonPropertyName("agentName")] - public required string AgentName { get; set; } - - /// List of tool names available to this agent, or null for all tools. - [JsonPropertyName("tools")] - public string[]? Tools { get; set; } -} - -/// Empty payload; the event signals that the custom agent was deselected, returning to the default agent. -public sealed partial class SubagentDeselectedData -{ -} - -/// Hook invocation start details including type and input data. -public sealed partial class HookStartData -{ - /// Unique identifier for this hook invocation. - [JsonPropertyName("hookInvocationId")] - public required string HookInvocationId { get; set; } + /// Model identifier used for this API call. + [JsonPropertyName("model")] + public required string Model { get; set; } - /// Type of hook being invoked (e.g., "preToolUse", "postToolUse", "sessionStart"). - [JsonPropertyName("hookType")] - public required string HookType { get; set; } + /// Number of tool calls returned by the model. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonInclude] + [JsonPropertyName("numToolCalls")] + internal long? NumToolCalls { get; set; } - /// Input data passed to the hook. + /// Number of output tokens produced. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("input")] - public JsonElement? Input { get; set; } -} + [JsonPropertyName("outputTokens")] + public long? OutputTokens { get; set; } -/// Hook invocation completion details including output, success status, and error information. -public sealed partial class HookEndData -{ - /// Error details when the hook failed. + /// Time to first observable model output in milliseconds. Includes text, reasoning, and tool-call output; only available for streaming requests that produce observable output. + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("error")] - public HookEndError? Error { get; set; } + [JsonPropertyName("outputTtftMs")] + public TimeSpan? OutputTtft { get; set; } - /// Identifier matching the corresponding hook.start event. - [JsonPropertyName("hookInvocationId")] - public required string HookInvocationId { get; set; } + /// Parent tool call ID when this usage originates from a sub-agent. + [EditorBrowsable(EditorBrowsableState.Never)] +#if NET5_0_OR_GREATER + [Obsolete("This member is deprecated and will be removed in a future version.", DiagnosticId = "GHCP001")] +#endif + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("parentToolCallId")] + public string? ParentToolCallId { get; set; } - /// Type of hook that was invoked (e.g., "preToolUse", "postToolUse", "sessionStart"). - [JsonPropertyName("hookType")] - public required string HookType { get; set; } + /// GitHub request tracing ID (x-github-request-id header) for server-side log correlation. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("providerCallId")] + public string? ProviderCallId { get; set; } - /// Output data produced by the hook. + /// Per-quota resource usage snapshots, keyed by quota identifier. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("output")] - public JsonElement? Output { get; set; } + [JsonInclude] + [JsonPropertyName("quotaSnapshots")] + internal IDictionary? QuotaSnapshots { get; set; } - /// Whether the hook completed successfully. - [JsonPropertyName("success")] - public required bool Success { get; set; } -} + /// Reasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh", "max"). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("reasoningEffort")] + public string? ReasoningEffort { get; set; } -/// Ephemeral progress update from a running hook process. -public sealed partial class HookProgressData -{ - /// Human-readable progress message from the hook process. - [JsonPropertyName("message")] - public required string Message { get; set; } + /// Reasoning summary mode used for this model call, if applicable. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("reasoningSummary")] + public ReasoningSummary? ReasoningSummary { get; set; } - /// When true, this status message replaces the previous temporary one instead of accumulating. + /// Number of output tokens used for reasoning (e.g., chain-of-thought). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("temporary")] - public bool? Temporary { get; set; } -} + [JsonPropertyName("reasoningTokens")] + public long? ReasoningTokens { get; set; } -/// Canonical bytes for a content-addressed binary asset shared by reference across events. -public sealed partial class SessionBinaryAssetData -{ - /// Content-addressed id for this binary asset (e.g. "sha256:..."). - [JsonPropertyName("assetId")] - public required string AssetId { get; set; } + /// Number of rejected speculative prediction tokens. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("rejectedPredictionTokens")] + public long? RejectedPredictionTokens { get; set; } - /// Decoded byte length of the binary asset. - [JsonPropertyName("byteLength")] - public required long ByteLength { get; set; } + /// Per-request treatment/eligibility signal returned by the Copilot API in the `X-GitHub-Copilot-Request-TE` response header for the associated model call; `false` when the header was absent or unparseable. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("rte")] + public bool? Rte { get; set; } - /// Base64-encoded binary data. - [Base64String] - [JsonPropertyName("data")] - public required string Data { get; set; } + /// Copilot service request ID (x-copilot-service-request-id header) for CAPI log correlation. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("serviceRequestId")] + public string? ServiceRequestId { get; set; } - /// Human-readable description of the binary data. + /// Time to first token in milliseconds. Only available for streaming requests. + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("description")] - public string? Description { get; set; } + [JsonPropertyName("timeToFirstTokenMs")] + public TimeSpan? TimeToFirstToken { get; set; } - /// Optional metadata from the producing tool. + /// Tool-call counts keyed by tool name. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("metadata")] - public IDictionary? Metadata { get; set; } + [JsonInclude] + [JsonPropertyName("toolCounts")] + internal IDictionary? ToolCounts { get; set; } - /// MIME type of the binary asset. - [JsonPropertyName("mimeType")] - public required string MimeType { get; set; } + /// Number of tokens used by tool definitions for this call. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonInclude] + [JsonPropertyName("toolTokenCount")] + internal long? ToolTokenCount { get; set; } - /// Binary asset type discriminator. Use "image" for images and "resource" otherwise. - [JsonPropertyName("type")] - public required BinaryAssetType Type { get; set; } + /// Transport used for this model call (http or websocket). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("transport")] + public AssistantUsageTransport? Transport { get; set; } } -/// System/developer instruction content with role and optional template metadata. -public sealed partial class SystemMessageData +/// A detected loss of a previously cached prompt prefix. +public sealed partial class PromptCacheBreakData { - /// The system or developer prompt text sent as model input. - [JsonPropertyName("content")] - public required string Content { get; set; } - - /// Logical interaction identifier for the model run receiving this prompt. + /// Request state whose cached prefix fell short. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("interactionId")] - public string? InteractionId { get; set; } + [JsonInclude] + [JsonPropertyName("afterRequest")] + internal JsonElement? AfterRequest { get; set; } - /// Metadata about the prompt template and its construction. + /// Name of the sub-agent whose conversation broke, stamped by the parent bridge. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("metadata")] - public SystemMessageMetadata? Metadata { get; set; } + [JsonInclude] + [JsonPropertyName("agentName")] + internal string? AgentName { get; set; } - /// Optional name identifier for the message source. + /// Request state that established the prior cache frontier. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("name")] - public string? Name { get; set; } + [JsonInclude] + [JsonPropertyName("beforeRequest")] + internal JsonElement? BeforeRequest { get; set; } - /// Message role: "system" for system prompts, "developer" for developer-injected instructions. - [JsonPropertyName("role")] - public required SystemMessageRole Role { get; set; } -} + /// Names of the cache-configuration fields that changed. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonInclude] + [JsonPropertyName("cacheConfigChangedFields")] + internal string[]? CacheConfigChangedFields { get; set; } -/// System-generated notification for runtime events like background task completion. -public sealed partial class SystemNotificationData -{ - /// The notification text, typically wrapped in <system_notification> XML tags. - [JsonPropertyName("content")] - public required string Content { get; set; } + /// All reasons that contributed to the cache break, ordered by precedence. + [JsonPropertyName("contributingReasons")] + public required string[] ContributingReasons { get; set; } - /// Structured metadata identifying what triggered this notification. - [JsonPropertyName("kind")] - public required SystemNotification Kind { get; set; } -} + /// Prior cached prompt frontier in tokens. + [JsonPropertyName("frontierTokens")] + public required long FrontierTokens { get; set; } -/// Permission request notification requiring client approval with request details. -public sealed partial class PermissionRequestedData -{ - /// Details of the permission being requested. - [JsonPropertyName("permissionRequest")] - public required PermissionRequest PermissionRequest { get; set; } + /// Model that held the prior cache frontier, when the call changed models. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonInclude] + [JsonPropertyName("modelFrom")] + internal string? ModelFrom { get; set; } - /// Derived user-facing permission prompt details for UI consumers. + /// Model this call targeted, when the call changed models. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("promptRequest")] - public PermissionPromptRequest? PromptRequest { get; set; } + [JsonInclude] + [JsonPropertyName("modelTo")] + internal string? ModelTo { get; set; } - /// Unique identifier for this permission request; used to respond via session.respondToPermission(). - [JsonPropertyName("requestId")] - public required string RequestId { get; set; } + /// The highest-precedence reason for the cache break. + [JsonPropertyName("primaryReason")] + public required string PrimaryReason { get; set; } - /// When true, this permission was already resolved by a permissionRequest hook and requires no client action. + /// Fraction of the prior cache frontier that survived. + [JsonPropertyName("retentionRatio")] + public required double RetentionRatio { get; set; } + + /// Index of the first conversation message whose content changed. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("resolvedByHook")] - public bool? ResolvedByHook { get; set; } + [JsonInclude] + [JsonPropertyName("rewriteMessageIndex")] + internal long? RewriteMessageIndex { 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. + /// Shape of the history rewrite, for example whether the history grew or shrank. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("riskAssessment")] - public JsonElement? RiskAssessment { get; set; } -} + [JsonInclude] + [JsonPropertyName("rewriteShape")] + internal string? RewriteShape { get; set; } -/// Permission request completion notification signaling UI dismissal. -public sealed partial class PermissionCompletedData -{ - /// Request ID of the resolved permission request; clients should dismiss any UI for this request. - [JsonPropertyName("requestId")] - public required string RequestId { get; set; } + /// Subsystems that announced a history rewrite before this call, for example compaction or truncation. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonInclude] + [JsonPropertyName("rewriteSource")] + internal string[]? RewriteSource { get; set; } - /// The result of the permission request. - [JsonPropertyName("result")] - public required PermissionResult Result { get; set; } + /// Cached prefix tokens lost since the prior call. + [JsonPropertyName("shortfallTokens")] + public required long ShortfallTokens { get; set; } - /// Optional tool call ID associated with this permission prompt; clients may use it to correlate UI created from tool-scoped prompts. + /// Number of cached prefix tokens that survived. + [JsonPropertyName("survivedTokens")] + public required long SurvivedTokens { get; set; } + + /// Names of the system-prompt segments whose content changed. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } -} + [JsonInclude] + [JsonPropertyName("systemSegmentsChanged")] + internal string[]? SystemSegmentsChanged { get; set; } -/// User input request notification with question and optional predefined choices. -public sealed partial class UserInputRequestedData -{ - /// Whether the user can provide a free-form text response in addition to predefined choices. + /// Telemetry-safe names of tools added since the prior call. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("allowFreeform")] - public bool? AllowFreeform { get; set; } + [JsonInclude] + [JsonPropertyName("toolsAdded")] + internal string[]? ToolsAdded { get; set; } - /// Predefined choices for the user to select from, if applicable. + /// Raw names of tools added since the prior call, restricted because a tool name can be user-authored. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("choices")] - public string[]? Choices { get; set; } + [JsonInclude] + [JsonPropertyName("toolsAddedRaw")] + internal string[]? ToolsAddedRaw { get; set; } - /// The question or prompt to present to the user. - [JsonPropertyName("question")] - public required string Question { get; set; } + /// Telemetry-safe names of tools whose definition changed since the prior call. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonInclude] + [JsonPropertyName("toolsRedefined")] + internal string[]? ToolsRedefined { get; set; } - /// Unique identifier for this input request; used to respond via session.respondToUserInput(). - [JsonPropertyName("requestId")] - public required string RequestId { get; set; } + /// Raw names of tools redefined since the prior call, restricted because a tool name can be user-authored. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonInclude] + [JsonPropertyName("toolsRedefinedRaw")] + internal string[]? ToolsRedefinedRaw { get; set; } - /// The LLM-assigned tool call ID that triggered this request; used by remote UIs to correlate responses. + /// Telemetry-safe names of tools removed since the prior call. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } -} + [JsonInclude] + [JsonPropertyName("toolsRemoved")] + internal string[]? ToolsRemoved { get; set; } -/// User input request completion with the user's response. -public sealed partial class UserInputCompletedData -{ - /// The user's answer to the input request. + /// Raw names of tools removed since the prior call, restricted because a tool name can be user-authored. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("answer")] - public string? Answer { get; set; } + [JsonInclude] + [JsonPropertyName("toolsRemovedRaw")] + internal string[]? ToolsRemovedRaw { get; set; } - /// Request ID of the resolved user input request; clients should dismiss any UI for this request. - [JsonPropertyName("requestId")] - public required string RequestId { get; set; } - - /// Whether the answer was typed as free-form text rather than selected from choices. + /// Whether the tool list kept its members but changed their order. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("wasFreeform")] - public bool? WasFreeform { get; set; } + [JsonInclude] + [JsonPropertyName("toolsReordered")] + internal bool? ToolsReordered { get; set; } } -/// Elicitation request; may be form-based (structured input) or URL-based (browser redirect). -public sealed partial class ElicitationRequestedData +/// Failed LLM API call metadata for telemetry. +public sealed partial class ModelCallFailureData { - /// The source that initiated the request (MCP server name, or absent for agent-initiated). + /// Completion ID from the model provider (e.g., chatcmpl-abc123). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("elicitationSource")] - public string? ElicitationSource { get; set; } + [JsonPropertyName("apiCallId")] + public string? ApiCallId { get; set; } - /// Message describing what information is needed from the user. - [JsonPropertyName("message")] - public required string Message { get; set; } + /// API endpoint used for this model call, matching CAPI supported_endpoints vocabulary. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("apiEndpoint")] + public AssistantUsageApiEndpoint? ApiEndpoint { get; set; } - /// Elicitation mode; "form" for structured input, "url" for browser-based. Defaults to "form" when absent. + /// For HTTP 400 failures only: whether the response carried a structured CAPI error envelope (structured_error, a deterministic validation failure) or no error body (bodyless, the transient gateway/proxy signature). Absent for non-400 failures. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("mode")] - public ElicitationRequestedMode? Mode { get; set; } + [JsonPropertyName("badRequestKind")] + public ModelCallFailureBadRequestKind? BadRequestKind { get; set; } - /// JSON Schema describing the form fields to present to the user (form mode only). + /// Duration of the failed API call in milliseconds. + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("requestedSchema")] - public ElicitationRequestedSchema? RequestedSchema { get; set; } + [JsonPropertyName("durationMs")] + public TimeSpan? Duration { get; set; } - /// Unique identifier for this elicitation request; used to respond via session.respondToElicitation(). - [JsonPropertyName("requestId")] - public required string RequestId { get; set; } + /// For HTTP 400 failures only: the `code` from the CAPI error envelope (e.g. 'model_max_prompt_tokens_exceeded') identifying which deterministic validation failure occurred. Raw server-controlled string, emitted only through restricted telemetry. Absent for bodyless or non-400 failures. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("errorCode")] + public string? ErrorCode { get; set; } - /// Tool call ID from the LLM completion; used to correlate with CompletionChunk.toolCall.id for remote UIs. + /// Raw provider/runtime error message for restricted telemetry. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } + [JsonPropertyName("errorMessage")] + public string? ErrorMessage { get; set; } - /// URL to open in the user's browser (url mode only). + /// For HTTP 400 failures only: the `type` from the CAPI error envelope (e.g. 'websocket_error'), a coarser companion to errorCode for envelopes that carry no code. Raw server-controlled string, emitted only through restricted telemetry. Absent for bodyless or non-400 failures. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("url")] - public string? Url { get; set; } -} + [JsonPropertyName("errorType")] + public string? ErrorType { get; set; } -/// Elicitation request completion with the user's response. -public sealed partial class ElicitationCompletedData -{ - /// The user action: "accept" (submitted form), "decline" (explicitly refused), or "cancel" (dismissed). + /// Whether the failure originated from an API response or the request transport. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("action")] - public ElicitationCompletedAction? Action { get; set; } + [JsonPropertyName("failureKind")] + public ModelCallFailureKind? FailureKind { get; set; } - /// The submitted form data when action is 'accept'; keys match the requested schema fields. + /// Experimental HydraFusion attribution for this failed concrete model call. + [Experimental(Diagnostics.Experimental)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("content")] - public IDictionary? Content { get; set; } + [JsonPropertyName("fusion")] + public FusionAttribution? Fusion { get; set; } - /// Request ID of the resolved elicitation request; clients should dismiss any UI for this request. - [JsonPropertyName("requestId")] - public required string RequestId { get; set; } -} + /// What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("initiator")] + public string? Initiator { get; set; } -/// Sampling request from an MCP server; contains the server name and a requestId for correlation. -public sealed partial class SamplingRequestedData -{ - /// The JSON-RPC request ID from the MCP protocol. - [JsonPropertyName("mcpRequestId")] - public required JsonElement McpRequestId { get; set; } + /// Authoritative interaction classification for the failed call, matching `assistant.usage.interactionType` (for example `conversation-agent`, `conversation-subagent`, or `conversation-sampling`). Absent when the producer cannot classify the interaction. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("interactionType")] + public string? InteractionType { get; set; } - /// Unique identifier for this sampling request; used to respond via session.respondToSampling(). - [JsonPropertyName("requestId")] - public required string RequestId { get; set; } + /// Whether the session selected Auto mode for the failed call. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("isAuto")] + public bool? IsAuto { get; set; } - /// Name of the MCP server that initiated the sampling request. - [JsonPropertyName("serverName")] - public required string ServerName { get; set; } -} + /// Whether the failed call used a bring-your-own-key provider. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("isByok")] + public bool? IsByok { get; set; } -/// Sampling request completion notification signaling UI dismissal. -public sealed partial class SamplingCompletedData -{ - /// Request ID of the resolved sampling request; clients should dismiss any UI for this request. - [JsonPropertyName("requestId")] - public required string RequestId { get; set; } -} + /// Effective maximum output-token limit for the failed call. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("maxOutputTokens")] + public long? MaxOutputTokens { get; set; } -/// OAuth authentication request for an MCP server. -public sealed partial class McpOauthRequiredData -{ - /// Raw HTTP response details from the OAuth auth challenge, as observed by the runtime. Header order and casing are transport-dependent, and duplicate header names may appear multiple times. + /// Effective maximum prompt-token limit for the failed call. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("httpResponse")] - public McpOauthHttpResponse? HttpResponse { get; set; } + [JsonPropertyName("maxPromptTokens")] + public long? MaxPromptTokens { get; set; } - /// Why the runtime is requesting host-provided OAuth credentials. - [JsonPropertyName("reason")] - public required McpOauthRequestReason Reason { get; set; } + /// Model identifier used for the failed API call. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("model")] + public string? Model { get; set; } - /// Unique identifier for this OAuth request; used to respond via session.mcp.oauth.handlePendingRequest. - [JsonPropertyName("requestId")] - public required string RequestId { get; set; } + /// GitHub request tracing ID (x-github-request-id header) for server-side log correlation. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("providerCallId")] + public string? ProviderCallId { get; set; } - /// Raw OAuth protected-resource metadata document fetched for the MCP server, if available. + /// Per-quota usage snapshots parsed from the failed response's quota headers, keyed by quota identifier. Present when the error response carried quota headers (e.g. a 402 once the additional spend limit is reached) so the UI can refresh the quota display on failure. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("resourceMetadata")] - public string? ResourceMetadata { get; set; } + [JsonInclude] + [JsonPropertyName("quotaSnapshots")] + internal IDictionary? QuotaSnapshots { get; set; } - /// Display name of the MCP server that requires OAuth. - [JsonPropertyName("serverName")] - public required string ServerName { get; set; } + /// Reasoning effort level used for the failed model call, if applicable. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("reasoningEffort")] + public string? ReasoningEffort { get; set; } - /// URL of the MCP server that requires OAuth. - [JsonPropertyName("serverUrl")] - public required string ServerUrl { get; set; } + /// 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. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("requestFingerprint")] + public ModelCallFailureRequestFingerprint? RequestFingerprint { get; set; } - /// Static OAuth client configuration, if the server specifies one. + /// Per-request treatment/eligibility signal returned by the Copilot API in the `X-GitHub-Copilot-Request-TE` response header for the associated model call; `false` when the header was absent or unparseable. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("staticClientConfig")] - public McpOauthRequiredStaticClientConfig? StaticClientConfig { get; set; } + [JsonPropertyName("rte")] + public bool? Rte { get; set; } - /// OAuth WWW-Authenticate parameters parsed from the auth challenge, if available. + /// Copilot service request ID (x-copilot-service-request-id header) for CAPI log correlation. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("wwwAuthenticateParams")] - public McpOauthWWWAuthenticateParams? WwwAuthenticateParams { get; set; } -} + [JsonPropertyName("serviceRequestId")] + public string? ServiceRequestId { get; set; } -/// MCP OAuth request completion notification. -public sealed partial class McpOauthCompletedData -{ - /// How the pending OAuth request was completed. - [JsonPropertyName("outcome")] - public required McpOauthCompletionOutcome Outcome { get; set; } + /// Where the failed model call originated. + [JsonPropertyName("source")] + public required ModelCallFailureSource Source { get; set; } - /// Request ID of the resolved OAuth request. - [JsonPropertyName("requestId")] - public required string RequestId { get; set; } + /// HTTP status code from the failed request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("statusCode")] + public int? StatusCode { get; set; } + + /// Transport used for the failed model call (http or websocket). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("transport")] + public ModelCallFailureTransport? Transport { get; set; } } -/// Dynamic headers refresh request for a remote MCP server. -public sealed partial class McpHeadersRefreshRequiredData +/// Final lifecycle outcome for one logical model dispatch. A logical dispatch may include internal reconnect or fallback work, so event count is not provider HTTP-request count. +public sealed partial class ModelCallFinishedData { - /// Why dynamic headers are being requested. - [JsonPropertyName("reason")] - public required McpHeadersRefreshRequiredReason Reason { get; set; } + /// Whether an accepted successful response requested the exact name and command semantics of a built-in file edit tool, including an external tool explicitly replacing that built-in name. Absent when the logical dispatch did not produce an accepted response. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("containsBuiltInFileEditRequest")] + public bool? ContainsBuiltInFileEditRequest { get; set; } - /// Unique identifier for this headers refresh request; used to respond via session.mcp.headers.handlePendingHeadersRefreshRequest(). - [JsonPropertyName("requestId")] - public required string RequestId { get; set; } + /// Monotonic elapsed time spent in the logical model dispatch, including any internal transport reconnect or fallback and excluding orchestrator retry backoff, tool execution, confirmations, and post-response processing. + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] + [JsonPropertyName("dispatchDurationMs")] + public required TimeSpan DispatchDuration { get; set; } - /// Display name of the remote MCP server requesting headers. - [JsonPropertyName("serverName")] - public required string ServerName { get; set; } + /// Version of the built-in file-edit semantic classifier used for this event. + [JsonPropertyName("editClassifierVersion")] + public required long EditClassifierVersion { get; set; } - /// URL of the remote MCP server requesting headers. - [JsonPropertyName("serverUrl")] - public required string ServerUrl { get; set; } -} + /// Identifier of the user interaction that owns the model dispatch, matching assistant.turn_start.interactionId when available. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("interactionId")] + public string? InteractionId { get; set; } -/// MCP headers refresh request completion notification. -public sealed partial class McpHeadersRefreshCompletedData -{ - /// How the pending MCP headers refresh request resolved. + /// Final outcome after post-response acceptance processing. [JsonPropertyName("outcome")] - public required McpHeadersRefreshCompletedOutcome Outcome { get; set; } + public required ModelCallFinishedOutcome Outcome { get; set; } - /// Request ID of the resolved headers refresh request. - [JsonPropertyName("requestId")] - public required string RequestId { get; set; } + /// Agent-loop iteration within the interaction that initiated the model dispatch. + [JsonPropertyName("turnId")] + public required string TurnId { get; set; } } -/// Opaque custom notification data. Consumers may branch on source and name, but payload semantics are source-defined. -public sealed partial class SessionCustomNotificationData +/// Model API dispatch metadata for internal telemetry. +public sealed partial class ModelCallStartData { - /// Source-defined custom notification name. - [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 required string Name { get; set; } - - /// Source-defined JSON payload for the custom notification. - [JsonPropertyName("payload")] - public required JsonElement Payload { get; set; } - - /// Namespace for the custom notification producer. - [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("source")] - public required string Source { get; set; } + /// Experimental HydraFusion attribution for this concrete model call. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("fusion")] + public FusionAttribution? Fusion { get; set; } - /// Optional source-defined string identifiers describing the payload subject. + /// Model identifier used for this API call, when known. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("subject")] - public IDictionary? Subject { get; set; } + [JsonPropertyName("model")] + public string? Model { get; set; } - /// Optional source-defined payload schema version. + /// Previous response or interaction identifier included in the model request, when present. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("version")] - public long? Version { get; set; } -} + [JsonInclude] + [JsonPropertyName("previousResponseId")] + internal string? PreviousResponseId { get; set; } -/// External tool invocation request for client-side tool execution. -public sealed partial class ExternalToolRequestedData + /// Identifier of the assistant turn that initiated the model call. + [JsonPropertyName("turnId")] + public required string TurnId { get; set; } +} + +/// Turn abort information including the reason for termination. +public sealed partial class AbortData { - /// Arguments to pass to the external tool. + /// Finite reason code describing why the current turn was aborted. + [JsonPropertyName("reason")] + public required AbortReason Reason { get; set; } +} + +/// User-initiated tool invocation request with tool name and arguments. +public sealed partial class ToolUserRequestedData +{ + /// Arguments for the tool invocation. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("arguments")] public JsonElement? Arguments { get; set; } - /// Unique identifier for this request; used to respond via session.respondToExternalTool(). - [JsonPropertyName("requestId")] - public required string RequestId { get; set; } - - /// Session ID that this external tool request belongs to. - [JsonPropertyName("sessionId")] - public required string SessionId { get; set; } - - /// Tool call ID assigned to this external tool invocation. + /// Unique identifier for this tool call. [JsonPropertyName("toolCallId")] public required string ToolCallId { get; set; } - /// Name of the external tool to invoke. + /// Name of the tool the user wants to invoke. [JsonPropertyName("toolName")] public required string ToolName { get; set; } +} - /// W3C Trace Context traceparent header for the execute_tool span. +/// Tool execution startup details including MCP server information when applicable. +public sealed partial class ToolExecutionStartData +{ + /// Arguments passed to the tool. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("traceparent")] - public string? Traceparent { get; set; } + [JsonPropertyName("arguments")] + public JsonElement? Arguments { get; set; } - /// W3C Trace Context tracestate header for the execute_tool span. + /// When true, the tool output should be displayed expanded (verbatim) in the CLI timeline. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("tracestate")] - public string? Tracestate { get; set; } + [JsonPropertyName("displayVerbatim")] + public bool? DisplayVerbatim { get; set; } - /// Active session working directory, when known. + /// Experimental HydraFusion attribution for this tool execution. + [Experimental(Diagnostics.Experimental)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("workingDirectory")] - public string? WorkingDirectory { get; set; } -} - -/// External tool completion notification signaling UI dismissal. -public sealed partial class ExternalToolCompletedData -{ - /// Request ID of the resolved external tool request; clients should dismiss any UI for this request. - [JsonPropertyName("requestId")] - public required string RequestId { get; set; } -} - -/// Queued slash command dispatch request for client execution. -public sealed partial class CommandQueuedData -{ - /// The slash command text to be executed (e.g., /help, /clear). - [JsonPropertyName("command")] - public required string Command { get; set; } - - /// Unique identifier for this request; used to respond via session.respondToQueuedCommand(). - [JsonPropertyName("requestId")] - public required string RequestId { get; set; } -} + [JsonPropertyName("fusion")] + public FusionAttribution? Fusion { get; set; } -/// Registered command dispatch request routed to the owning client. -public sealed partial class CommandExecuteData -{ - /// Raw argument string after the command name. - [JsonPropertyName("args")] - public required string Args { get; set; } + /// Name of the MCP server hosting this tool, when the tool is an MCP tool. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("mcpServerName")] + public string? McpServerName { get; set; } - /// The full command text (e.g., /deploy production). - [JsonPropertyName("command")] - public required string Command { get; set; } + /// Original tool name on the MCP server, when the tool is an MCP tool. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("mcpToolName")] + public string? McpToolName { get; set; } - /// Command name without leading /. - [JsonPropertyName("commandName")] - public required string CommandName { get; set; } + /// Model identifier that generated this tool call. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("model")] + public string? Model { get; set; } - /// Unique identifier; used to respond via session.commands.handlePendingCommand(). - [JsonPropertyName("requestId")] - public required string RequestId { get; set; } -} + /// Tool call ID of the parent tool invocation when this event originates from a sub-agent. + [EditorBrowsable(EditorBrowsableState.Never)] +#if NET5_0_OR_GREATER + [Obsolete("This member is deprecated and will be removed in a future version.", DiagnosticId = "GHCP001")] +#endif + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("parentToolCallId")] + public string? ParentToolCallId { get; set; } -/// Queued command completion notification signaling UI dismissal. -public sealed partial class CommandCompletedData -{ - /// Request ID of the resolved command request; clients should dismiss any UI for this request. - [JsonPropertyName("requestId")] - public required string RequestId { get; set; } -} + /// Per-request treatment/eligibility signal returned by the Copilot API in the `X-GitHub-Copilot-Request-TE` response header for the associated model call; `false` when the header was absent or unparseable. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("rte")] + public bool? Rte { get; set; } -/// Auto mode switch request notification requiring user approval. -public sealed partial class AutoModeSwitchRequestedData -{ - /// The rate limit error code that triggered this request. + /// 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("errorCode")] - public string? ErrorCode { get; set; } + [JsonPropertyName("shellToolInfo")] + public ToolExecutionStartShellToolInfo? ShellToolInfo { get; set; } - /// Unique identifier for this request; used to respond via session.respondToAutoModeSwitch(). - [JsonPropertyName("requestId")] - public required string RequestId { get; set; } + /// Unique identifier for this tool call. + [JsonPropertyName("toolCallId")] + public required string ToolCallId { get; set; } - /// Seconds until the rate limit resets, when known. Lets clients render a humanized reset time alongside the prompt. + /// Tool definition metadata, present for MCP tools with MCP Apps support. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("retryAfterSeconds")] - public long? RetryAfterSeconds { get; set; } -} + [JsonPropertyName("toolDescription")] + public ToolExecutionStartToolDescription? ToolDescription { get; set; } -/// Auto mode switch completion notification. -public sealed partial class AutoModeSwitchCompletedData -{ - /// Request ID of the resolved request; clients should dismiss any UI for this request. - [JsonPropertyName("requestId")] - public required string RequestId { get; set; } + /// Name of the tool being executed. + [JsonPropertyName("toolName")] + public required string ToolName { get; set; } - /// The user's auto-mode-switch choice. - [JsonPropertyName("response")] - public required AutoModeSwitchResponse Response { get; set; } + /// Identifier for the agent loop turn this tool was invoked in, matching the corresponding assistant.turn_start event. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("turnId")] + public string? TurnId { get; set; } } -/// Session limit exhaustion notification requiring user action. -public sealed partial class SessionLimitsExhaustedRequestedData +/// Streaming tool execution output for incremental result display. +public sealed partial class ToolExecutionPartialResultData { - /// Configured max AI Credits for the current accounting window. - [JsonPropertyName("maxAiCredits")] - public required double MaxAiCredits { get; set; } - - /// Unique identifier for this request; used to respond via session.ui.handlePendingSessionLimitsExhausted(). - [JsonPropertyName("requestId")] - public required string RequestId { get; set; } + /// Incremental output chunk from the running tool. + [JsonPropertyName("partialOutput")] + public required string PartialOutput { get; set; } - /// AI Credits already consumed in the current accounting window. - [JsonPropertyName("usedAiCredits")] - public required double UsedAiCredits { get; set; } + /// Tool call ID this partial result belongs to. + [JsonPropertyName("toolCallId")] + public required string ToolCallId { get; set; } } -/// Session limit exhaustion prompt completion notification. -public sealed partial class SessionLimitsExhaustedCompletedData +/// Tool execution progress notification with status message. +public sealed partial class ToolExecutionProgressData { - /// Request ID of the resolved request; clients should dismiss any UI for this request. - [JsonPropertyName("requestId")] - public required string RequestId { get; set; } + /// Human-readable progress status message (e.g., from an MCP server). + [JsonPropertyName("progressMessage")] + public required string ProgressMessage { get; set; } - /// The user's selected session-limit action. - [JsonPropertyName("response")] - public required SessionLimitsExhaustedResponse Response { get; set; } + /// Tool call ID this progress notification belongs to. + [JsonPropertyName("toolCallId")] + public required string ToolCallId { get; set; } } -/// 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(Diagnostics.Experimental)] -public sealed partial class SessionAutoModeResolvedData +/// Tool execution completion results including success status, detailed output, and error information. +public sealed partial class ToolExecutionCompleteData { - /// Models offered to the router for this resolution. + /// Error details when the tool execution failed. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("availableModels")] - public string[]? AvailableModels { get; set; } + [JsonPropertyName("error")] + public ToolExecutionCompleteError? Error { get; set; } - /// Ordered candidate model list the router returned, when not a fallback. + /// Experimental HydraFusion attribution for this tool completion. + [Experimental(Diagnostics.Experimental)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("candidateModels")] - public string[]? CandidateModels { get; set; } + [JsonPropertyName("fusion")] + public FusionAttribution? Fusion { get; set; } - /// 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. + /// CAPI interaction ID for correlating this tool execution with upstream telemetry. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("categoryScores")] - public IDictionary? CategoryScores { get; set; } + [JsonPropertyName("interactionId")] + public string? InteractionId { get; set; } - /// The concrete model the session will use after any intent refinement. - [JsonPropertyName("chosenModel")] - public required string ChosenModel { get; set; } + /// Whether this tool call was explicitly requested by the user rather than the assistant. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("isUserRequested")] + public bool? IsUserRequested { get; set; } - /// The chosen model's score shortfall relative to the top candidate. + /// FIDES IFC label projected from tool ingress metadata (MCP `CallToolResult._meta` or synthesized built-in ingress labels). Persisted as `{ ifc: ... }` so the label survives session resume, including model-visible failure results. Experimental. + [Experimental(Diagnostics.Experimental)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("chosenShortfall")] - public double? ChosenShortfall { get; set; } + [JsonPropertyName("mcpMeta")] + public JsonElement? McpMeta { get; set; } - /// Classifier confidence for the predicted label, when available. + /// Model identifier that generated this tool call. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("confidence")] - public double? Confidence { get; set; } + [JsonPropertyName("model")] + public string? Model { get; set; } - /// End-to-end client wait time for the router request in milliseconds. + /// Tool call ID of the parent tool invocation when this event originates from a sub-agent. + [EditorBrowsable(EditorBrowsableState.Never)] +#if NET5_0_OR_GREATER + [Obsolete("This member is deprecated and will be removed in a future version.", DiagnosticId = "GHCP001")] +#endif [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("endToEndLatencyMs")] - public double? EndToEndLatencyMs { get; set; } + [JsonPropertyName("parentToolCallId")] + public string? ParentToolCallId { get; set; } - /// Whether the router fell back to the standard Auto selection. + /// Tool execution result on success. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("fallback")] - public bool? Fallback { get; set; } + [JsonPropertyName("result")] + public ToolExecutionCompleteResult? Result { get; set; } - /// Server-provided reason for falling back, when available. + /// Per-request treatment/eligibility signal returned by the Copilot API in the `X-GitHub-Copilot-Request-TE` response header for the associated model call; `false` when the header was absent or unparseable. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("fallbackReason")] - public string? FallbackReason { get; set; } + [JsonPropertyName("rte")] + public bool? Rte { get; set; } - /// Whether the routed prompt contained an image. + /// Whether this tool execution ran inside a sandbox container. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("hasImage")] - public bool? HasImage { get; set; } + [JsonPropertyName("sandboxed")] + public bool? Sandboxed { get; set; } - /// The predicted classifier label (e.g. `needs_reasoning`), when available. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("predictedLabel")] - public string? PredictedLabel { get; set; } + /// Whether the tool execution completed successfully. + [JsonPropertyName("success")] + public required bool Success { get; set; } - /// Coarse request-difficulty bucket, for explaining why a model was chosen ("picked X because this looks like high-reasoning work"). - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("reasoningBucket")] - public AutoModeResolvedReasoningBucket? ReasoningBucket { get; set; } + /// Unique identifier for the completed tool call. + [JsonPropertyName("toolCallId")] + public required string ToolCallId { get; set; } - /// Server-reported router processing time in milliseconds. + /// Tool definition metadata, present for MCP tools with MCP Apps support. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("routerLatencyMs")] - public double? RouterLatencyMs { get; set; } + [JsonPropertyName("toolDescription")] + public ToolExecutionCompleteToolDescription? ToolDescription { get; set; } - /// The routing method the server applied, when Auto Intent ran. + /// Tool-specific telemetry data (e.g., CodeQL check counts, grep match counts). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("routingMethod")] - public string? RoutingMethod { get; set; } + [JsonPropertyName("toolTelemetry")] + public IDictionary? ToolTelemetry { get; set; } - /// Whether a sticky model choice overrode the router result. + /// Identifier for the agent loop turn this tool was invoked in, matching the corresponding assistant.turn_start event. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("stickyOverride")] - public bool? StickyOverride { get; set; } + [JsonPropertyName("turnId")] + public string? TurnId { 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. -[Experimental(Diagnostics.Experimental)] -public sealed partial class SessionManagedSettingsResolvedData +/// Persisted generic client-side tool activations restored when a session resumes. +public sealed partial class ToolSearchActivatedData { - /// Whether enterprise policy disables bypass-permissions ("yolo") mode for this session. Deny-wins across layers, and forced on when `failClosed` is true. - [JsonPropertyName("bypassPermissionsDisabled")] - public required bool BypassPermissionsDisabled { get; set; } + /// Tool-search strategy that activated the definitions. + [JsonPropertyName("strategy")] + public required string Strategy { get; set; } - /// Whether the device (MDM/plist/registry/file) managed-settings layer was present. - [JsonPropertyName("deviceManaged")] - public required bool DeviceManaged { get; set; } + /// Names of tool definitions activated by this search invocation. + [JsonPropertyName("toolNames")] + public required string[] ToolNames { get; set; } +} - /// 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. - [JsonPropertyName("failClosed")] - public required bool FailClosed { get; set; } +/// Skill invocation details including content, allowed tools, and plugin metadata. +public sealed partial class SkillInvokedData +{ + /// Tool names that should be auto-approved when this skill is active. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("allowedTools")] + public string[]? AllowedTools { get; set; } - /// The setting keys under enterprise management in the effective managed settings (e.g. `model`, `enabledPlugins`, `permissions`). Empty when no managed settings are in force. - [JsonPropertyName("managedKeys")] - public required string[] ManagedKeys { get; set; } + /// Full content of the skill file, injected into the conversation for the model. + [JsonPropertyName("content")] + public required string Content { get; set; } - /// Whether server and device each supplied a permission allowlist, so enforcement intersects them and the flattened settings payload omits `permissions.allow`. + /// Description of the skill from its SKILL.md frontmatter. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("permissionsAllowIntersected")] - public bool? PermissionsAllowIntersected { get; set; } + [JsonPropertyName("description")] + public string? Description { get; set; } - /// Whether the server (account/org) managed-settings layer was present. - [JsonPropertyName("serverManaged")] - public required bool ServerManaged { get; set; } + /// Whether model invocation is disabled for this skill. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("disableModelInvocation")] + public bool? DisableModelInvocation { get; set; } - /// The effective (resolved) managed settings values, so clients can render exactly what is enforced. Absent when no managed policy is in force. + /// Model identifier active when the skill was invoked, when known. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("settings")] - public JsonElement? Settings { get; set; } + [JsonPropertyName("model")] + public string? Model { get; set; } - /// Which channel supplied the effective managed settings (the winning layer), or `none` when no policy is in force. - [JsonPropertyName("source")] - public required ManagedSettingsResolvedSource Source { get; set; } -} + /// Name of the invoked skill. + [JsonPropertyName("name")] + public required string Name { get; set; } -/// Runtime enforcement of enterprise managed settings: fires when the session blocks or caps a runtime action because enterprise policy governs it, so SDK clients can explain *why* an action was governed. Unlike `session.managed_settings_resolved` (which reports *what* is managed), this reports a concrete governed action — e.g. a user or host tried to turn on a bypass-permissions escalation while policy disables it. Emitted live (not persisted to the session event log) on user/host-initiated attempts only, never for silent policy application. Marked experimental while the managed-settings surface stabilizes. -[Experimental(Diagnostics.Experimental)] -public sealed partial class SessionManagedSettingsEnforcedData -{ - /// The category of runtime action that managed policy governed. - [JsonPropertyName("action")] - public required ManagedSettingsEnforcedAction Action { get; set; } + /// File path to the SKILL.md definition, or an empty string for an SDK-provided skill without a filesystem identity. + [JsonPropertyName("path")] + public required string Path { get; set; } - /// For a `bypass_permissions_blocked` action, which permission-escalation primitive was refused. Absent for actions without a specific escalation primitive. + /// Name of the plugin this skill originated from, when applicable. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("escalation")] - public ManagedSettingsEnforcedEscalation? Escalation { get; set; } - - /// Whether the enforcement was forced by fail-closed handling (managed policy could not be determined) rather than an explicit managed setting. When true, `setting` still names the restriction that was applied. - [JsonPropertyName("failClosed")] - public required bool FailClosed { get; set; } - - /// A human-readable explanation of why the action was governed, suitable for surfacing to the user. - [JsonPropertyName("message")] - public required string Message { get; set; } + [JsonPropertyName("pluginName")] + public string? PluginName { get; set; } - /// The managed setting key responsible for the enforcement (e.g. `permissions.disableBypassPermissionsMode`). - [JsonPropertyName("setting")] - public required string Setting { get; set; } -} + /// Version of the plugin this skill originated from, when applicable. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("pluginVersion")] + public string? PluginVersion { get; set; } -/// SDK command registration change notification. -public sealed partial class CommandsChangedData -{ - /// Current list of registered SDK commands. - [JsonPropertyName("commands")] - public required CommandsChangedCommand[] Commands { get; set; } -} + /// Source identifier for where the skill was discovered. Known values include: project (workspace skill), inherited (parent-directory skill), personal-copilot (~/.copilot/skills), personal-agents (~/.agents/skills), custom (configured directory), plugin (installed plugin), builtin (bundled runtime skill), remote (org/enterprise skill), and sdk (SDK-provided skill). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("source")] + public string? Source { get; set; } -/// Session capability change notification. -public sealed partial class CapabilitiesChangedData -{ - /// UI capability changes. + /// What triggered the skill invocation: `user-invoked` (explicit user action, such as via a slash command or UI affordance), `agent-invoked` (agent requested the skill), or `context-load` (loaded as part of another context, such as preloading skills configured on a custom agent or subagent). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("ui")] - public CapabilitiesChangedUI? Ui { get; set; } + [JsonPropertyName("trigger")] + public SkillInvokedTrigger? Trigger { get; set; } } -/// Plan approval request with plan content and available user actions. -public sealed partial class ExitPlanModeRequestedData -{ - /// Available actions the user can take. - [JsonPropertyName("actions")] - public required ExitPlanModeAction[] Actions { get; set; } +/// Payload of `sandbox.decision`, a bounded governance record of what the process sandbox was configured to do and whether it took effect. Discriminated by `kind`. +public sealed partial class SandboxDecisionData { } - /// Full content of the plan file. - [JsonPropertyName("planContent")] - public required string PlanContent { get; set; } +/// Sub-agent startup details including parent tool call and agent information. +public sealed partial class SubagentStartedData +{ + /// Description of what the sub-agent does. + [JsonPropertyName("agentDescription")] + public required string AgentDescription { get; set; } - /// Recommended action to preselect for the user. - [JsonPropertyName("recommendedAction")] - public required ExitPlanModeAction RecommendedAction { get; set; } + /// Human-readable display name of the sub-agent. + [JsonPropertyName("agentDisplayName")] + public required string AgentDisplayName { get; set; } - /// Unique identifier for this request; used to respond via session.respondToExitPlanMode(). - [JsonPropertyName("requestId")] - public required string RequestId { get; set; } + /// Internal name of the sub-agent. + [JsonPropertyName("agentName")] + public required string AgentName { get; set; } - /// Summary of the plan that was created. - [JsonPropertyName("summary")] - public required string Summary { get; set; } -} + /// Type of the sub-agent selected at spawn time. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("agentType")] + public string? AgentType { get; set; } -/// Plan mode exit completion with the user's approval decision and optional feedback. -public sealed partial class ExitPlanModeCompletedData -{ - /// Whether the plan was approved by the user. + /// Whether the sub-agent runs synchronously or in the background. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("approved")] - public bool? Approved { get; set; } + [JsonPropertyName("executionMode")] + public string? ExecutionMode { get; set; } - /// Whether edits should be auto-approved without confirmation. + /// Root id of the factory run that spawned this sub-agent, when it was spawned by one. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("autoApproveEdits")] - public bool? AutoApproveEdits { get; set; } + [JsonPropertyName("factoryRunId")] + public string? FactoryRunId { get; set; } - /// Free-form feedback from the user if they requested changes to the plan. + /// Model the sub-agent will run with, when known at start. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("feedback")] - public string? Feedback { get; set; } + [JsonPropertyName("model")] + public string? Model { get; set; } - /// Request ID of the resolved exit plan mode request; clients should dismiss any UI for this request. - [JsonPropertyName("requestId")] - public required string RequestId { get; set; } + /// Task-registry ID of the spawning sub-agent. Absent when the root session spawned this child. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("parentId")] + public string? ParentId { get; set; } - /// Action selected by the user. + /// Whether this sub-agent can be resumed. Currently always false. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("selectedAction")] - public ExitPlanModeAction? SelectedAction { get; set; } + [JsonPropertyName("resumable")] + public bool? Resumable { get; set; } + + /// Tool call ID of the parent tool invocation that spawned this sub-agent. + [JsonPropertyName("toolCallId")] + public required string ToolCallId { get; set; } } -/// Payload of `session.tools_updated` identifying the model whose resolved tools were updated. -public sealed partial class SessionToolsUpdatedData +/// Resolved runtime configuration for a configured sub-agent. +public sealed partial class SubagentConfiguredData { - /// Identifier of the model the resolved tools apply to. + /// Resolved context tier, when configured for the model. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("contextTier")] + public string? ContextTier { get; set; } + + /// Resolved model the sub-agent will run with. [JsonPropertyName("model")] public required string Model { get; set; } -} - -/// Empty payload for `session.background_tasks_changed`, indicating background task state changed. -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; } + /// Whether the sub-agent accepts follow-up turns. + [JsonPropertyName("multiTurn")] + public required bool MultiTurn { get; set; } - /// Gets or sets the runId value. - [JsonPropertyName("runId")] - public required string RunId { get; set; } + /// Resolved reasoning effort, when configured for the model. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("reasoningEffort")] + public string? ReasoningEffort { get; set; } } -/// Payload of `session.skills_loaded` listing resolved skill metadata. -public sealed partial class SessionSkillsLoadedData +/// Sub-agent completion details for successful execution. +public sealed partial class SubagentCompletedData { - /// Array of resolved skill metadata. - [JsonPropertyName("skills")] - public required SkillsLoadedSkill[] Skills { get; set; } -} + /// Human-readable display name of the sub-agent. + [JsonPropertyName("agentDisplayName")] + public required string AgentDisplayName { get; set; } -/// Payload of `session.custom_agents_updated` with loaded custom agents plus non-fatal warnings and fatal errors. -public sealed partial class SessionCustomAgentsUpdatedData -{ - /// Array of loaded custom agent metadata. - [JsonPropertyName("agents")] - public required CustomAgentsUpdatedAgent[] Agents { get; set; } + /// Internal name of the sub-agent. + [JsonPropertyName("agentName")] + public required string AgentName { get; set; } - /// Fatal errors from agent loading. - [JsonPropertyName("errors")] - public required string[] Errors { 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; } - /// Non-fatal warnings from agent loading. - [JsonPropertyName("warnings")] - public required string[] Warnings { get; set; } -} + /// Whether the first model actually dispatched matched the user's configured preference. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("configuredModelMatchesActual")] + public bool? ConfiguredModelMatchesActual { get; set; } -/// Payload of `session.mcp_servers_loaded` listing MCP server status summaries. -public sealed partial class SessionMcpServersLoadedData -{ - /// Array of MCP server status summaries. - [JsonPropertyName("servers")] - public required McpServersLoadedServer[] Servers { get; set; } -} + /// Concrete model the user configured for this sub-agent via `/subagents`, when present. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("configuredModelPreference")] + public string? ConfiguredModelPreference { get; set; } -/// Payload of `session.mcp_server_status_changed` for one MCP server's status and optional failure error. -public sealed partial class SessionMcpServerStatusChangedData -{ - /// Error message if the server entered a failed state. + /// Wall-clock duration of the sub-agent execution in milliseconds. + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("error")] - public string? Error { get; set; } + [JsonPropertyName("durationMs")] + public TimeSpan? Duration { get; set; } - /// Name of the MCP server whose status changed. - [JsonPropertyName("serverName")] - public required string ServerName { get; set; } + /// Whether the explicit task-call model matched the user's configured preference. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("explicitModelMatchesPreference")] + public bool? ExplicitModelMatchesPreference { get; set; } - /// Connection status: connected, failed, needs-auth, pending, disabled, stopped, or not_configured. - [JsonPropertyName("status")] - public required McpServerStatus Status { get; set; } -} + /// Explicit model supplied by the parent agent on the task call, when present. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("explicitModelOverride")] + public string? ExplicitModelOverride { get; set; } -/// Payload identifying the MCP server associated with a list change. -public sealed partial class McpToolsListChangedData -{ - /// Name of the MCP server whose list changed. - [JsonPropertyName("serverName")] - public required string ServerName { get; set; } -} + /// First model for which the sub-agent started an inference request, when one was dispatched. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("firstDispatchedModel")] + public string? FirstDispatchedModel { get; set; } -/// Payload identifying the MCP server associated with a list change. -public sealed partial class McpResourcesListChangedData -{ - /// Name of the MCP server whose list changed. - [JsonPropertyName("serverName")] - public required string ServerName { get; set; } -} + /// Model used by the sub-agent. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("model")] + public string? Model { get; set; } -/// Payload identifying the MCP server associated with a list change. -public sealed partial class McpPromptsListChangedData -{ - /// Name of the MCP server whose list changed. - [JsonPropertyName("serverName")] - public required string ServerName { get; set; } -} + /// Why an explicit task-call model did not become the effective model. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("modelOverrideReason")] + public string? ModelOverrideReason { get; set; } -/// Payload of `session.extensions_loaded` listing discovered extensions and their statuses. -public sealed partial class SessionExtensionsLoadedData -{ - /// Array of discovered extensions and their status. - [JsonPropertyName("extensions")] - public required ExtensionsLoadedExtension[] Extensions { get; set; } + /// Tool call ID of the parent tool invocation that spawned this sub-agent. + [JsonPropertyName("toolCallId")] + public required string ToolCallId { get; set; } + + /// Total tokens (input + output) consumed by the sub-agent. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("totalTokens")] + public long? TotalTokens { get; set; } + + /// Total number of tool calls made by the sub-agent. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("totalToolCalls")] + public long? TotalToolCalls { get; set; } } -/// Payload of `session.canvas.opened` with canvas instance and provider IDs plus optional icon, title, status, URL, and input. -[Experimental(Diagnostics.Experimental)] -public sealed partial class SessionCanvasOpenedData +/// Sub-agent failure details including error message and agent information. +public sealed partial class SubagentFailedData { - /// Provider-local canvas identifier. - [JsonPropertyName("canvasId")] - public required string CanvasId { get; set; } + /// Human-readable display name of the sub-agent. + [JsonPropertyName("agentDisplayName")] + public required string AgentDisplayName { get; set; } - /// Owning provider identifier. - [JsonPropertyName("extensionId")] - public required string ExtensionId { get; set; } + /// Internal name of the sub-agent. + [JsonPropertyName("agentName")] + public required string AgentName { get; set; } - /// Owning extension display name, when available. + /// Whether the first model actually dispatched matched the user's configured preference. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("extensionName")] - public string? ExtensionName { get; set; } + [JsonPropertyName("configuredModelMatchesActual")] + public bool? ConfiguredModelMatchesActual { get; set; } - /// Host-local PNG path for the canvas icon, when supplied. + /// Concrete model the user configured for this sub-agent via `/subagents`, when present. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("icon")] - public string? Icon { get; set; } + [JsonPropertyName("configuredModelPreference")] + public string? ConfiguredModelPreference { get; set; } - /// Input supplied when the instance was opened. + /// Wall-clock duration of the sub-agent execution in milliseconds. + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("input")] - public JsonElement? Input { get; set; } + [JsonPropertyName("durationMs")] + public TimeSpan? Duration { get; set; } - /// Stable caller-supplied canvas instance identifier. - [JsonPropertyName("instanceId")] - public required string InstanceId { get; set; } + /// Error message describing why the sub-agent failed. + [JsonPropertyName("error")] + public required string Error { get; set; } - /// Provider-supplied status text. + /// Whether the explicit task-call model matched the user's configured preference. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("status")] - public string? Status { get; set; } + [JsonPropertyName("explicitModelMatchesPreference")] + public bool? ExplicitModelMatchesPreference { get; set; } - /// Rendered title. + /// Explicit model supplied by the parent agent on the task call, when present. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("title")] - public string? Title { get; set; } + [JsonPropertyName("explicitModelOverride")] + public string? ExplicitModelOverride { get; set; } - /// URL for web-rendered canvases. + /// First model for which the sub-agent started an inference request, when one was dispatched. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("url")] - public string? Url { get; set; } -} + [JsonPropertyName("firstDispatchedModel")] + public string? FirstDispatchedModel { get; set; } -/// Payload of `session.canvas.registry_changed` listing the canvas declarations currently available. -[Experimental(Diagnostics.Experimental)] -public sealed partial class SessionCanvasRegistryChangedData -{ - /// Canvas declarations currently available. - [JsonPropertyName("canvases")] - public required CanvasRegistryChangedCanvas[] Canvases { get; set; } -} + /// Model selected for the sub-agent, when known. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("model")] + public string? Model { get; set; } -/// Payload of `session.canvas.closed` with the closed canvas instance ID, provider ID, and canvas ID. -[Experimental(Diagnostics.Experimental)] -public sealed partial class SessionCanvasClosedData -{ - /// Provider-local canvas identifier. - [JsonPropertyName("canvasId")] - public required string CanvasId { get; set; } + /// Why an explicit task-call model did not become the effective model. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("modelOverrideReason")] + public string? ModelOverrideReason { get; set; } - /// Owning provider identifier. - [JsonPropertyName("extensionId")] - public required string ExtensionId { get; set; } + /// Tool call ID of the parent tool invocation that spawned this sub-agent. + [JsonPropertyName("toolCallId")] + public required string ToolCallId { get; set; } - /// Stable caller-supplied identifier of the canvas instance that was closed. - [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("instanceId")] - public required string InstanceId { get; set; } + /// Total tokens (input + output) consumed before the sub-agent failed. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("totalTokens")] + public long? TotalTokens { get; set; } + + /// Total number of tool calls made before the sub-agent failed. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("totalToolCalls")] + public long? TotalToolCalls { get; set; } } -/// Transient signal that an open canvas instance's provider has dropped (for example the extension is reloading mid-session). The host should keep the panel mounted and surface a reconnecting affordance rather than tearing it down; a subsequent `session.canvas.opened` for the same instanceId clears the affordance once the provider reconnects with a fresh url. Ephemeral and never persisted, so it is never replayed on cold resume. -[Experimental(Diagnostics.Experimental)] -public sealed partial class SessionCanvasUnavailableData +/// Custom agent selection details including name and available tools. +public sealed partial class SubagentSelectedData { - /// Provider-local canvas identifier. - [JsonPropertyName("canvasId")] - public required string CanvasId { get; set; } + /// Human-readable display name of the selected custom agent. + [JsonPropertyName("agentDisplayName")] + public required string AgentDisplayName { get; set; } - /// Owning provider identifier. - [JsonPropertyName("extensionId")] - public required string ExtensionId { get; set; } + /// Internal name of the selected custom agent. + [JsonPropertyName("agentName")] + public required string AgentName { get; set; } - /// Stable caller-supplied identifier of the canvas instance whose provider became unavailable. - [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("instanceId")] - public required string InstanceId { get; set; } + /// List of tool names available to this agent, or null for all tools. + [JsonPropertyName("tools")] + public string[]? Tools { get; set; } } -/// Durable record that a canvas instance is open, used to restore open canvases on cold session resume. Intentionally omits the transient url and availability. -[Experimental(Diagnostics.Experimental)] -public sealed partial class SessionCanvasRecordedData +/// Empty payload; the event signals that the custom agent was deselected, returning to the default agent. +public sealed partial class SubagentDeselectedData { - /// Provider-local canvas identifier. - [JsonPropertyName("canvasId")] - public required string CanvasId { get; set; } +} - /// Owning provider identifier. - [JsonPropertyName("extensionId")] - public required string ExtensionId { get; set; } +/// Hook invocation start details including type and input data. +public sealed partial class HookStartData +{ + /// Unique identifier for this hook invocation. + [JsonPropertyName("hookInvocationId")] + public required string HookInvocationId { get; set; } - /// Input supplied when the instance was opened. + /// Type of hook being invoked (e.g., "preToolUse", "postToolUse", "sessionStart"). + [JsonPropertyName("hookType")] + public required string HookType { get; set; } + + /// Input data passed to the hook. For postToolUse hooks the retained copy served by session.eventLog.read (and by a resumed session) elides the tool result's inline `contents`/`uiResource` and replaces an over-long `textResultForLlm` with a `[copilot:elided ...]` marker, to keep a multi-megabyte payload out of the durable event log; the live subscription stream still delivers the full value. Read the adjacent tool.execution_complete event for the tool result itself. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("input")] public JsonElement? Input { get; set; } - /// Stable caller-supplied canvas instance identifier. - [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("instanceId")] - public required string InstanceId { get; set; } - - /// Rendered title. + /// Tool call ID of the parent tool invocation when this event originates from a sub-agent. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("title")] - public string? Title { get; set; } + [JsonPropertyName("parentToolCallId")] + public string? ParentToolCallId { get; set; } } -/// Durable record that a canvas instance was closed, superseding a prior instance_recorded during resume replay. -[Experimental(Diagnostics.Experimental)] -public sealed partial class SessionCanvasRemovedData +/// Hook invocation completion details including output, success status, and error information. +public sealed partial class HookEndData { - /// Provider-local canvas identifier. - [JsonPropertyName("canvasId")] - public required string CanvasId { get; set; } - - /// Owning provider identifier. - [JsonPropertyName("extensionId")] - public required string ExtensionId { get; set; } + /// Error details when the hook failed. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("error")] + public HookEndError? Error { get; set; } - /// Stable caller-supplied identifier of the canvas instance that was closed. - [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("instanceId")] - public required string InstanceId { get; set; } -} + /// Identifier matching the corresponding hook.start event. + [JsonPropertyName("hookInvocationId")] + public required string HookInvocationId { get; set; } -/// Payload of `session.extensions.attachments_pushed` with extension-contributed attachments for the next send. -public sealed partial class SessionExtensionsAttachmentsPushedData -{ - /// Attachments contributed by an extension; the host should surface these as composer pills and forward them via the next session.send call. - [JsonPropertyName("attachments")] - public required Attachment[] Attachments { get; set; } -} + /// Type of hook that was invoked (e.g., "preToolUse", "postToolUse", "sessionStart"). + [JsonPropertyName("hookType")] + public required string HookType { get; set; } -/// MCP App view called a tool on a connected MCP server (SEP-1865). -public sealed partial class McpAppToolCallCompleteData -{ - /// Arguments passed to the tool by the app view, if any. + /// Output data produced by the hook. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("arguments")] - public IDictionary? Arguments { get; set; } - - /// Wall-clock duration of the underlying tools/call in milliseconds. - [JsonPropertyName("durationMs")] - public required double DurationMs { get; set; } - - /// Set when the underlying tools/call threw an error before returning a CallToolResult. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("error")] - public McpAppToolCallCompleteError? Error { get; set; } + [JsonPropertyName("output")] + public JsonElement? Output { get; set; } - /// Standard MCP CallToolResult returned by the server. Present whether or not the call set isError. + /// Tool call ID of the parent tool invocation when this event originates from a sub-agent. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("result")] - public IDictionary? Result { get; set; } - - /// Name of the MCP server hosting the tool. - [JsonPropertyName("serverName")] - public required string ServerName { get; set; } + [JsonPropertyName("parentToolCallId")] + public string? ParentToolCallId { get; set; } - /// True when the call completed without throwing AND the MCP CallToolResult did not set isError. + /// Whether the hook completed successfully. [JsonPropertyName("success")] public required bool Success { get; set; } - - /// The tool's `_meta.ui` block at the time of the call, so consumers can decide whether to forward the result to the model without re-listing tools. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolMeta")] - public McpAppToolCallCompleteToolMeta? ToolMeta { get; set; } - - /// MCP tool name that was invoked. - [JsonPropertyName("toolName")] - public required string ToolName { get; set; } } -/// Working directory and git context at session start. -/// Nested data type for WorkingDirectoryContext. -public sealed partial class WorkingDirectoryContext +/// Ephemeral progress update from a running hook process. +public sealed partial class HookProgressData { - /// Base commit of current git branch at session start time. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("baseCommit")] - public string? BaseCommit { get; set; } + /// Human-readable progress message from the hook process. + [JsonPropertyName("message")] + public required string Message { get; set; } - /// Current git branch name. + /// When true, this status message replaces the previous temporary one instead of accumulating. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("branch")] - public string? Branch { get; set; } + [JsonPropertyName("temporary")] + public bool? Temporary { get; set; } +} - /// Current working directory path. - [JsonPropertyName("cwd")] - public required string Cwd { get; set; } +/// Canonical bytes for a content-addressed binary asset shared by reference across events. +public sealed partial class SessionBinaryAssetData +{ + /// Content-addressed id for this binary asset (e.g. "sha256:..."). + [JsonPropertyName("assetId")] + public required string AssetId { get; set; } - /// Root directory of the git repository, resolved via git rev-parse. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("gitRoot")] - public string? GitRoot { get; set; } + /// Decoded byte length of the binary asset. + [JsonPropertyName("byteLength")] + public required long ByteLength { get; set; } - /// Head commit of current git branch at session start time. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("headCommit")] - public string? HeadCommit { get; set; } + /// Base64-encoded binary data. + [Base64String] + [JsonPropertyName("data")] + public required string Data { get; set; } - /// Hosting platform type of the repository (github or ado). + /// Human-readable description of the binary data. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("hostType")] - public WorkingDirectoryContextHostType? HostType { get; set; } + [JsonPropertyName("description")] + public string? Description { 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). + /// Optional metadata from the producing tool. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("pendingGitContext")] - public bool? PendingGitContext { get; set; } + [JsonPropertyName("metadata")] + public IDictionary? Metadata { 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")] - public string? Repository { get; set; } + /// MIME type of the binary asset. + [JsonPropertyName("mimeType")] + public required string MimeType { get; set; } - /// Raw host string from the git remote URL (e.g. "github.com", "mycompany.ghe.com", "dev.azure.com"). - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("repositoryHost")] - public string? RepositoryHost { get; set; } + /// Binary asset type discriminator. Use "image" for images and "resource" otherwise. + [JsonPropertyName("type")] + public required BinaryAssetType Type { get; set; } } -/// Optional session limits. -/// Nested data type for SessionLimitsConfig. -public sealed partial class SessionLimitsConfig +/// System/developer instruction content with role and optional template metadata. +public sealed partial class SystemMessageData { - /// Maximum AI Credits allowed across the session's current accounting window. + /// The system or developer prompt text sent as model input. + [JsonPropertyName("content")] + public required string Content { get; set; } + + /// Logical interaction identifier for the model run receiving this prompt. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("maxAiCredits")] - public double? MaxAiCredits { get; set; } -} + [JsonPropertyName("interactionId")] + public string? InteractionId { get; set; } -/// Repository context for the handed-off session. -/// Nested data type for HandoffRepository. -public sealed partial class HandoffRepository -{ - /// Git branch name, if applicable. + /// Metadata about the prompt template and its construction. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("branch")] - public string? Branch { get; set; } + [JsonPropertyName("metadata")] + public SystemMessageMetadata? Metadata { get; set; } - /// Repository name. + /// Optional name identifier for the message source. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("name")] - public required string Name { get; set; } + public string? Name { get; set; } - /// Repository owner (user or organization). - [JsonPropertyName("owner")] - public required string Owner { get; set; } + /// Message role: "system" for system prompts, "developer" for developer-injected instructions. + [JsonPropertyName("role")] + public required SystemMessageRole Role { get; set; } } -/// Aggregate code change metrics for the session. -/// Nested data type for ShutdownCodeChanges. -public sealed partial class ShutdownCodeChanges +/// System-generated notification for runtime events like background task completion. +public sealed partial class SystemNotificationData { - /// List of file paths that were modified during the session. - [JsonPropertyName("filesModified")] - public required string[] FilesModified { get; set; } - - /// Total number of lines added during the session. - [JsonPropertyName("linesAdded")] - public required long LinesAdded { get; set; } + /// The notification text, typically wrapped in <system_notification> XML tags. + [JsonPropertyName("content")] + public required string Content { get; set; } - /// Total number of lines removed during the session. - [JsonPropertyName("linesRemoved")] - public required long LinesRemoved { get; set; } + /// Structured metadata identifying what triggered this notification. + [JsonPropertyName("kind")] + public required SystemNotification Kind { get; set; } } -/// Request count and cost metrics. -/// Nested data type for ShutdownModelMetricRequests. -public sealed partial class ShutdownModelMetricRequests +/// Permission request notification requiring client approval with request details. +public sealed partial class PermissionRequestedData { - /// Cumulative cost multiplier for requests to this model. - [Experimental(Diagnostics.Experimental)] + /// Agent mode captured from the owning turn when permission evaluation began. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("cost")] - public double? Cost { get; set; } + [JsonPropertyName("agentMode")] + public SessionMode? AgentMode { get; set; } - /// Total number of API requests made to this model. - [Experimental(Diagnostics.Experimental)] + /// Details of the permission being requested. + [JsonPropertyName("permissionRequest")] + public required PermissionRequest PermissionRequest { get; set; } + + /// Derived user-facing permission prompt details for UI consumers. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("count")] - public long? Count { get; set; } -} + [JsonPropertyName("promptRequest")] + public PermissionPromptRequest? PromptRequest { get; set; } -/// A token-type entry in a shutdown model metric, storing the accumulated token count. -/// Nested data type for ShutdownModelMetricTokenDetail. -public sealed partial class ShutdownModelMetricTokenDetail -{ - /// Accumulated token count for this token type. - [JsonPropertyName("tokenCount")] - public required long TokenCount { get; set; } -} + /// Unique identifier for this permission request; used to respond via session.respondToPermission(). + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } -/// Token usage breakdown. -/// Nested data type for ShutdownModelMetricUsage. -public sealed partial class ShutdownModelMetricUsage -{ - /// Total tokens read from prompt cache across all requests. - [JsonPropertyName("cacheReadTokens")] - public required long CacheReadTokens { get; set; } + /// When true, this permission was already resolved by a permissionRequest hook and requires no client action. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("resolvedByHook")] + public bool? ResolvedByHook { get; set; } - /// Total tokens written to prompt cache across all requests. - [JsonPropertyName("cacheWriteTokens")] - public required long CacheWriteTokens { 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; } +} - /// Total input tokens consumed across all requests to this model. - [JsonPropertyName("inputTokens")] - public required long InputTokens { get; set; } +/// Permission request completion notification signaling UI dismissal. +public sealed partial class PermissionCompletedData +{ + /// Request ID of the resolved permission request; clients should dismiss any UI for this request. + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } - /// Total output tokens produced across all requests to this model. - [JsonPropertyName("outputTokens")] - public required long OutputTokens { get; set; } + /// The result of the permission request. + [JsonPropertyName("result")] + public required PermissionResult Result { get; set; } - /// Total reasoning tokens produced across all requests to this model. + /// Optional tool call ID associated with this permission prompt; clients may use it to correlate UI created from tool-scoped prompts. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("reasoningTokens")] - public long? ReasoningTokens { get; set; } + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } } -/// Per-model shutdown metrics with request counts, token usage, nano-AI units, and token details. -/// Nested data type for ShutdownModelMetric. -public sealed partial class ShutdownModelMetric +/// User input request notification with question and optional predefined choices. +public sealed partial class UserInputRequestedData { - /// Request count and cost metrics. - [JsonPropertyName("requests")] - public required ShutdownModelMetricRequests Requests { get; set; } - - /// Token count details per type. + /// Whether the user can provide a free-form text response in addition to predefined choices. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("tokenDetails")] - public IDictionary? TokenDetails { get; set; } + [JsonPropertyName("allowFreeform")] + public bool? AllowFreeform { get; set; } - /// Accumulated nano-AI units cost for this model. - [Experimental(Diagnostics.Experimental)] + /// Predefined choices for the user to select from, if applicable. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("totalNanoAiu")] - public double? TotalNanoAiu { get; set; } + [JsonPropertyName("choices")] + public string[]? Choices { get; set; } - /// Token usage breakdown. - [JsonPropertyName("usage")] - public required ShutdownModelMetricUsage Usage { get; set; } -} - -/// A session-wide shutdown token-type entry storing the accumulated token count. -/// Nested data type for ShutdownTokenDetail. -public sealed partial class ShutdownTokenDetail -{ - /// Accumulated token count for this token type. - [JsonPropertyName("tokenCount")] - public required long TokenCount { get; set; } -} - -/// Internal prompt-cache expiration state for one model. -/// Nested data type for UsageCheckpointModelCacheState. -internal sealed partial class UsageCheckpointModelCacheState -{ - /// Latest known prompt-cache expiration. - [JsonPropertyName("cacheExpiresAt")] - public required DateTimeOffset CacheExpiresAt { get; set; } + /// The question or prompt to present to the user. + [JsonPropertyName("question")] + public required string Question { get; set; } - /// Retained cache lifetime in seconds, used to refresh expiration after a cache read. - [JsonInclude] - [JsonPropertyName("cacheTtlSeconds")] - internal required long CacheTtlSeconds { get; set; } + /// Unique identifier for this input request; used to respond via session.respondToUserInput(). + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } - /// Model identifier associated with this cache state. - [JsonPropertyName("modelId")] - public required string ModelId { get; set; } + /// The LLM-assigned tool call ID that triggered this request; used by remote UIs to correlate responses. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } } -/// Token usage detail for a single billing category. -/// Nested data type for CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail. -public sealed partial class CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail +/// User input request completion with the user's response. +public sealed partial class UserInputCompletedData { - /// Number of tokens in this billing batch. - [JsonPropertyName("batchSize")] - public required long BatchSize { get; set; } - - /// Cost per batch of tokens. - [JsonPropertyName("costPerBatch")] - public required long CostPerBatch { get; set; } + /// The user's answer to the input request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("answer")] + public string? Answer { get; set; } - /// Total token count for this entry. - [JsonPropertyName("tokenCount")] - public required long TokenCount { get; set; } + /// Request ID of the resolved user input request; clients should dismiss any UI for this request. + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } - /// Token category (e.g., "input", "output"). - [JsonPropertyName("tokenType")] - public required string TokenType { get; set; } + /// Whether the answer was typed as free-form text rather than selected from choices. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("wasFreeform")] + public bool? WasFreeform { get; set; } } -/// Per-request cost and usage data from the CAPI copilot_usage response field. -/// Nested data type for CompactionCompleteCompactionTokensUsedCopilotUsage. -internal sealed partial class CompactionCompleteCompactionTokensUsedCopilotUsage +/// Elicitation request; may be form-based (structured input) or URL-based (browser redirect). +public sealed partial class ElicitationRequestedData { - /// Itemized token usage breakdown. + /// The source that initiated the request (MCP server name, or absent for agent-initiated). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonInclude] - [JsonPropertyName("tokenDetails")] - internal CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail[]? TokenDetails { get; set; } + [JsonPropertyName("elicitationSource")] + public string? ElicitationSource { get; set; } - /// Total cost in nano-AI units for this request. - [JsonPropertyName("totalNanoAiu")] - public required double TotalNanoAiu { get; set; } -} + /// Message describing what information is needed from the user. + [JsonPropertyName("message")] + public required string Message { get; set; } -/// Token usage breakdown for the compaction LLM call (aligned with assistant.usage format). -/// Nested data type for CompactionCompleteCompactionTokensUsed. -public sealed partial class CompactionCompleteCompactionTokensUsed -{ - /// Cached input tokens reused in the compaction LLM call. + /// Elicitation mode; "form" for structured input, "url" for browser-based. Defaults to "form" when absent. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("cacheReadTokens")] - public long? CacheReadTokens { get; set; } + [JsonPropertyName("mode")] + public ElicitationRequestedMode? Mode { get; set; } - /// Tokens written to prompt cache in the compaction LLM call. + /// JSON Schema describing the form fields to present to the user (form mode only). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("cacheWriteTokens")] - public long? CacheWriteTokens { get; set; } + [JsonPropertyName("requestedSchema")] + public ElicitationRequestedSchema? RequestedSchema { get; set; } - /// Per-request cost and usage data from the CAPI copilot_usage response field. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonInclude] - [JsonPropertyName("copilotUsage")] - internal CompactionCompleteCompactionTokensUsedCopilotUsage? CopilotUsage { get; set; } + /// Unique identifier for this elicitation request; used to respond via session.respondToElicitation(). + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } - /// Duration of the compaction LLM call in milliseconds. - [JsonConverter(typeof(MillisecondsTimeSpanConverter))] + /// Tool call ID from the LLM completion; used to correlate with CompletionChunk.toolCall.id for remote UIs. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("duration")] - public TimeSpan? Duration { get; set; } + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } - /// Input tokens consumed by the compaction LLM call. + /// URL to open in the user's browser (url mode only). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("inputTokens")] - public long? InputTokens { get; set; } + [JsonPropertyName("url")] + public string? Url { get; set; } +} - /// Model identifier used for the compaction LLM call. +/// Elicitation request completion with the user's response. +public sealed partial class ElicitationCompletedData +{ + /// The user action: "accept" (submitted form), "decline" (explicitly refused), or "cancel" (dismissed). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("model")] - public string? Model { get; set; } + [JsonPropertyName("action")] + public ElicitationCompletedAction? Action { get; set; } - /// Output tokens produced by the compaction LLM call. + /// The submitted form data when action is 'accept'; keys match the requested schema fields. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("outputTokens")] - public long? OutputTokens { get; set; } + [JsonPropertyName("content")] + public IDictionary? Content { get; set; } + + /// Request ID of the resolved elicitation request; clients should dismiss any UI for this request. + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } } -/// Optional line range to scope the attachment to a specific section of the file. -/// Nested data type for AttachmentFileLineRange. -public sealed partial class AttachmentFileLineRange +/// Sampling request from an MCP server; contains the server name and a requestId for correlation. +public sealed partial class SamplingRequestedData { - /// End line number (1-based, inclusive). - [JsonPropertyName("end")] - public required long End { get; set; } + /// The JSON-RPC request ID from the MCP protocol. + [JsonPropertyName("mcpRequestId")] + public required JsonElement McpRequestId { get; set; } - /// Start line number (1-based). - [JsonPropertyName("start")] - public required long Start { get; set; } + /// Unique identifier for this sampling request; used to respond via session.respondToSampling(). + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } + + /// Name of the MCP server that initiated the sampling request. + [JsonPropertyName("serverName")] + public required string ServerName { get; set; } } -/// File attachment. -/// The file variant of . -public sealed partial class AttachmentFile : Attachment +/// Sampling request completion notification signaling UI dismissal. +public sealed partial class SamplingCompletedData { - /// - [JsonIgnore] - public override string Type => "file"; + /// Request ID of the resolved sampling request; clients should dismiss any UI for this request. + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } +} - /// Internal: content-addressed id of the session.binary_asset event holding this attachment's model-facing bytes (e.g. "sha256:..."). Absent externally. +/// OAuth authentication request for an MCP server. +public sealed partial class McpOauthRequiredData +{ + /// Raw HTTP response details from the OAuth auth challenge, as observed by the runtime. Header order and casing are transport-dependent, and duplicate header names may appear multiple times. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("assetId")] - public string? AssetId { get; set; } + [JsonPropertyName("httpResponse")] + public McpOauthHttpResponse? HttpResponse { get; set; } - /// Internal: decoded byte length of the attachment's model-facing bytes. Absent externally. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("byteLength")] - public long? ByteLength { get; set; } + /// Why the runtime is requesting host-provided OAuth credentials. + [JsonPropertyName("reason")] + public required McpOauthRequestReason Reason { get; set; } - /// User-facing display name for the attachment. - [JsonPropertyName("displayName")] - public required string DisplayName { get; set; } + /// Unique identifier for this OAuth request; used to respond via session.mcp.oauth.handlePendingRequest. + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } - /// Optional line range to scope the attachment to a specific section of the file. + /// Raw OAuth protected-resource metadata document fetched for the MCP server, if available. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("lineRange")] - public AttachmentFileLineRange? LineRange { get; set; } + [JsonPropertyName("resourceMetadata")] + public string? ResourceMetadata { get; set; } - /// Internal: MIME type of the file's model-facing bytes (post-resize for images). Set when the file's bytes are interned to an asset. Absent externally. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("mimeType")] - public string? MimeType { get; set; } + /// Display name of the MCP server that requires OAuth. + [JsonPropertyName("serverName")] + public required string ServerName { get; set; } - /// Internal: why model-facing bytes are absent from persistence. Absent externally. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("omittedReason")] - public OmittedBinaryOmittedReason? OmittedReason { get; set; } + /// URL of the MCP server that requires OAuth. + [JsonPropertyName("serverUrl")] + public required string ServerUrl { get; set; } - /// Absolute file path. - [JsonPropertyName("path")] - public required string Path { get; set; } + /// Static OAuth client configuration, if the server specifies one. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("staticClientConfig")] + public McpOauthRequiredStaticClientConfig? StaticClientConfig { get; set; } - /// Frozen rendered line this attachment contributed to the <tagged_files> prompt block (e.g. "* /path (123 lines)"). Captured at send time so resumed history reproduces the exact text the model saw, independent of later filesystem changes. Present only for attachments routed to <tagged_files> (mutually exclusive with assetId, which marks bytes sent natively). + /// OAuth WWW-Authenticate parameters parsed from the auth challenge, if available. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("taggedFilesEntry")] - public string? TaggedFilesEntry { get; set; } + [JsonPropertyName("wwwAuthenticateParams")] + public McpOauthWWWAuthenticateParams? WwwAuthenticateParams { get; set; } } -/// Directory attachment. -/// The directory variant of . -public sealed partial class AttachmentDirectory : Attachment +/// MCP OAuth request completion notification. +public sealed partial class McpOauthCompletedData { - /// - [JsonIgnore] - public override string Type => "directory"; - - /// 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; } + /// How the pending OAuth request was completed. + [JsonPropertyName("outcome")] + public required McpOauthCompletionOutcome Outcome { get; set; } - /// Frozen rendered line this attachment contributed to the <tagged_files> prompt block (e.g. "* /path (12 items)"). Captured at send time so resumed history reproduces the exact text the model saw, independent of later filesystem changes. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("taggedFilesEntry")] - public string? TaggedFilesEntry { get; set; } + /// Request ID of the resolved OAuth request. + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } } -/// End position of the selection. -/// Nested data type for AttachmentSelectionDetailsEnd. -public sealed partial class AttachmentSelectionDetailsEnd +/// Dynamic headers refresh request for a remote MCP server. +public sealed partial class McpHeadersRefreshRequiredData { - /// End character offset within the line (0-based). - [JsonPropertyName("character")] - public required long Character { get; set; } + /// Why dynamic headers are being requested. + [JsonPropertyName("reason")] + public required McpHeadersRefreshRequiredReason Reason { get; set; } - /// End line number (0-based). - [JsonPropertyName("line")] - public required long Line { get; set; } -} + /// Unique identifier for this headers refresh request; used to respond via session.mcp.headers.handlePendingHeadersRefreshRequest(). + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } -/// Start position of the selection. -/// Nested data type for AttachmentSelectionDetailsStart. -public sealed partial class AttachmentSelectionDetailsStart -{ - /// Start character offset within the line (0-based). - [JsonPropertyName("character")] - public required long Character { get; set; } + /// Display name of the remote MCP server requesting headers. + [JsonPropertyName("serverName")] + public required string ServerName { get; set; } - /// Start line number (0-based). - [JsonPropertyName("line")] - public required long Line { get; set; } + /// URL of the remote MCP server requesting headers. + [JsonPropertyName("serverUrl")] + public required string ServerUrl { get; set; } } -/// Position range of the selection within the file. -/// Nested data type for AttachmentSelectionDetails. -public sealed partial class AttachmentSelectionDetails +/// MCP headers refresh request completion notification. +public sealed partial class McpHeadersRefreshCompletedData { - /// End position of the selection. - [JsonPropertyName("end")] - public required AttachmentSelectionDetailsEnd End { get; set; } + /// How the pending MCP headers refresh request resolved. + [JsonPropertyName("outcome")] + public required McpHeadersRefreshCompletedOutcome Outcome { get; set; } - /// Start position of the selection. - [JsonPropertyName("start")] - public required AttachmentSelectionDetailsStart Start { get; set; } + /// Request ID of the resolved headers refresh request. + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } } -/// Code selection attachment from an editor. -/// The selection variant of . -public sealed partial class AttachmentSelection : Attachment +/// Opaque custom notification data. Consumers may branch on source and name, but payload semantics are source-defined. +public sealed partial class SessionCustomNotificationData { - /// - [JsonIgnore] - public override string Type => "selection"; + /// Source-defined custom notification name. + [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 required string Name { get; set; } - /// User-facing display name for the selection. - [JsonPropertyName("displayName")] - public required string DisplayName { get; set; } + /// Source-defined JSON payload for the custom notification. + [JsonPropertyName("payload")] + public required JsonElement Payload { get; set; } - /// Absolute path to the file containing the selection. - [JsonPropertyName("filePath")] - public required string FilePath { get; set; } + /// Namespace for the custom notification producer. + [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("source")] + public required string Source { get; set; } - /// Position range of the selection within the file. - [JsonPropertyName("selection")] - public required AttachmentSelectionDetails Selection { get; set; } + /// Optional source-defined string identifiers describing the payload subject. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("subject")] + public IDictionary? Subject { get; set; } - /// The selected text content. - [JsonPropertyName("text")] - public required string Text { get; set; } + /// Optional source-defined payload schema version. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("version")] + public long? Version { get; set; } } -/// GitHub issue, pull request, or discussion reference. -/// The github_reference variant of . -public sealed partial class AttachmentGitHubReference : Attachment +/// Ordered output and terminal state for a transient query that does not modify conversation history. +[Experimental(Diagnostics.Experimental)] +public sealed partial class UiEphemeralQueryData { - /// - [JsonIgnore] - public override string Type => "github_reference"; - - /// Issue, pull request, or discussion number. - [JsonPropertyName("number")] - public required long Number { get; set; } + /// Full response text, present for the `completed` phase. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("answer")] + public string? Answer { get; set; } - /// Type of GitHub reference. - [JsonPropertyName("referenceType")] - public required AttachmentGitHubReferenceType ReferenceType { get; set; } + /// Ordered text delta, present for the `chunk` phase. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("chunk")] + public string? Chunk { get; set; } - /// Current state of the referenced item (e.g., open, closed, merged). - [JsonPropertyName("state")] - public required string State { get; set; } + /// Model or transport failure message, present for the `failed` phase. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("error")] + public string? Error { get; set; } - /// Title of the referenced item. - [JsonPropertyName("title")] - public required string Title { get; set; } + /// Current query lifecycle phase. + [JsonPropertyName("phase")] + public required UIEphemeralQueryPhase Phase { get; set; } - /// URL to the referenced item on GitHub. - [JsonPropertyName("url")] - public required string Url { get; set; } + /// Runtime-minted query identifier. + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } } -/// Pointer to a GitHub repository. -/// Nested data type for GitHubRepoRef. -public sealed partial class GitHubRepoRef +/// External tool invocation request for client-side tool execution. +public sealed partial class ExternalToolRequestedData { - /// Numeric GitHub repository id. + /// Arguments to pass to the external tool. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("id")] - public long? Id { get; set; } + [JsonPropertyName("arguments")] + public JsonElement? Arguments { get; set; } - /// Repository name (without owner). - [JsonPropertyName("name")] - public required string Name { get; set; } + /// Stable provider identity captured with an extension-owned tool definition; hosts use it to route the request to the same provider that was offered to the model. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("providerId")] + public string? ProviderId { get; set; } - /// Repository owner login (user or organization). - [JsonPropertyName("owner")] - public required string Owner { get; set; } -} + /// Unique identifier for this request; used to respond via session.respondToExternalTool(). + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } -/// Pointer to a GitHub commit. -/// The github_commit variant of . -public sealed partial class AttachmentGitHubCommit : Attachment -{ - /// - [JsonIgnore] - public override string Type => "github_commit"; + /// Session ID that this external tool request belongs to. + [JsonPropertyName("sessionId")] + public required string SessionId { get; set; } - /// First line of the commit message. - [JsonPropertyName("message")] - public required string Message { get; set; } + /// Tool call ID assigned to this external tool invocation. + [JsonPropertyName("toolCallId")] + public required string ToolCallId { get; set; } - /// Full commit SHA. - [JsonPropertyName("oid")] - public required string Oid { get; set; } + /// Name of the external tool to invoke. + [JsonPropertyName("toolName")] + public required string ToolName { get; set; } - /// Repository the commit belongs to. - [JsonPropertyName("repo")] - public required GitHubRepoRef Repo { get; set; } + /// W3C Trace Context traceparent header for the execute_tool span. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("traceparent")] + public string? Traceparent { get; set; } - /// URL to the commit on GitHub. - [JsonPropertyName("url")] - public required string Url { get; set; } + /// W3C Trace Context tracestate header for the execute_tool span. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("tracestate")] + public string? Tracestate { get; set; } + + /// Active session working directory, when known. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("workingDirectory")] + public string? WorkingDirectory { get; set; } } -/// Pointer to a GitHub release. -/// The github_release variant of . -public sealed partial class AttachmentGitHubRelease : Attachment +/// External tool completion notification signaling UI dismissal. +public sealed partial class ExternalToolCompletedData { - /// - [JsonIgnore] - public override string Type => "github_release"; - - /// Human-readable release name. - [JsonPropertyName("name")] - public required string Name { get; set; } - - /// Repository the release belongs to. - [JsonPropertyName("repo")] - public required GitHubRepoRef Repo { get; set; } + /// Request ID of the resolved external tool request; clients should dismiss any UI for this request. + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } +} - /// Git tag the release is anchored to. - [JsonPropertyName("tagName")] - public required string TagName { get; set; } +/// Queued slash command dispatch request for client execution. +public sealed partial class CommandQueuedData +{ + /// The slash command text to be executed (e.g., /help, /clear). + [JsonPropertyName("command")] + public required string Command { get; set; } - /// URL to the release on GitHub. - [JsonPropertyName("url")] - public required string Url { get; set; } + /// Unique identifier for this request; used to respond via session.respondToQueuedCommand(). + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } } -/// Pointer to a GitHub Actions job. -/// The github_actions_job variant of . -public sealed partial class AttachmentGitHubActionsJob : Attachment +/// Registered command dispatch request routed to the owning client. +public sealed partial class CommandExecuteData { - /// - [JsonIgnore] - public override string Type => "github_actions_job"; + /// Raw argument string after the command name. + [JsonPropertyName("args")] + public required string Args { 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; } + /// The full command text (e.g., /deploy production). + [JsonPropertyName("command")] + public required string Command { get; set; } - /// Job id within the workflow run. - [JsonPropertyName("jobId")] - public required long JobId { get; set; } + /// Command name without leading /. + [JsonPropertyName("commandName")] + public required string CommandName { get; set; } - /// Display name of the job. - [JsonPropertyName("jobName")] - public required string JobName { get; set; } + /// Unique identifier; used to respond via session.commands.handlePendingCommand(). + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } +} - /// Repository the workflow run belongs to. - [JsonPropertyName("repo")] - public required GitHubRepoRef 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; } +/// Queued command completion notification signaling UI dismissal. +public sealed partial class CommandCompletedData +{ + /// Request ID of the resolved command request; clients should dismiss any UI for this request. + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } } -/// Pointer to a GitHub repository. -/// The github_repository variant of . -public sealed partial class AttachmentGitHubRepository : Attachment +/// Auto mode switch request notification requiring user approval. +public sealed partial class AutoModeSwitchRequestedData { - /// - [JsonIgnore] - public override string Type => "github_repository"; - - /// Short description of the repository. + /// The rate limit error code that triggered this request. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("description")] - public string? Description { get; set; } + [JsonPropertyName("errorCode")] + public string? ErrorCode { get; set; } - /// Git ref this attachment is anchored at (branch, tag, or commit). When absent the default branch is implied. + /// Unique identifier for this request; used to respond via session.respondToAutoModeSwitch(). + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } + + /// Seconds until the rate limit resets, when known. Lets clients render a humanized reset time alongside the prompt. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("ref")] - public string? Ref { get; set; } + [JsonPropertyName("retryAfterSeconds")] + public long? RetryAfterSeconds { get; set; } +} - /// Repository pointer. - [JsonPropertyName("repo")] - public required GitHubRepoRef Repo { get; set; } +/// Auto mode switch completion notification. +public sealed partial class AutoModeSwitchCompletedData +{ + /// Request ID of the resolved request; clients should dismiss any UI for this request. + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } - /// URL to the repository on GitHub. - [JsonPropertyName("url")] - public required string Url { get; set; } + /// The user's auto-mode-switch choice. + [JsonPropertyName("response")] + public required AutoModeSwitchResponse Response { get; set; } } -/// One side of a file diff (head or base). -/// Nested data type for AttachmentGitHubFileDiffSide. -public sealed partial class AttachmentGitHubFileDiffSide +/// Session limit exhaustion notification requiring user action. +public sealed partial class SessionLimitsExhaustedRequestedData { - /// Repository-relative path to the file. - [JsonPropertyName("path")] - public required string Path { get; set; } + /// Configured max AI Credits for the current accounting window. + [JsonPropertyName("maxAiCredits")] + public required double MaxAiCredits { get; set; } - /// Git ref (branch, tag, or commit SHA) the file is read at. - [JsonPropertyName("ref")] - public required string Ref { get; set; } + /// Unique identifier for this request; used to respond via session.ui.handlePendingSessionLimitsExhausted(). + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } - /// Repository the file lives in. - [JsonPropertyName("repo")] - public required GitHubRepoRef Repo { get; set; } + /// AI Credits already consumed in the current accounting window. + [JsonPropertyName("usedAiCredits")] + public required double UsedAiCredits { get; set; } } -/// Pointer to a single-file diff. At least one of `head` and `base` must be present. -/// The github_file_diff variant of . -public sealed partial class AttachmentGitHubFileDiff : Attachment +/// Session limit exhaustion prompt completion notification. +public sealed partial class SessionLimitsExhaustedCompletedData { - /// - [JsonIgnore] - public override string Type => "github_file_diff"; + /// Request ID of the resolved request; clients should dismiss any UI for this request. + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } - /// File location on the base side of the diff. Absent for additions. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("base")] - public AttachmentGitHubFileDiffSide? Base { get; set; } + /// The user's selected session-limit action. + [JsonPropertyName("response")] + public required SessionLimitsExhaustedResponse Response { get; set; } +} - /// File location on the head side of the diff. Absent for deletions. +/// 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(Diagnostics.Experimental)] +public sealed partial class SessionAutoModeResolvedData +{ + /// Models offered to the router for this resolution. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("head")] - public AttachmentGitHubFileDiffSide? Head { get; set; } + [JsonPropertyName("availableModels")] + public string[]? AvailableModels { 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; } -} + /// Ordered candidate model list the router returned, when not a fallback. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("candidateModels")] + public string[]? CandidateModels { get; set; } -/// One side of a tree comparison (head or base). -/// Nested data type for AttachmentGitHubTreeComparisonSide. -public sealed partial class AttachmentGitHubTreeComparisonSide -{ - /// Repository the revision belongs to. - [JsonPropertyName("repo")] - public required GitHubRepoRef Repo { get; set; } + /// 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. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("categoryScores")] + public IDictionary? CategoryScores { get; set; } - /// Git revision (branch, tag, or commit SHA). - [JsonPropertyName("revision")] - public required string Revision { get; set; } -} + /// The concrete model the session will use after any intent refinement. + [JsonPropertyName("chosenModel")] + public required string ChosenModel { get; set; } -/// Pointer to a comparison between two git revisions. -/// The github_tree_comparison variant of . -public sealed partial class AttachmentGitHubTreeComparison : Attachment -{ - /// - [JsonIgnore] - public override string Type => "github_tree_comparison"; + /// The chosen model's score shortfall relative to the top candidate. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("chosenShortfall")] + public double? ChosenShortfall { get; set; } - /// Base side of the comparison. - [JsonPropertyName("base")] - public required AttachmentGitHubTreeComparisonSide Base { get; set; } + /// Classifier confidence for the predicted label, when available. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("confidence")] + public double? Confidence { get; set; } - /// Head side of the comparison. - [JsonPropertyName("head")] - public required AttachmentGitHubTreeComparisonSide Head { 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; } - /// URL to the comparison on GitHub. - [JsonPropertyName("url")] - public required string Url { get; set; } -} + /// Whether the router fell back to the standard Auto selection. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("fallback")] + public bool? Fallback { get; set; } -/// Generic GitHub URL reference. -/// The github_url variant of . -public sealed partial class AttachmentGitHubUrl : Attachment -{ - /// - [JsonIgnore] - public override string Type => "github_url"; + /// Server-provided reason for falling back, when available. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("fallbackReason")] + public string? FallbackReason { get; set; } - /// URL to the GitHub resource. - [JsonPropertyName("url")] - public required string Url { get; set; } -} + /// Whether the routed prompt contained an image. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("hasImage")] + public bool? HasImage { get; set; } -/// Pointer to a file in a GitHub repository at a specific ref. -/// The github_file variant of . -public sealed partial class AttachmentGitHubFile : Attachment -{ - /// - [JsonIgnore] - public override string Type => "github_file"; + /// The predicted classifier label (e.g. `needs_reasoning`), when available. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("predictedLabel")] + public string? PredictedLabel { get; set; } - /// Repository-relative path to the file. - [JsonPropertyName("path")] - public required string Path { get; set; } + /// Coarse request-difficulty bucket, for explaining why a model was chosen ("picked X because this looks like high-reasoning work"). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("reasoningBucket")] + public AutoModeResolvedReasoningBucket? ReasoningBucket { get; set; } - /// Git ref the file is read at (branch, tag, or commit SHA). - [JsonPropertyName("ref")] - public required string Ref { get; set; } + /// Server-reported router processing time in milliseconds. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("routerLatencyMs")] + public double? RouterLatencyMs { get; set; } - /// Repository the file lives in. - [JsonPropertyName("repo")] - public required GitHubRepoRef Repo { get; set; } + /// The routing method the server applied, when Auto Intent ran. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("routingMethod")] + public string? RoutingMethod { get; set; } - /// URL to the file on GitHub. - [JsonPropertyName("url")] - public required string Url { get; set; } + /// Whether a sticky model choice overrode the router result. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("stickyOverride")] + public bool? StickyOverride { get; set; } } -/// Pointer to a line range inside a file in a GitHub repository. -/// The github_snippet variant of . -public sealed partial class AttachmentGitHubSnippet : Attachment +/// 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, then the policy helper, per ordinary key, while permissions compose restrictively across device, server, policy-helper, 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 { - /// - [JsonIgnore] - public override string Type => "github_snippet"; - - /// Line range the snippet covers. - [JsonPropertyName("lineRange")] - public required AttachmentFileLineRange LineRange { get; set; } + /// Whether enterprise policy disables bypass-permissions ("yolo") mode for this session. Deny-wins across layers, and forced on when `failClosed` is true. + [JsonPropertyName("bypassPermissionsDisabled")] + public required bool BypassPermissionsDisabled { get; set; } - /// Repository-relative path to the file. - [JsonPropertyName("path")] - public required string Path { get; set; } + /// Whether a session-local permissions layer injected by the SDK host was present. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("clientManaged")] + public bool? ClientManaged { get; set; } - /// Git ref the file is read at (branch, tag, or commit SHA). - [JsonPropertyName("ref")] - public required string Ref { get; set; } + /// Whether an actual device MDM/plist/registry/file managed-settings layer was present. + [JsonPropertyName("deviceManaged")] + public required bool DeviceManaged { get; set; } - /// Repository the file lives in. - [JsonPropertyName("repo")] - public required GitHubRepoRef Repo { get; set; } + /// 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. + [JsonPropertyName("failClosed")] + public required bool FailClosed { get; set; } - /// URL to the snippet on GitHub (with line anchor). - [JsonPropertyName("url")] - public required string Url { get; set; } -} - -/// Blob attachment with inline base64-encoded data. -/// The blob variant of . -public sealed partial class AttachmentBlob : Attachment -{ - /// - [JsonIgnore] - public override string Type => "blob"; - - /// Internal: content-addressed id of the session.binary_asset event holding this attachment's model-facing bytes (e.g. "sha256:..."). Absent externally. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("assetId")] - public string? AssetId { get; set; } + /// The setting keys under enterprise management in the effective managed settings (e.g. `model`, `enabledPlugins`, `permissions`). Empty when no managed settings are in force. + [JsonPropertyName("managedKeys")] + public required string[] ManagedKeys { get; set; } - /// Internal: decoded byte length of the attachment's model-facing bytes. Absent externally. + /// 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("byteLength")] - public long? ByteLength { get; set; } + [JsonPropertyName("permissionsAllowIntersected")] + public bool? PermissionsAllowIntersected { get; set; } - /// Base64-encoded content. Present on input and for external consumers; replaced by an internal `assetId` reference in persisted events when interned to a content-addressed asset. - [Base64String] + /// Whether the policy-helper managed-settings layer was present. The policy helper is the weakest channel: it fills keys no enterprise source set and can never replace one. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("data")] - public string? Data { get; set; } + [JsonPropertyName("policyHelperManaged")] + public bool? PolicyHelperManaged { get; set; } - /// User-facing display name for the attachment. + /// Whether the effective sandbox policy forces the sandbox on *only* because managed policy could not be determined, rather than because the policy requires it. Lets clients tell a user whose `--no-sandbox` was overridden that the sandbox stayed on as a fail-closed fallback, instead of attributing it to an administrator who set no such policy. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("displayName")] - public string? DisplayName { get; set; } + [JsonPropertyName("sandboxEnabledByUndeterminedPolicy")] + public bool? SandboxEnabledByUndeterminedPolicy { get; set; } - /// MIME type of the inline data. - [JsonPropertyName("mimeType")] - public required string MimeType { get; set; } + /// Whether the server (account/org) managed-settings layer was present. + [JsonPropertyName("serverManaged")] + public required bool ServerManaged { get; set; } - /// Internal: why model-facing bytes are absent from persistence. Absent externally. + /// The effective (resolved) managed settings values, so clients can render exactly what is enforced. Absent when no managed policy is in force. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("omittedReason")] - public OmittedBinaryOmittedReason? OmittedReason { get; set; } + [JsonPropertyName("settings")] + public JsonElement? Settings { get; set; } + + /// Channel summary: `server`, `device`, `client`, or `policyHelper` 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; } } -/// Structured context contributed by an extension. Composer pills displayed in the host are forwarded back through session.send.attachments, then rendered into the model prompt as an <extension_context> XML block. -/// The extension_context variant of . -public sealed partial class AttachmentExtensionContext : Attachment +/// Runtime enforcement of enterprise managed settings: fires when the session blocks or caps a runtime action because enterprise policy governs it, so SDK clients can explain *why* an action was governed. Unlike `session.managed_settings_resolved` (which reports *what* is managed), this reports a concrete governed action — e.g. a user or host tried to turn on a bypass-permissions escalation while policy disables it. Emitted live (not persisted to the session event log) on user/host-initiated attempts only, never for silent policy application. Marked experimental while the managed-settings surface stabilizes. +[Experimental(Diagnostics.Experimental)] +public sealed partial class SessionManagedSettingsEnforcedData { - /// - [JsonIgnore] - public override string Type => "extension_context"; + /// The category of runtime action that managed policy governed. + [JsonPropertyName("action")] + public required ManagedSettingsEnforcedAction Action { get; set; } - /// Provider-local canvas identifier when the push was bound to a canvas instance. + /// For a `bypass_permissions_blocked` action, which permission-escalation primitive was refused. Absent for actions without a specific escalation primitive. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("canvasId")] - public string? CanvasId { get; set; } - - /// ISO 8601 timestamp captured by the runtime when the push was accepted. - [JsonPropertyName("capturedAt")] - public required DateTimeOffset CapturedAt { get; set; } - - /// Owning extension identifier. Runtime-derived from the caller's connection when produced via session.extensions.sendAttachmentsToMessage; preserved verbatim on subsequent transports. - [JsonPropertyName("extensionId")] - public required string ExtensionId { get; set; } + [JsonPropertyName("escalation")] + public ManagedSettingsEnforcedEscalation? Escalation { get; set; } - /// Open canvas instance identifier when the push was bound to a canvas instance. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("instanceId")] - public string? InstanceId { get; set; } + /// Whether the enforcement was forced by fail-closed handling (managed policy could not be determined) rather than an explicit managed setting. When true, `setting` still names the restriction that was applied. + [JsonPropertyName("failClosed")] + public required bool FailClosed { get; set; } - /// Caller-supplied JSON payload. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("payload")] - public JsonElement? Payload { get; set; } + /// A human-readable explanation of why the action was governed, suitable for surfacing to the user. + [JsonPropertyName("message")] + public required string Message { 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; } + /// The managed setting key responsible for the enforcement (e.g. `permissions.disableBypassPermissionsMode`). + [JsonPropertyName("setting")] + public required string Setting { get; set; } } -/// A user message attachment — a file, directory, code selection, blob, GitHub reference, GitHub-anchored pointer, or extension-supplied context payload. -/// Polymorphic base type discriminated by type. -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "type", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(AttachmentFile), "file")] -[JsonDerivedType(typeof(AttachmentDirectory), "directory")] -[JsonDerivedType(typeof(AttachmentSelection), "selection")] -[JsonDerivedType(typeof(AttachmentGitHubReference), "github_reference")] -[JsonDerivedType(typeof(AttachmentGitHubCommit), "github_commit")] -[JsonDerivedType(typeof(AttachmentGitHubRelease), "github_release")] -[JsonDerivedType(typeof(AttachmentGitHubActionsJob), "github_actions_job")] -[JsonDerivedType(typeof(AttachmentGitHubRepository), "github_repository")] -[JsonDerivedType(typeof(AttachmentGitHubFileDiff), "github_file_diff")] -[JsonDerivedType(typeof(AttachmentGitHubTreeComparison), "github_tree_comparison")] -[JsonDerivedType(typeof(AttachmentGitHubUrl), "github_url")] -[JsonDerivedType(typeof(AttachmentGitHubFile), "github_file")] -[JsonDerivedType(typeof(AttachmentGitHubSnippet), "github_snippet")] -[JsonDerivedType(typeof(AttachmentBlob), "blob")] -[JsonDerivedType(typeof(AttachmentExtensionContext), "extension_context")] -public partial class Attachment +/// SDK command registration change notification. +public sealed partial class CommandsChangedData { - /// The type discriminator. - [JsonPropertyName("type")] - public virtual string Type { get; set; } = string.Empty; + /// Current list of registered SDK commands. + [JsonPropertyName("commands")] + public required CommandsChangedCommand[] Commands { get; set; } } +/// Session capability change notification. +public sealed partial class CapabilitiesChangedData +{ + /// UI capability changes. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("ui")] + public CapabilitiesChangedUI? Ui { get; set; } +} -/// A source that backs one or more cited spans in the assistant's response. -/// Nested data type for CitationSource. -[Experimental(Diagnostics.Experimental)] -public sealed partial class CitationSource +/// Plan approval request with plan content and available user actions. +public sealed partial class ExitPlanModeRequestedData { - /// Stable, turn-scoped identifier for this source, referenced by CitationReference.sourceId. - [JsonPropertyName("id")] - public required string Id { get; set; } + /// Available actions the user can take. + [JsonPropertyName("actions")] + public required ExitPlanModeAction[] Actions { get; set; } - /// File path relative to the agent's workspace root, when the source is a file. + /// Model the session had selected when the plan was authored, when one is known. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("path")] - public string? Path { get; set; } + [JsonPropertyName("model")] + public string? Model { get; set; } - /// The system that produced this citation. - [JsonPropertyName("provider")] - public required CitationProvider Provider { get; set; } + /// Full content of the plan file. + [JsonPropertyName("planContent")] + public required string PlanContent { get; set; } - /// Human-readable title of the source. + /// Recommended action to preselect for the user. + [JsonPropertyName("recommendedAction")] + public required ExitPlanModeAction RecommendedAction { get; set; } + + /// Unique identifier for this request; used to respond via session.respondToExitPlanMode(). + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } + + /// Summary of the plan that was created. + [JsonPropertyName("summary")] + public required string Summary { get; set; } +} + +/// Plan mode exit completion with the user's approval decision and optional feedback. +public sealed partial class ExitPlanModeCompletedData +{ + /// Whether the plan was approved by the user. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("title")] - public string? Title { get; set; } + [JsonPropertyName("approved")] + public bool? Approved { get; set; } - /// URL of the source, when it is a web resource. + /// Whether edits should be auto-approved without confirmation. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("url")] - public string? Url { get; set; } + [JsonPropertyName("autoApproveEdits")] + public bool? AutoApproveEdits { get; set; } + + /// Free-form feedback from the user if they requested changes to the plan. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("feedback")] + public string? Feedback { get; set; } + + /// Request ID of the resolved exit plan mode request; clients should dismiss any UI for this request. + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } + + /// Action selected by the user. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("selectedAction")] + public ExitPlanModeAction? SelectedAction { get; set; } } -/// A character range within the source's text content. -/// The char variant of . -[Experimental(Diagnostics.Experimental)] -public sealed partial class CitationLocationChar : CitationLocation +/// Payload of `session.tools_updated` identifying the model whose resolved tools were updated. +public sealed partial class SessionToolsUpdatedData { - /// - [JsonIgnore] - public override string Type => "char"; + /// Identifier of the model the resolved tools apply to. + [JsonPropertyName("model")] + public required string Model { get; set; } +} - /// End character offset within the source text (zero-based, exclusive). - [JsonPropertyName("endIndex")] - public required long EndIndex { get; set; } +/// Empty payload for `session.background_tasks_changed`, indicating background task state changed. +public sealed partial class SessionBackgroundTasksChangedData +{ +} - /// Start character offset within the source text (zero-based, inclusive). - [JsonPropertyName("startIndex")] - public required long StartIndex { get; set; } +/// 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; } + + /// Factory run identifier. + [JsonPropertyName("runId")] + public required string RunId { get; set; } } -/// A page range within a paginated source document. -/// The page variant of . +/// Ephemeral signal that a factory run attempt began executing. [Experimental(Diagnostics.Experimental)] -public sealed partial class CitationLocationPage : CitationLocation +public sealed partial class FactoryRunStartedData { - /// - [JsonIgnore] - public override string Type => "page"; + /// Attempt number this start committed; a resumed run increments it. + [JsonPropertyName("attempt")] + public required long Attempt { get; set; } - /// Last page number of the cited range (inclusive). - [JsonPropertyName("endPage")] - public required long EndPage { get; set; } + /// Name of the factory this run executes. Low cardinality by construction. + [JsonPropertyName("factoryName")] + public required string FactoryName { get; set; } - /// First page number of the cited range. - [JsonPropertyName("startPage")] - public required long StartPage { get; set; } + /// Identifier of the factory run that started. + [JsonPropertyName("runId")] + public required string RunId { get; set; } } -/// A content-block range within a structured source document. -/// The block variant of . +/// Ephemeral signal that a factory run reached a terminal status. [Experimental(Diagnostics.Experimental)] -public sealed partial class CitationLocationBlock : CitationLocation +public sealed partial class FactoryRunSettledData { - /// - [JsonIgnore] - public override string Type => "block"; + /// AI credits this run consumed, in nano-AIU. + [JsonPropertyName("consumedNanoAiu")] + public required long ConsumedNanoAiu { get; set; } - /// Index of the last content block of the cited range (zero-based, exclusive). - [JsonPropertyName("endBlock")] - public required long EndBlock { get; set; } + /// Subagents this run consumed against its limits. + [JsonPropertyName("consumedSubagents")] + public required long ConsumedSubagents { get; set; } - /// Index of the first content block of the cited range (zero-based, inclusive). - [JsonPropertyName("startBlock")] - public required long StartBlock { get; set; } + /// Active milliseconds accumulated across every attempt of this run. + [JsonPropertyName("elapsedMs")] + public required long ElapsedMs { get; set; } + + /// Typed failure class recorded on the run, when it failed with one (e.g. `factory_limit_reached`). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("failureType")] + public string? FailureType { get; set; } + + /// Identifier of the factory run that settled. + [JsonPropertyName("runId")] + public required string RunId { get; set; } + + /// Terminal status the run committed. + [JsonPropertyName("status")] + public required FactoryRunSettledStatus Status { get; set; } } -/// Location within a cited source (character, page, or content-block range) that supports a span. -/// Polymorphic base type discriminated by type. -[Experimental(Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "type", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(CitationLocationChar), "char")] -[JsonDerivedType(typeof(CitationLocationPage), "page")] -[JsonDerivedType(typeof(CitationLocationBlock), "block")] -public partial class CitationLocation +/// Payload of `session.skills_loaded` listing resolved skill metadata. +public sealed partial class SessionSkillsLoadedData { - /// The type discriminator. - [JsonPropertyName("type")] - public virtual string Type { get; set; } = string.Empty; + /// Array of resolved skill metadata. + [JsonPropertyName("skills")] + public required SkillsLoadedSkill[] Skills { get; set; } } - -/// A single citation occurrence linking a span of generated text to a supporting source. -/// Nested data type for CitationReference. -[Experimental(Diagnostics.Experimental)] -public sealed partial class CitationReference +/// Payload of `session.custom_agents_updated` with loaded custom agents plus non-fatal warnings and fatal errors. +public sealed partial class SessionCustomAgentsUpdatedData { - /// The exact text from the source that supports the cited span, when provided by the model. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("citedText")] - public string? CitedText { get; set; } + /// Array of loaded custom agent metadata. + [JsonPropertyName("agents")] + public required CustomAgentsUpdatedAgent[] Agents { get; set; } - /// Location within the source that supports the cited span, when the provider reports one. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("location")] - public CitationLocation? Location { get; set; } + /// Fatal errors from agent loading. + [JsonPropertyName("errors")] + public required string[] Errors { get; set; } - /// Provider-native citation correlation data (e.g. Anthropic search_result_index / document_index), passed through opaquely for debugging and forward compatibility. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("providerMetadata")] - public JsonElement? ProviderMetadata { get; set; } + /// Non-fatal warnings from agent loading. + [JsonPropertyName("warnings")] + public required string[] Warnings { get; set; } +} - /// Identifier of the CitationSource this reference points to (CitationSource.id). - [JsonPropertyName("sourceId")] - public required string SourceId { get; set; } +/// Payload of `session.mcp_servers_loaded` listing MCP server status summaries. +public sealed partial class SessionMcpServersLoadedData +{ + /// Array of MCP server status summaries. + [JsonPropertyName("servers")] + public required McpServersLoadedServer[] Servers { get; set; } } -/// A contiguous span of generated assistant text and the source references that support it. -/// Nested data type for CitationSpan. -[Experimental(Diagnostics.Experimental)] -public sealed partial class CitationSpan +/// Payload of `session.mcp_server_status_changed` for one MCP server's status and optional failure error. +public sealed partial class SessionMcpServerStatusChangedData { - /// End offset of the cited span within the final assistant message content (UTF-16 code units, zero-based, exclusive). - [JsonPropertyName("endIndex")] - public required long EndIndex { get; set; } + /// Error message if the server entered a failed state. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("error")] + public string? Error { get; set; } - /// The sources that support this span of generated text. - [JsonPropertyName("references")] - public required CitationReference[] References { get; set; } + /// Name of the MCP server whose status changed. + [JsonPropertyName("serverName")] + public required string ServerName { get; set; } - /// Start offset of the cited span within the final assistant message content (UTF-16 code units, zero-based, inclusive). - [JsonPropertyName("startIndex")] - public required long StartIndex { get; set; } + /// Connection status: connected, failed, needs-auth, pending, disabled, stopped, or not_configured. + [JsonPropertyName("status")] + public required McpServerStatus Status { get; set; } } -/// Provider-agnostic citations linking spans of the assistant's response to their supporting sources. -/// Nested data type for Citations. -[Experimental(Diagnostics.Experimental)] -public sealed partial class Citations +/// Payload identifying the MCP server associated with a list change. +public sealed partial class McpToolsListChangedData { - /// Deduplicated set of sources referenced by the citation spans. - [JsonPropertyName("sources")] - public required CitationSource[] Sources { get; set; } - - /// Spans of generated text annotated with the sources that support them. - [JsonPropertyName("spans")] - public required CitationSpan[] Spans { get; set; } + /// Name of the MCP server whose list changed. + [JsonPropertyName("serverName")] + public required string ServerName { get; set; } } -/// Neutral provider-tagged server-side tool-use payload (tool search, advisor) for verbatim round-tripping. -/// Nested data type for AssistantMessageServerTools. -[Experimental(Diagnostics.Experimental)] -public sealed partial class AssistantMessageServerTools +/// Payload identifying the MCP server associated with a list change. +public sealed partial class McpResourcesListChangedData { - /// Gets or sets the advisorModel value. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("advisorModel")] - public string? AdvisorModel { get; set; } - - /// Gets or sets the functionCallNamespaces value. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("functionCallNamespaces")] - public IDictionary? FunctionCallNamespaces { get; set; } - - /// Gets or sets the items value. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("items")] - public JsonElement[]? Items { get; set; } + /// Name of the MCP server whose list changed. + [JsonPropertyName("serverName")] + public required string ServerName { get; set; } +} - /// Gets or sets the provider value. - [JsonPropertyName("provider")] - public required string Provider { get; set; } +/// Payload identifying the MCP server associated with a list change. +public sealed partial class McpPromptsListChangedData +{ + /// Name of the MCP server whose list changed. + [JsonPropertyName("serverName")] + public required string ServerName { get; set; } +} - /// Gets or sets the rawContentBlocks value. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("rawContentBlocks")] - public JsonElement[]? RawContentBlocks { get; set; } +/// Payload of `session.extensions_loaded` listing discovered extensions and their statuses. +public sealed partial class SessionExtensionsLoadedData +{ + /// Array of discovered extensions and their status. + [JsonPropertyName("extensions")] + public required ExtensionsLoadedExtension[] Extensions { get; set; } } -/// A tool invocation request from the assistant. -/// Nested data type for AssistantMessageToolRequest. -public sealed partial class AssistantMessageToolRequest +/// Payload of `session.canvas.opened` with canvas instance and provider IDs plus optional icon, title, status, URL, and input. +[Experimental(Diagnostics.Experimental)] +public sealed partial class SessionCanvasOpenedData { - /// Arguments to pass to the tool, format depends on the tool. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("arguments")] - public JsonElement? Arguments { get; set; } + /// Provider-local canvas identifier. + [JsonPropertyName("canvasId")] + public required string CanvasId { get; set; } - /// Resolved intention summary describing what this specific call does. + /// Owning provider identifier. + [JsonPropertyName("extensionId")] + public required string ExtensionId { get; set; } + + /// Owning extension display name, when available. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("intentionSummary")] - public string? IntentionSummary { get; set; } + [JsonPropertyName("extensionName")] + public string? ExtensionName { get; set; } - /// Name of the MCP server hosting this tool, when the tool is an MCP tool. + /// Host-local PNG path for the canvas icon, when supplied. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("mcpServerName")] - public string? McpServerName { get; set; } + [JsonPropertyName("icon")] + public string? Icon { get; set; } - /// Original tool name on the MCP server, when the tool is an MCP tool. + /// Input supplied when the instance was opened. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("mcpToolName")] - public string? McpToolName { get; set; } + [JsonPropertyName("input")] + public JsonElement? Input { get; set; } - /// Name of the tool being invoked. - [JsonPropertyName("name")] - public required string Name { get; set; } + /// Stable caller-supplied canvas instance identifier. + [JsonPropertyName("instanceId")] + public required string InstanceId { get; set; } - /// Unique identifier for this tool call. - [JsonPropertyName("toolCallId")] - public required string ToolCallId { get; set; } + /// Provider-supplied status text. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("status")] + public string? Status { get; set; } - /// Human-readable display title for the tool. + /// Rendered title. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolTitle")] - public string? ToolTitle { get; set; } + [JsonPropertyName("title")] + public string? Title { get; set; } - /// Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. + /// URL for web-rendered canvases. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("type")] - public AssistantMessageToolRequestType? Type { get; set; } + [JsonPropertyName("url")] + public string? Url { get; set; } } -/// Token usage detail for a single billing category. -/// Nested data type for AssistantUsageCopilotUsageTokenDetail. -public sealed partial class AssistantUsageCopilotUsageTokenDetail +/// Payload of `session.canvas.registry_changed` listing the canvas declarations currently available. +[Experimental(Diagnostics.Experimental)] +public sealed partial class SessionCanvasRegistryChangedData { - /// Number of tokens in this billing batch. - [JsonPropertyName("batchSize")] - public required long BatchSize { get; set; } + /// Canvas declarations currently available. + [JsonPropertyName("canvases")] + public required CanvasRegistryChangedCanvas[] Canvases { get; set; } +} - /// Cost per batch of tokens. - [JsonPropertyName("costPerBatch")] - public required long CostPerBatch { get; set; } +/// Payload of `session.canvas.closed` with the closed canvas instance ID, provider ID, and canvas ID. +[Experimental(Diagnostics.Experimental)] +public sealed partial class SessionCanvasClosedData +{ + /// Provider-local canvas identifier. + [JsonPropertyName("canvasId")] + public required string CanvasId { get; set; } - /// Total token count for this entry. - [JsonPropertyName("tokenCount")] - public required long TokenCount { get; set; } + /// Owning provider identifier. + [JsonPropertyName("extensionId")] + public required string ExtensionId { get; set; } - /// Token category (e.g., "input", "output"). - [JsonPropertyName("tokenType")] - public required string TokenType { get; set; } + /// Stable caller-supplied identifier of the canvas instance that was closed. + [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("instanceId")] + public required string InstanceId { get; set; } } -/// Per-request cost and usage data from the CAPI copilot_usage response field. -/// Nested data type for AssistantUsageCopilotUsage. -public sealed partial class AssistantUsageCopilotUsage +/// Transient signal that an open canvas instance's provider has dropped (for example the extension is reloading mid-session). The host should keep the panel mounted and surface a reconnecting affordance rather than tearing it down; a subsequent `session.canvas.opened` for the same instanceId clears the affordance once the provider reconnects with a fresh url. Ephemeral and never persisted, so it is never replayed on cold resume. +[Experimental(Diagnostics.Experimental)] +public sealed partial class SessionCanvasUnavailableData { - /// Itemized token usage breakdown. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonInclude] - [JsonPropertyName("tokenDetails")] - internal AssistantUsageCopilotUsageTokenDetail[]? TokenDetails { get; set; } + /// Provider-local canvas identifier. + [JsonPropertyName("canvasId")] + public required string CanvasId { get; set; } - /// Total cost in nano-AI units for this request. - [JsonPropertyName("totalNanoAiu")] - public required double TotalNanoAiu { get; set; } + /// Owning provider identifier. + [JsonPropertyName("extensionId")] + public required string ExtensionId { get; set; } + + /// Stable caller-supplied identifier of the canvas instance whose provider became unavailable. + [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("instanceId")] + public required string InstanceId { get; set; } } -/// Internal per-quota snapshot for assistant usage, including entitlement, consumed requests, overage, reset date, and remaining quota. -/// Nested data type for AssistantUsageQuotaSnapshot. -internal sealed partial class AssistantUsageQuotaSnapshot +/// Durable record that a canvas instance is open, used to restore open canvases on cold session resume. Intentionally omits the transient url and availability. +[Experimental(Diagnostics.Experimental)] +public sealed partial class SessionCanvasRecordedData { - /// Total requests allowed by the entitlement. - [JsonInclude] - [JsonPropertyName("entitlementRequests")] - internal required long EntitlementRequests { get; set; } - - /// Whether the user currently has quota available for use. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonInclude] - [JsonPropertyName("hasQuota")] - internal bool? HasQuota { get; set; } - - /// Whether the user has an unlimited usage entitlement. - [JsonInclude] - [JsonPropertyName("isUnlimitedEntitlement")] - internal required bool IsUnlimitedEntitlement { get; set; } - - /// Number of additional usage requests made this period. - [JsonInclude] - [JsonPropertyName("overage")] - internal required double Overage { get; set; } + /// Provider-local canvas identifier. + [JsonPropertyName("canvasId")] + public required string CanvasId { get; set; } - /// Whether additional usage is allowed when quota is exhausted. - [JsonInclude] - [JsonPropertyName("overageAllowedWithExhaustedQuota")] - internal required bool OverageAllowedWithExhaustedQuota { get; set; } + /// Owning provider identifier. + [JsonPropertyName("extensionId")] + public required string ExtensionId { get; set; } - /// Pay-as-you-go additional-usage budget cap in AI credits (1 credit = $0.01); present only when CAPI emits a finite value. + /// Input supplied when the instance was opened. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonInclude] - [JsonPropertyName("overageEntitlement")] - internal double? OverageEntitlement { get; set; } + [JsonPropertyName("input")] + public JsonElement? Input { get; set; } - /// Percentage of quota remaining (0 to 100). - [JsonInclude] - [JsonPropertyName("remainingPercentage")] - internal required double RemainingPercentage { get; set; } + /// Stable caller-supplied canvas instance identifier. + [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("instanceId")] + public required string InstanceId { get; set; } - /// Date when the quota resets. + /// Rendered title. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonInclude] - [JsonPropertyName("resetDate")] - internal DateTimeOffset? ResetDate { get; set; } + [JsonPropertyName("title")] + public string? Title { get; set; } +} - /// Whether this snapshot uses token-based billing (AI-credits allocation). - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonInclude] - [JsonPropertyName("tokenBasedBilling")] - internal bool? TokenBasedBilling { get; set; } +/// Durable record that a canvas instance was closed, superseding a prior instance_recorded during resume replay. +[Experimental(Diagnostics.Experimental)] +public sealed partial class SessionCanvasRemovedData +{ + /// Provider-local canvas identifier. + [JsonPropertyName("canvasId")] + public required string CanvasId { get; set; } - /// Whether usage is still permitted after quota exhaustion. - [JsonInclude] - [JsonPropertyName("usageAllowedWithExhaustedQuota")] - internal required bool UsageAllowedWithExhaustedQuota { get; set; } + /// Owning provider identifier. + [JsonPropertyName("extensionId")] + public required string ExtensionId { get; set; } - /// Number of requests already consumed. - [JsonInclude] - [JsonPropertyName("usedRequests")] - internal required long UsedRequests { get; set; } + /// Stable caller-supplied identifier of the canvas instance that was closed. + [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("instanceId")] + public required string InstanceId { get; set; } } -/// Content-free structural summary of the failing request for diagnosing malformed 4xx calls. -/// Nested data type for ModelCallFailureRequestFingerprint. -public sealed partial class ModelCallFailureRequestFingerprint +/// Payload of `session.extensions.attachments_pushed` with extension-contributed attachments for the next send. +public sealed partial class SessionExtensionsAttachmentsPushedData { - /// Total number of image content parts. - [JsonPropertyName("imagePartCount")] - public required long ImagePartCount { get; set; } + /// Attachments contributed by an extension; the host should surface these as composer pills and forward them via the next session.send call. + [JsonPropertyName("attachments")] + public required Attachment[] Attachments { get; set; } +} - /// Image parts whose media type cannot be determined (rejected by strict providers). - [JsonPropertyName("imagePartsMissingMediaType")] - public required long ImagePartsMissingMediaType { get; set; } +/// MCP App view called a tool on a connected MCP server (SEP-1865). +public sealed partial class McpAppToolCallCompleteData +{ + /// Arguments passed to the tool by the app view, if any. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("arguments")] + public IDictionary? Arguments { get; set; } - /// Role of the final message in the request. + /// Wall-clock duration of the underlying tools/call in milliseconds. + [JsonPropertyName("durationMs")] + public required double DurationMs { get; set; } + + /// Set when the underlying tools/call threw an error before returning a CallToolResult. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("lastMessageRole")] - public string? LastMessageRole { get; set; } + [JsonPropertyName("error")] + public McpAppToolCallCompleteError? Error { get; set; } - /// Total number of messages in the request. - [JsonPropertyName("messageCount")] - public required long MessageCount { get; set; } + /// Standard MCP CallToolResult returned by the server. Present whether or not the call set isError. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("result")] + public IDictionary? Result { get; set; } - /// Tool calls whose name is missing or empty (rejected by strict providers). - [JsonPropertyName("namelessToolCallCount")] - public required long NamelessToolCallCount { get; set; } + /// Name of the MCP server hosting the tool. + [JsonPropertyName("serverName")] + public required string ServerName { get; set; } - /// Total number of tool calls across assistant messages. - [JsonPropertyName("toolCallCount")] - public required long ToolCallCount { get; set; } + /// True when the call completed without throwing AND the MCP CallToolResult did not set isError. + [JsonPropertyName("success")] + public required bool Success { get; set; } - /// Number of "tool" result messages in the request. - [JsonPropertyName("toolResultMessageCount")] - public required long ToolResultMessageCount { get; set; } + /// The tool's `_meta.ui` block at the time of the call, so consumers can decide whether to forward the result to the model without re-listing tools. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolMeta")] + public McpAppToolCallCompleteToolMeta? ToolMeta { get; set; } + + /// MCP tool name that was invoked. + [JsonPropertyName("toolName")] + public required string ToolName { get; set; } } -/// 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. -/// Nested data type for ToolExecutionStartShellToolInfo. -public sealed partial class ToolExecutionStartShellToolInfo +/// Working directory and git context at session start. +/// Nested data type for WorkingDirectoryContext. +public sealed partial class WorkingDirectoryContext { - /// 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)] + /// Base commit of current git branch at session start time. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("displayCommand")] - public string? DisplayCommand { get; set; } + [JsonPropertyName("baseCommit")] + public string? BaseCommit { get; set; } - /// Whether the command includes a file write redirection (e.g., > or >>). - [JsonPropertyName("hasWriteFileRedirection")] - public required bool HasWriteFileRedirection { get; set; } + /// Current git branch name. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("branch")] + public string? Branch { get; set; } - /// 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. - [JsonPropertyName("possiblePaths")] - public required string[] PossiblePaths { get; set; } -} + /// Current working directory path. + [JsonPropertyName("cwd")] + public required string Cwd { get; set; } -/// MCP Apps tool `_meta.ui` resource URI and visibility captured on `tool.execution_start`. -/// Nested data type for ToolExecutionStartToolDescriptionMetaUI. -public sealed partial class ToolExecutionStartToolDescriptionMetaUI -{ - /// URI of the UI resource. + /// Root directory of the git repository, resolved via git rev-parse. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("resourceUri")] - public string? ResourceUri { get; set; } + [JsonPropertyName("gitRoot")] + public string? GitRoot { get; set; } - /// Who can access this tool. + /// Head commit of current git branch at session start time. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("visibility")] - public ToolExecutionStartToolDescriptionMetaUIVisibility[]? Visibility { get; set; } -} + [JsonPropertyName("headCommit")] + public string? HeadCommit { get; set; } -/// MCP Apps metadata for UI resource association. -/// Nested data type for ToolExecutionStartToolDescriptionMeta. -public sealed partial class ToolExecutionStartToolDescriptionMeta -{ - /// MCP Apps tool `_meta.ui` resource URI and visibility captured on `tool.execution_start`. + /// Hosting platform type of the repository (github or ado). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("ui")] - public ToolExecutionStartToolDescriptionMetaUI? Ui { get; set; } -} + [JsonPropertyName("hostType")] + public WorkingDirectoryContextHostType? HostType { get; set; } -/// Tool definition metadata, present for MCP tools with MCP Apps support. -/// Nested data type for ToolExecutionStartToolDescription. -public sealed partial class ToolExecutionStartToolDescription -{ - /// MCP Apps metadata for UI resource association. + /// 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("_meta")] - public ToolExecutionStartToolDescriptionMeta? Meta { get; set; } + [JsonPropertyName("pendingGitContext")] + public bool? PendingGitContext { get; set; } - /// Tool description. + /// Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("description")] - public string? Description { get; set; } + [JsonPropertyName("repository")] + public string? Repository { get; set; } - /// Tool name. - [JsonPropertyName("name")] - public required string Name { get; set; } + /// Raw host string from the git remote URL (e.g. "github.com", "mycompany.ghe.com", "dev.azure.com"). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("repositoryHost")] + public string? RepositoryHost { get; set; } } -/// Error details when the tool execution failed. -/// Nested data type for ToolExecutionCompleteError. -public sealed partial class ToolExecutionCompleteError +/// Optional session limits. +/// Nested data type for SessionLimitsConfig. +public sealed partial class SessionLimitsConfig { - /// Machine-readable error code. + /// Maximum AI Credits allowed across the session's current accounting window. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("code")] - public string? Code { get; set; } - - /// Human-readable error message. - [JsonPropertyName("message")] - public required string Message { get; set; } + [JsonPropertyName("maxAiCredits")] + public double? MaxAiCredits { get; set; } } -/// Binary result returned by a tool for the model. -/// Nested data type for PersistedBinaryImage. -public sealed partial class PersistedBinaryImage +/// Repository context for the handed-off session. +/// Nested data type for HandoffRepository. +public sealed partial class HandoffRepository { - /// Base64-encoded binary data. - [Base64String] - [JsonPropertyName("data")] - public required string Data { get; set; } - - /// Human-readable description of the binary data. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("description")] - public string? Description { get; set; } - - /// Optional metadata from the producing tool. + /// Git branch name, if applicable. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("metadata")] - public IDictionary? Metadata { get; set; } + [JsonPropertyName("branch")] + public string? Branch { get; set; } - /// MIME type of the binary data. - [JsonPropertyName("mimeType")] - public required string MimeType { get; set; } + /// Repository name. + [JsonPropertyName("name")] + public required string Name { get; set; } - /// Binary result type discriminator. Use "image" for images and "resource" for other binary data. - [JsonPropertyName("type")] - public required PersistedBinaryImageType Type { get; set; } + /// Repository owner (user or organization). + [JsonPropertyName("owner")] + public required string Owner { get; set; } } -/// A binary result whose data was omitted from persistence due to the inline size limit. -/// Nested data type for OmittedBinaryResult. -[Experimental(Diagnostics.Experimental)] -public sealed partial class OmittedBinaryResult +/// Request count and cost metrics. +/// Nested data type for ShutdownModelMetricRequests. +public sealed partial class ShutdownModelMetricRequests { - /// Decoded byte length of the omitted binary data. - [JsonPropertyName("byteLength")] - public required long ByteLength { get; set; } - - /// Human-readable description of the binary data. + /// Cumulative cost multiplier for requests to this model. + [Experimental(Diagnostics.Experimental)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("description")] - public string? Description { get; set; } + [JsonPropertyName("cost")] + public double? Cost { get; set; } - /// Optional metadata from the producing tool. + /// Total number of API requests made to this model. + [Experimental(Diagnostics.Experimental)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("metadata")] - public IDictionary? Metadata { get; set; } + [JsonPropertyName("count")] + public long? Count { get; set; } +} - /// MIME type of the omitted binary data. - [JsonPropertyName("mimeType")] - public required string MimeType { get; set; } +/// A token-type entry in a shutdown model metric, storing the accumulated token count. +/// Nested data type for ShutdownModelMetricTokenDetail. +public sealed partial class ShutdownModelMetricTokenDetail +{ + /// Accumulated token count for this token type. + [JsonPropertyName("tokenCount")] + public required long TokenCount { get; set; } +} - /// Why the binary data is absent: it exceeded the inline size limit, or its asset was unavailable. - [JsonPropertyName("omittedReason")] - public required OmittedBinaryOmittedReason OmittedReason { get; set; } +/// Token usage breakdown. +/// Nested data type for ShutdownModelMetricUsage. +public sealed partial class ShutdownModelMetricUsage +{ + /// Total tokens read from prompt cache across all requests. + [JsonPropertyName("cacheReadTokens")] + public required long CacheReadTokens { get; set; } - /// Binary result type discriminator. Use "image" for images and "resource" for other binary data. - [JsonPropertyName("type")] - public required OmittedBinaryType Type { get; set; } + /// Total tokens written to prompt cache across all requests. + [JsonPropertyName("cacheWriteTokens")] + public required long CacheWriteTokens { get; set; } + + /// Total input tokens consumed across all requests to this model. + [JsonPropertyName("inputTokens")] + public required long InputTokens { get; set; } + + /// Total output tokens produced across all requests to this model. + [JsonPropertyName("outputTokens")] + public required long OutputTokens { get; set; } + + /// Total reasoning tokens produced across all requests to this model. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("reasoningTokens")] + public long? ReasoningTokens { get; set; } } -/// A reference to binary data persisted once on a session.binary_asset event and shared by id. -/// Nested data type for BinaryAssetReference. -[Experimental(Diagnostics.Experimental)] -public sealed partial class BinaryAssetReference +/// Per-model shutdown metrics with request counts, token usage, nano-AI units, and token details. +/// Nested data type for ShutdownModelMetric. +public sealed partial class ShutdownModelMetric { - /// Content-addressed id of the session.binary_asset event that holds this binary's bytes (e.g. "sha256:..."). - [JsonPropertyName("assetId")] - public required string AssetId { get; set; } + /// Request count and cost metrics. + [JsonPropertyName("requests")] + public required ShutdownModelMetricRequests Requests { get; set; } - /// Decoded byte length of the referenced binary data. - [JsonPropertyName("byteLength")] - public required long ByteLength { get; set; } + /// Token count details per type. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("tokenDetails")] + public IDictionary? TokenDetails { get; set; } - /// Human-readable description of the binary data. + /// Accumulated nano-AI units cost for this model. + [Experimental(Diagnostics.Experimental)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("description")] - public string? Description { get; set; } + [JsonPropertyName("totalNanoAiu")] + public double? TotalNanoAiu { get; set; } - /// Optional metadata from the producing tool. + /// Token usage breakdown. + [JsonPropertyName("usage")] + public required ShutdownModelMetricUsage Usage { get; set; } +} + +/// Usage attributed to one agent instance at session shutdown. +/// Nested data type for ShutdownAgentMetric. +public sealed partial class ShutdownAgentMetric +{ + /// Human-readable label for this subagent invocation, copied from the originating `subagent.started` event. For task-tool subagents this is the invocation's task description rather than the agent's configured display name, so group by `agentName` for stable per-agent labels. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("metadata")] - public IDictionary? Metadata { get; set; } + [JsonPropertyName("agentDisplayName")] + public string? AgentDisplayName { get; set; } - /// MIME type of the referenced binary data. - [JsonPropertyName("mimeType")] - public required string MimeType { get; set; } + /// Configured agent name, when this is a subagent. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("agentName")] + public string? AgentName { get; set; } - /// Binary result type discriminator. Use "image" for images and "resource" for other binary data. - [JsonPropertyName("type")] - public required BinaryAssetReferenceType Type { get; set; } + /// Per-model usage for this agent, keyed by model identifier. + [JsonPropertyName("modelMetrics")] + public required IDictionary ModelMetrics { get; set; } + + /// Time spent in model API calls by this agent, in milliseconds. + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] + [JsonPropertyName("totalApiDurationMs")] + public required TimeSpan TotalApiDuration { get; set; } + + /// Accumulated nano-AI units cost for this agent. + [JsonPropertyName("totalNanoAiu")] + public required double TotalNanoAiu { get; set; } } -/// A model-facing binary result as persisted: full inline data, a size-omitted marker, or a deduplicated asset reference. -/// JSON union data type for PersistedBinaryResult. -[JsonConverter(typeof(Converter))] -public sealed partial class PersistedBinaryResult +/// Aggregate code change metrics for the session. +/// Nested data type for ShutdownCodeChanges. +public sealed partial class ShutdownCodeChanges { - /// Gets the value when this instance contains . - public PersistedBinaryImage? PersistedBinaryImage { get; } + /// List of file paths that were modified during the session. + [JsonPropertyName("filesModified")] + public required string[] FilesModified { get; set; } - /// Gets the value when this instance contains . - public OmittedBinaryResult? OmittedBinaryResult { get; } + /// Total number of lines added during the session. + [JsonPropertyName("linesAdded")] + public required long LinesAdded { get; set; } - /// Gets the value when this instance contains . - public BinaryAssetReference? BinaryAssetReference { get; } + /// Total number of lines removed during the session. + [JsonPropertyName("linesRemoved")] + public required long LinesRemoved { get; set; } +} - /// Initializes a new instance of the class from . - public PersistedBinaryResult(PersistedBinaryImage value) - { - ArgumentNullException.ThrowIfNull(value); - PersistedBinaryImage = value; - } +/// A session-wide shutdown token-type entry storing the accumulated token count. +/// Nested data type for ShutdownTokenDetail. +public sealed partial class ShutdownTokenDetail +{ + /// Accumulated token count for this token type. + [JsonPropertyName("tokenCount")] + public required long TokenCount { get; set; } +} - /// Converts to . - public static implicit operator PersistedBinaryResult(PersistedBinaryImage value) => new(value); +/// Internal prompt-cache expiration state for one model. +/// Nested data type for UsageCheckpointModelCacheState. +internal sealed partial class UsageCheckpointModelCacheState +{ + /// Latest known prompt-cache expiration. + [JsonPropertyName("cacheExpiresAt")] + public required DateTimeOffset CacheExpiresAt { get; set; } - /// Initializes a new instance of the class from . - public PersistedBinaryResult(OmittedBinaryResult value) - { - ArgumentNullException.ThrowIfNull(value); - OmittedBinaryResult = value; - } + /// Retained cache lifetime in seconds, used to refresh expiration after a cache read. + [JsonInclude] + [JsonPropertyName("cacheTtlSeconds")] + internal required long CacheTtlSeconds { get; set; } - /// Converts to . - public static implicit operator PersistedBinaryResult(OmittedBinaryResult value) => new(value); + /// Model identifier associated with this cache state. + [JsonPropertyName("modelId")] + public required string ModelId { get; set; } +} - /// Initializes a new instance of the class from . - public PersistedBinaryResult(BinaryAssetReference value) - { - ArgumentNullException.ThrowIfNull(value); - BinaryAssetReference = value; - } +/// Token usage detail for a single billing category. +/// Nested data type for CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail. +public sealed partial class CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail +{ + /// Number of tokens in this billing batch. + [JsonPropertyName("batchSize")] + public required long BatchSize { get; set; } - /// Converts to . - public static implicit operator PersistedBinaryResult(BinaryAssetReference value) => new(value); + /// Cost per batch of tokens. + [JsonPropertyName("costPerBatch")] + public required long CostPerBatch { get; set; } - /// Provides a for serializing instances. - [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter - { - /// - public override PersistedBinaryResult Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (reader.TokenType == JsonTokenType.Null) - { - throw new JsonException("Expected JSON object for PersistedBinaryResult."); - } + /// Total token count for this entry. + [JsonPropertyName("tokenCount")] + public required long TokenCount { get; set; } - using var document = JsonDocument.ParseValue(ref reader); - var element = document.RootElement; - if (element.ValueKind == JsonValueKind.Object && element.TryGetProperty("data", out _) && !element.TryGetProperty("assetId", out _) && !element.TryGetProperty("byteLength", out _) && !element.TryGetProperty("omittedReason", out _)) - { - var persistedBinaryImage = JsonSerializer.Deserialize(element, SessionEventsJsonContext.Default.PersistedBinaryImage); - return persistedBinaryImage is null ? throw new JsonException("Expected PersistedBinaryImage value.") : new PersistedBinaryResult(persistedBinaryImage); - } - if (element.ValueKind == JsonValueKind.Object && element.TryGetProperty("omittedReason", out _) && !element.TryGetProperty("assetId", out _) && !element.TryGetProperty("data", out _)) - { - var omittedBinaryResult = JsonSerializer.Deserialize(element, SessionEventsJsonContext.Default.OmittedBinaryResult); - return omittedBinaryResult is null ? throw new JsonException("Expected OmittedBinaryResult value.") : new PersistedBinaryResult(omittedBinaryResult); - } - if (element.ValueKind == JsonValueKind.Object && element.TryGetProperty("assetId", out _) && !element.TryGetProperty("data", out _) && !element.TryGetProperty("omittedReason", out _)) - { - var binaryAssetReference = JsonSerializer.Deserialize(element, SessionEventsJsonContext.Default.BinaryAssetReference); - return binaryAssetReference is null ? throw new JsonException("Expected BinaryAssetReference value.") : new PersistedBinaryResult(binaryAssetReference); - } - - throw new JsonException("JSON value did not match any PersistedBinaryResult variant."); - } + /// Token category (e.g., "input", "output"). + [JsonPropertyName("tokenType")] + public required string TokenType { get; set; } +} - /// - public override void Write(Utf8JsonWriter writer, PersistedBinaryResult value, JsonSerializerOptions options) - { - if (value.PersistedBinaryImage is { } persistedBinaryImage) - { - JsonSerializer.Serialize(writer, persistedBinaryImage, SessionEventsJsonContext.Default.PersistedBinaryImage); - return; - } - if (value.OmittedBinaryResult is { } omittedBinaryResult) - { - JsonSerializer.Serialize(writer, omittedBinaryResult, SessionEventsJsonContext.Default.OmittedBinaryResult); - return; - } - if (value.BinaryAssetReference is { } binaryAssetReference) - { - JsonSerializer.Serialize(writer, binaryAssetReference, SessionEventsJsonContext.Default.BinaryAssetReference); - return; - } +/// Per-request cost and usage data from the CAPI copilot_usage response field. +/// Nested data type for CompactionCompleteCompactionTokensUsedCopilotUsage. +internal sealed partial class CompactionCompleteCompactionTokensUsedCopilotUsage +{ + /// Itemized token usage breakdown. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonInclude] + [JsonPropertyName("tokenDetails")] + internal CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail[]? TokenDetails { get; set; } - throw new JsonException("No PersistedBinaryResult variant value is set."); - } - } + /// Total cost in nano-AI units for this request. + [JsonPropertyName("totalNanoAiu")] + public required double TotalNanoAiu { get; set; } } -/// A source supplied by a tool that should be made available to the model as citable content. -/// Nested data type for CitableSource. -[Experimental(Diagnostics.Experimental)] -public sealed partial class CitableSource +/// Token usage breakdown for the compaction LLM call (aligned with assistant.usage format). +/// Nested data type for CompactionCompleteCompactionTokensUsed. +public sealed partial class CompactionCompleteCompactionTokensUsed { - /// The source text made available to the model as citable content. - [JsonPropertyName("content")] - public required string Content { get; set; } + /// Cached input tokens reused in the compaction LLM call. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("cacheReadTokens")] + public long? CacheReadTokens { get; set; } - /// Stable identifier for this source within the tool result. Used for deduplication and may be used by future provider integrations to correlate response citations back to the originating source. - [JsonPropertyName("id")] - public required string Id { get; set; } + /// Tokens written to prompt cache in the compaction LLM call. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("cacheWriteTokens")] + public long? CacheWriteTokens { get; set; } - /// File path relative to the agent's workspace root, when the source is a file. + /// Per-request cost and usage data from the CAPI copilot_usage response field. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("path")] - public string? Path { get; set; } + [JsonInclude] + [JsonPropertyName("copilotUsage")] + internal CompactionCompleteCompactionTokensUsedCopilotUsage? CopilotUsage { get; set; } - /// Human-readable title of the source. + /// Duration of the compaction LLM call in milliseconds. + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("title")] - public string? Title { get; set; } + [JsonPropertyName("duration")] + public TimeSpan? Duration { get; set; } - /// URL of the source, when it is a web resource. + /// Input tokens consumed by the compaction LLM call. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("url")] - public string? Url { get; set; } -} + [JsonPropertyName("inputTokens")] + public long? InputTokens { get; set; } -/// Plain text content block. -/// The text variant of . -public sealed partial class ToolExecutionCompleteContentText : ToolExecutionCompleteContent -{ - /// - [JsonIgnore] - public override string Type => "text"; + /// Model identifier used for the compaction LLM call. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("model")] + public string? Model { get; set; } - /// The text content. - [JsonPropertyName("text")] - public required string Text { get; set; } + /// Output tokens produced by the compaction LLM call. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("outputTokens")] + public long? OutputTokens { get; set; } } -/// Deprecated for shell command exit metadata. Use ToolExecutionCompleteContentShellExit instead. -/// The terminal variant of . -[EditorBrowsable(EditorBrowsableState.Never)] -#if NET5_0_OR_GREATER -[Obsolete("This member is deprecated and will be removed in a future version.", DiagnosticId = "GHCP001")] -#endif -public sealed partial class ToolExecutionCompleteContentTerminal : ToolExecutionCompleteContent +/// Inclusive durable event range summarized by a completion receipt. +/// Nested data type for CompletionReceiptEventRange. +public sealed partial class CompletionReceiptEventRange { - /// - [JsonIgnore] - public override string Type => "terminal"; + /// Identifier of the assistant turn-end event that ends the covered exchange. Always equals the receipt's sourceEventId, so either field is a valid join key. + [JsonPropertyName("endEventId")] + public required string EndEventId { get; set; } - /// Working directory where the command was executed. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("cwd")] - public string? Cwd { get; set; } + /// Identifier of the user message that starts the covered exchange. + [JsonPropertyName("startEventId")] + public required string StartEventId { get; set; } +} - /// Process exit code, if the command has completed. +/// Final structured tool completion in the covered event range. +/// Nested data type for CompletionReceiptFinalTool. +public sealed partial class CompletionReceiptFinalTool +{ + /// Process exit code from a structured shell result, when available. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("exitCode")] public long? ExitCode { get; set; } - /// Terminal/shell output text. - [JsonPropertyName("text")] - public required string Text { get; set; } -} - -/// Shell command exit metadata with optional output preview. -/// The shell_exit variant of . -public sealed partial class ToolExecutionCompleteContentShellExit : ToolExecutionCompleteContent -{ - /// - [JsonIgnore] - public override string Type => "shell_exit"; - - /// Working directory where the shell command was executed. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("cwd")] - public string? Cwd { get; set; } + /// Structured success or failure status from the tool completion event. + [JsonPropertyName("status")] + public required CompletionReceiptToolStatus Status { get; set; } - /// Exit code from the completed shell command. - [JsonPropertyName("exitCode")] - public required long ExitCode { get; set; } + /// Unique identifier of the completed tool call. + [JsonPropertyName("toolCallId")] + public required string ToolCallId { get; set; } - /// Output associated with this shell command, if available. May be partial, truncated, or a preview; not guaranteed to be full output. + /// Tool name from the matching tool execution start event, when available. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("outputPreview")] - public string? OutputPreview { get; set; } + [JsonPropertyName("toolName")] + public string? ToolName { get; set; } +} - /// Whether outputPreview is known to be incomplete or truncated. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("outputTruncated")] - public bool? OutputTruncated { get; set; } +/// Durable server recommendation for subsequent HydraFusion turns. +/// Nested data type for FusionFollowUpRecommendation. +[Experimental(Diagnostics.Experimental)] +public sealed partial class FusionFollowUpRecommendation +{ + /// Recommended routing action for the next compaction turn. + [JsonPropertyName("compactionTurn")] + public required FusionFollowUpAction CompactionTurn { get; set; } - /// Shell id, as assigned by Copilot runtime. - [JsonPropertyName("shellId")] - public required string ShellId { get; set; } + /// Recommended routing action for the next user-message turn. + [JsonPropertyName("userTurn")] + public required FusionFollowUpAction UserTurn { get; set; } } -/// Image content block with base64-encoded data. -/// The image variant of . -public sealed partial class ToolExecutionCompleteContentImage : ToolExecutionCompleteContent +/// Presentation-neutral phase planned for a HydraFusion turn. +/// Nested data type for FusionPhasePlanStep. +[Experimental(Diagnostics.Experimental)] +public sealed partial class FusionPhasePlanStep { - /// - [JsonIgnore] - public override string Type => "image"; + /// Whether the phase executes only when an earlier phase requests it. + [JsonPropertyName("conditional")] + public required bool Conditional { get; set; } - /// Base64-encoded image data. - [Base64String] - [JsonPropertyName("data")] - public required string Data { get; set; } + /// Kind of phase that may execute. + [JsonPropertyName("kind")] + public required FusionPhaseKind Kind { get; set; } - /// MIME type of the image (e.g., image/png, image/jpeg). - [JsonPropertyName("mimeType")] - public required string MimeType { get; set; } + /// Semantic role assigned to the phase. + [JsonPropertyName("role")] + public required string Role { get; set; } + + /// Conversation scope in which the phase executes. + [JsonPropertyName("scope")] + public required FusionConversationScope Scope { get; set; } } -/// Audio content block with base64-encoded data. -/// The audio variant of . -public sealed partial class ToolExecutionCompleteContentAudio : ToolExecutionCompleteContent +/// Validated HydraFusion routing capability scores. +/// Nested data type for FusionScores. +[Experimental(Diagnostics.Experimental)] +public sealed partial class FusionScores { - /// - [JsonIgnore] - public override string Type => "audio"; + /// Code-generation capability score returned by the authenticated router. + [JsonPropertyName("codeGen")] + public required double CodeGen { get; set; } - /// Base64-encoded audio data. - [Base64String] - [JsonPropertyName("data")] - public required string Data { get; set; } + /// Debugging capability score returned by the authenticated router. + [JsonPropertyName("debugging")] + public required double Debugging { get; set; } - /// MIME type of the audio (e.g., audio/wav, audio/mpeg). - [JsonPropertyName("mimeType")] - public required string MimeType { get; set; } + /// Reasoning capability score returned by the authenticated router. + [JsonPropertyName("reasoning")] + public required double Reasoning { get; set; } + + /// Tool-use capability score returned by the authenticated router. + [JsonPropertyName("toolUse")] + public required double ToolUse { get; set; } } -/// Icon image for a resource. -/// Nested data type for ToolExecutionCompleteContentResourceLinkIcon. -public sealed partial class ToolExecutionCompleteContentResourceLinkIcon +/// Optional line range to scope the attachment to a specific section of the file. +/// Nested data type for AttachmentFileLineRange. +public sealed partial class AttachmentFileLineRange { - /// MIME type of the icon image. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("mimeType")] - public string? MimeType { get; set; } - - /// Available icon sizes (e.g., ['16x16', '32x32']). - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("sizes")] - public string[]? Sizes { get; set; } - - /// URL or path to the icon image. - [JsonPropertyName("src")] - public required string Src { get; set; } + /// End line number (1-based, inclusive). + [JsonPropertyName("end")] + public required long End { get; set; } - /// Theme variant this icon is intended for. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("theme")] - public ToolExecutionCompleteContentResourceLinkIconTheme? Theme { get; set; } + /// Start line number (1-based). + [JsonPropertyName("start")] + public required long Start { get; set; } } -/// Resource link content block referencing an external resource. -/// The resource_link variant of . -public sealed partial class ToolExecutionCompleteContentResourceLink : ToolExecutionCompleteContent +/// File attachment. +/// The file variant of . +public sealed partial class AttachmentFile : Attachment { /// [JsonIgnore] - public override string Type => "resource_link"; + public override string Type => "file"; - /// Human-readable description of the resource. + /// Internal: content-addressed id of the session.binary_asset event holding this attachment's model-facing bytes (e.g. "sha256:..."). Absent externally. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("description")] - public string? Description { get; set; } + [JsonPropertyName("assetId")] + public string? AssetId { get; set; } - /// Icons associated with this resource. + /// Internal: decoded byte length of the attachment's model-facing bytes. Absent externally. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("icons")] - public ToolExecutionCompleteContentResourceLinkIcon[]? Icons { get; set; } + [JsonPropertyName("byteLength")] + public long? ByteLength { get; set; } - /// MIME type of the resource content. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("mimeType")] - public string? MimeType { get; set; } - - /// Resource name identifier. - [JsonPropertyName("name")] - public required string Name { get; set; } - - /// Size of the resource in bytes. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("size")] - public long? Size { get; set; } + /// User-facing display name for the attachment. + [JsonPropertyName("displayName")] + public required string DisplayName { get; set; } - /// Human-readable display title for the resource. + /// Optional line range to scope the attachment to a specific section of the file. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("title")] - public string? Title { get; set; } - - /// URI identifying the resource. - [JsonPropertyName("uri")] - public required string Uri { get; set; } -} + [JsonPropertyName("lineRange")] + public AttachmentFileLineRange? LineRange { get; set; } -/// Embedded text resource contents identified by a URI, with an optional MIME type and a text payload. -/// Nested data type for EmbeddedTextResourceContents. -public sealed partial class EmbeddedTextResourceContents -{ - /// MIME type of the text content. + /// Internal: MIME type of the file's model-facing bytes (post-resize for images). Set when the file's bytes are interned to an asset. Absent externally. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("mimeType")] public string? MimeType { get; set; } - /// Text content of the resource. - [JsonPropertyName("text")] - public required string Text { get; set; } + /// Internal: why model-facing bytes are absent from persistence. Absent externally. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("omittedReason")] + public OmittedBinaryOmittedReason? OmittedReason { get; set; } - /// URI identifying the resource. - [JsonPropertyName("uri")] - public required string Uri { get; set; } + /// Absolute file path. + [JsonPropertyName("path")] + public required string Path { get; set; } + + /// Frozen rendered line this attachment contributed to the <tagged_files> prompt block (e.g. "* /path (123 lines)"). Captured at send time so resumed history reproduces the exact text the model saw, independent of later filesystem changes. Present only for attachments routed to <tagged_files> (mutually exclusive with assetId, which marks bytes sent natively). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("taggedFilesEntry")] + public string? TaggedFilesEntry { get; set; } } -/// Embedded binary resource contents identified by a URI, with an optional MIME type and a base64-encoded blob. -/// Nested data type for EmbeddedBlobResourceContents. -public sealed partial class EmbeddedBlobResourceContents +/// Directory attachment. +/// The directory variant of . +public sealed partial class AttachmentDirectory : Attachment { - /// Base64-encoded binary content of the resource. - [Base64String] - [JsonPropertyName("blob")] - public required string Blob { get; set; } + /// + [JsonIgnore] + public override string Type => "directory"; - /// MIME type of the blob content. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("mimeType")] - public string? MimeType { get; set; } + /// User-facing display name for the attachment. + [JsonPropertyName("displayName")] + public required string DisplayName { get; set; } - /// URI identifying the resource. - [JsonPropertyName("uri")] - public required string Uri { get; set; } + /// Absolute directory path. + [JsonPropertyName("path")] + public required string Path { get; set; } + + /// Frozen rendered line this attachment contributed to the <tagged_files> prompt block (e.g. "* /path (12 items)"). Captured at send time so resumed history reproduces the exact text the model saw, independent of later filesystem changes. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("taggedFilesEntry")] + public string? TaggedFilesEntry { get; set; } } -/// The embedded resource contents, either text or base64-encoded binary. -/// JSON union data type for ToolExecutionCompleteContentResourceDetails. -[JsonConverter(typeof(Converter))] -public sealed partial class ToolExecutionCompleteContentResourceDetails +/// End position of the selection. +/// Nested data type for AttachmentSelectionDetailsEnd. +public sealed partial class AttachmentSelectionDetailsEnd { - /// Gets the value when this instance contains . - public EmbeddedTextResourceContents? EmbeddedTextResourceContents { get; } + /// End character offset within the line (0-based). + [JsonPropertyName("character")] + public required long Character { get; set; } - /// Gets the value when this instance contains . - public EmbeddedBlobResourceContents? EmbeddedBlobResourceContents { get; } + /// End line number (0-based). + [JsonPropertyName("line")] + public required long Line { get; set; } +} - /// Initializes a new instance of the class from . - public ToolExecutionCompleteContentResourceDetails(EmbeddedTextResourceContents value) - { - ArgumentNullException.ThrowIfNull(value); - EmbeddedTextResourceContents = value; - } +/// Start position of the selection. +/// Nested data type for AttachmentSelectionDetailsStart. +public sealed partial class AttachmentSelectionDetailsStart +{ + /// Start character offset within the line (0-based). + [JsonPropertyName("character")] + public required long Character { get; set; } - /// Converts to . - public static implicit operator ToolExecutionCompleteContentResourceDetails(EmbeddedTextResourceContents value) => new(value); + /// Start line number (0-based). + [JsonPropertyName("line")] + public required long Line { get; set; } +} - /// Initializes a new instance of the class from . - public ToolExecutionCompleteContentResourceDetails(EmbeddedBlobResourceContents value) - { - ArgumentNullException.ThrowIfNull(value); - EmbeddedBlobResourceContents = value; - } +/// Position range of the selection within the file. +/// Nested data type for AttachmentSelectionDetails. +public sealed partial class AttachmentSelectionDetails +{ + /// End position of the selection. + [JsonPropertyName("end")] + public required AttachmentSelectionDetailsEnd End { get; set; } - /// Converts to . - public static implicit operator ToolExecutionCompleteContentResourceDetails(EmbeddedBlobResourceContents value) => new(value); + /// Start position of the selection. + [JsonPropertyName("start")] + public required AttachmentSelectionDetailsStart Start { get; set; } +} - /// Provides a for serializing instances. - [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter - { - /// - public override ToolExecutionCompleteContentResourceDetails Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (reader.TokenType == JsonTokenType.Null) - { - throw new JsonException("Expected JSON object for ToolExecutionCompleteContentResourceDetails."); - } +/// Code selection attachment from an editor. +/// The selection variant of . +public sealed partial class AttachmentSelection : Attachment +{ + /// + [JsonIgnore] + public override string Type => "selection"; - using var document = JsonDocument.ParseValue(ref reader); - var element = document.RootElement; - if (element.ValueKind == JsonValueKind.Object && element.TryGetProperty("text", out _) && !element.TryGetProperty("blob", out _)) - { - var embeddedTextResourceContents = JsonSerializer.Deserialize(element, SessionEventsJsonContext.Default.EmbeddedTextResourceContents); - return embeddedTextResourceContents is null ? throw new JsonException("Expected EmbeddedTextResourceContents value.") : new ToolExecutionCompleteContentResourceDetails(embeddedTextResourceContents); - } - if (element.ValueKind == JsonValueKind.Object && element.TryGetProperty("blob", out _) && !element.TryGetProperty("text", out _)) - { - var embeddedBlobResourceContents = JsonSerializer.Deserialize(element, SessionEventsJsonContext.Default.EmbeddedBlobResourceContents); - return embeddedBlobResourceContents is null ? throw new JsonException("Expected EmbeddedBlobResourceContents value.") : new ToolExecutionCompleteContentResourceDetails(embeddedBlobResourceContents); - } + /// User-facing display name for the selection. + [JsonPropertyName("displayName")] + public required string DisplayName { get; set; } - throw new JsonException("JSON value did not match any ToolExecutionCompleteContentResourceDetails variant."); - } + /// Absolute path to the file containing the selection. + [JsonPropertyName("filePath")] + public required string FilePath { get; set; } - /// - public override void Write(Utf8JsonWriter writer, ToolExecutionCompleteContentResourceDetails value, JsonSerializerOptions options) - { - if (value.EmbeddedTextResourceContents is { } embeddedTextResourceContents) - { - JsonSerializer.Serialize(writer, embeddedTextResourceContents, SessionEventsJsonContext.Default.EmbeddedTextResourceContents); - return; - } - if (value.EmbeddedBlobResourceContents is { } embeddedBlobResourceContents) - { - JsonSerializer.Serialize(writer, embeddedBlobResourceContents, SessionEventsJsonContext.Default.EmbeddedBlobResourceContents); - return; - } + /// Position range of the selection within the file. + [JsonPropertyName("selection")] + public required AttachmentSelectionDetails Selection { get; set; } - throw new JsonException("No ToolExecutionCompleteContentResourceDetails variant value is set."); - } - } + /// The selected text content. + [JsonPropertyName("text")] + public required string Text { get; set; } } -/// Embedded resource content block with inline text or binary data. -/// The resource variant of . -public sealed partial class ToolExecutionCompleteContentResource : ToolExecutionCompleteContent +/// GitHub issue, pull request, or discussion reference. +/// The github_reference variant of . +public sealed partial class AttachmentGitHubReference : Attachment { /// [JsonIgnore] - public override string Type => "resource"; + public override string Type => "github_reference"; - /// The embedded resource contents, either text or base64-encoded binary. - [JsonPropertyName("resource")] - public required ToolExecutionCompleteContentResourceDetails Resource { get; set; } -} + /// Issue, pull request, or discussion number. + [JsonPropertyName("number")] + public required long Number { get; set; } -/// A content block within a tool result, which may be text, terminal output, image, audio, or a resource. -/// Polymorphic base type discriminated by type. -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "type", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(ToolExecutionCompleteContentText), "text")] -[JsonDerivedType(typeof(ToolExecutionCompleteContentTerminal), "terminal")] -[JsonDerivedType(typeof(ToolExecutionCompleteContentShellExit), "shell_exit")] -[JsonDerivedType(typeof(ToolExecutionCompleteContentImage), "image")] -[JsonDerivedType(typeof(ToolExecutionCompleteContentAudio), "audio")] -[JsonDerivedType(typeof(ToolExecutionCompleteContentResourceLink), "resource_link")] -[JsonDerivedType(typeof(ToolExecutionCompleteContentResource), "resource")] -public partial class ToolExecutionCompleteContent -{ - /// The type discriminator. - [JsonPropertyName("type")] - public virtual string Type { get; set; } = string.Empty; -} + /// Type of GitHub reference. + [JsonPropertyName("referenceType")] + public required AttachmentGitHubReferenceType ReferenceType { get; set; } + /// Current state of the referenced item (e.g., open, closed, merged). + [JsonPropertyName("state")] + public required string State { get; set; } -/// CSP domain allowlists for an MCP Apps UI resource, including connect, resource, frame, and base URI domains. -/// Nested data type for ToolExecutionCompleteUIResourceMetaUICsp. -public sealed partial class ToolExecutionCompleteUIResourceMetaUICsp + /// 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; } +} + +/// Pointer to a GitHub repository. +/// Nested data type for GitHubRepoRef. +public sealed partial class GitHubRepoRef { - /// Gets or sets the baseUriDomains value. + /// Numeric GitHub repository id. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("baseUriDomains")] - public string[]? BaseUriDomains { get; set; } + [JsonPropertyName("id")] + public long? Id { get; set; } - /// Gets or sets the connectDomains value. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("connectDomains")] - public string[]? ConnectDomains { get; set; } - - /// Gets or sets the frameDomains value. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("frameDomains")] - public string[]? FrameDomains { get; set; } + /// Repository name (without owner). + [JsonPropertyName("name")] + public required string Name { get; set; } - /// Gets or sets the resourceDomains value. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("resourceDomains")] - public string[]? ResourceDomains { get; set; } + /// Repository owner login (user or organization). + [JsonPropertyName("owner")] + public required string Owner { get; set; } } -/// Marker object for camera permission on an MCP Apps UI resource. -/// Nested data type for ToolExecutionCompleteUIResourceMetaUIPermissionsCamera. -public sealed partial class ToolExecutionCompleteUIResourceMetaUIPermissionsCamera +/// Pointer to a GitHub commit. +/// The github_commit variant of . +public sealed partial class AttachmentGitHubCommit : Attachment { -} + /// + [JsonIgnore] + public override string Type => "github_commit"; -/// Marker object for clipboard-write permission on an MCP Apps UI resource. -/// Nested data type for ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite. -public sealed partial class ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite -{ -} + /// First line of the commit message. + [JsonPropertyName("message")] + public required string Message { get; set; } -/// Marker object for geolocation permission on an MCP Apps UI resource. -/// Nested data type for ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation. -public sealed partial class ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation -{ -} + /// Full commit SHA. + [JsonPropertyName("oid")] + public required string Oid { get; set; } -/// Marker object for microphone permission on an MCP Apps UI resource. -/// Nested data type for ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone. -public sealed partial class ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone -{ + /// Repository the commit belongs to. + [JsonPropertyName("repo")] + public required GitHubRepoRef Repo { get; set; } + + /// URL to the commit on GitHub. + [JsonPropertyName("url")] + public required string Url { get; set; } } -/// Browser permission metadata for an MCP Apps UI resource, including camera, microphone, geolocation, and clipboard-write. -/// Nested data type for ToolExecutionCompleteUIResourceMetaUIPermissions. -public sealed partial class ToolExecutionCompleteUIResourceMetaUIPermissions +/// Pointer to a GitHub release. +/// The github_release variant of . +public sealed partial class AttachmentGitHubRelease : Attachment { - /// Marker object for camera permission on an MCP Apps UI resource. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("camera")] - public ToolExecutionCompleteUIResourceMetaUIPermissionsCamera? Camera { get; set; } + /// + [JsonIgnore] + public override string Type => "github_release"; - /// Marker object for clipboard-write permission on an MCP Apps UI resource. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("clipboardWrite")] - public ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite? ClipboardWrite { get; set; } + /// Human-readable release name. + [JsonPropertyName("name")] + public required string Name { get; set; } - /// Marker object for geolocation permission on an MCP Apps UI resource. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("geolocation")] - public ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation? Geolocation { get; set; } + /// Repository the release belongs to. + [JsonPropertyName("repo")] + public required GitHubRepoRef Repo { get; set; } - /// Marker object for microphone permission on an MCP Apps UI resource. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("microphone")] - public ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone? Microphone { 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; } } -/// MCP Apps UI resource metadata for a completed tool result, including CSP, permissions, domain, and border preference. -/// Nested data type for ToolExecutionCompleteUIResourceMetaUI. -public sealed partial class ToolExecutionCompleteUIResourceMetaUI +/// Pointer to a GitHub Actions job. +/// The github_actions_job variant of . +public sealed partial class AttachmentGitHubActionsJob : Attachment { - /// CSP domain allowlists for an MCP Apps UI resource, including connect, resource, frame, and base URI domains. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("csp")] - public ToolExecutionCompleteUIResourceMetaUICsp? Csp { get; set; } + /// + [JsonIgnore] + public override string Type => "github_actions_job"; - /// Gets or sets the domain value. + /// Terminal conclusion of the job when finished (e.g., success, failure, cancelled). Absent for in-progress jobs. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("domain")] - public string? Domain { get; set; } + [JsonPropertyName("conclusion")] + public string? Conclusion { get; set; } - /// Browser permission metadata for an MCP Apps UI resource, including camera, microphone, geolocation, and clipboard-write. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("permissions")] - public ToolExecutionCompleteUIResourceMetaUIPermissions? Permissions { get; set; } + /// Job id within the workflow run. + [JsonPropertyName("jobId")] + public required long JobId { get; set; } - /// Gets or sets the prefersBorder value. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("prefersBorder")] - public bool? PrefersBorder { get; set; } -} + /// Display name of the job. + [JsonPropertyName("jobName")] + public required string JobName { get; set; } -/// Resource-level UI metadata (CSP, permissions, visual preferences). -/// Nested data type for ToolExecutionCompleteUIResourceMeta. -public sealed partial class ToolExecutionCompleteUIResourceMeta -{ - /// MCP Apps UI resource metadata for a completed tool result, including CSP, permissions, domain, and border preference. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("ui")] - public ToolExecutionCompleteUIResourceMetaUI? Ui { get; set; } + /// Repository the workflow run belongs to. + [JsonPropertyName("repo")] + public required GitHubRepoRef 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; } } -/// MCP Apps UI resource content for rendering in a sandboxed iframe. -/// Nested data type for ToolExecutionCompleteUIResource. -public sealed partial class ToolExecutionCompleteUIResource +/// Pointer to a GitHub repository. +/// The github_repository variant of . +public sealed partial class AttachmentGitHubRepository : Attachment { - /// Resource-level UI metadata (CSP, permissions, visual preferences). - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("_meta")] - public ToolExecutionCompleteUIResourceMeta? Meta { get; set; } + /// + [JsonIgnore] + public override string Type => "github_repository"; - /// Base64-encoded HTML content. + /// Short description of the repository. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("blob")] - public string? Blob { get; set; } - - /// MIME type of the content. - [JsonPropertyName("mimeType")] - public required string MimeType { get; set; } + [JsonPropertyName("description")] + public string? Description { get; set; } - /// HTML content as a string. + /// Git ref this attachment is anchored at (branch, tag, or commit). When absent the default branch is implied. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("text")] - public string? Text { get; set; } + [JsonPropertyName("ref")] + public string? Ref { get; set; } - /// The ui:// URI of the resource. - [JsonPropertyName("uri")] - public required string Uri { get; set; } + /// Repository pointer. + [JsonPropertyName("repo")] + public required GitHubRepoRef Repo { get; set; } + + /// URL to the repository on GitHub. + [JsonPropertyName("url")] + public required string Url { get; set; } } -/// Tool execution result on success. -/// Nested data type for ToolExecutionCompleteResult. -public sealed partial class ToolExecutionCompleteResult +/// One side of a file diff (head or base). +/// Nested data type for AttachmentGitHubFileDiffSide. +public sealed partial class AttachmentGitHubFileDiffSide { - /// Model-facing binary results (base64 inline or size-omitted markers) sent to the LLM for this tool call. - [Experimental(Diagnostics.Experimental)] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("binaryResultsForLlm")] - public PersistedBinaryResult[]? BinaryResultsForLlm { get; set; } - - /// Provider-neutral source material this tool makes available to the model as citable content. Persisted so it survives session resume. Experimental. - [Experimental(Diagnostics.Experimental)] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("citableSources")] - public CitableSource[]? CitableSources { get; set; } + /// Repository-relative path to the file. + [JsonPropertyName("path")] + public required string Path { get; set; } - /// Concise tool result text sent to the LLM for chat completion, potentially truncated for token efficiency. - [JsonPropertyName("content")] - public required string Content { get; set; } + /// Git ref (branch, tag, or commit SHA) the file is read at. + [JsonPropertyName("ref")] + public required string Ref { get; set; } - /// Structured content blocks (text, images, audio, resources) returned by the tool in their native format. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("contents")] - public ToolExecutionCompleteContent[]? Contents { get; set; } + /// Repository the file lives in. + [JsonPropertyName("repo")] + public required GitHubRepoRef Repo { get; set; } +} - /// Full detailed tool result for UI/timeline display, preserving complete content such as diffs. Falls back to content when absent. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("detailedContent")] - public string? DetailedContent { get; set; } +/// Pointer to a single-file diff. At least one of `head` and `base` must be present. +/// The github_file_diff variant of . +public sealed partial class AttachmentGitHubFileDiff : Attachment +{ + /// + [JsonIgnore] + public override string Type => "github_file_diff"; - /// FIDES IFC label projected from tool ingress metadata (MCP `CallToolResult._meta` or synthesized built-in ingress labels) — persisted as `{ ifc: ... }` (only the `ifc` key, not the whole `_meta`). Persisted so the FIDES IFC label survives session resume: the engine rehydrates accumulated taint by replaying these on load. Populated for ingress sources when FIDES IFC is on. Experimental. - [Experimental(Diagnostics.Experimental)] + /// File location on the base side of the diff. Absent for additions. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("mcpMeta")] - public JsonElement? McpMeta { get; set; } + [JsonPropertyName("base")] + public AttachmentGitHubFileDiffSide? Base { get; set; } - /// Structured content (arbitrary JSON) returned verbatim by the MCP tool. + /// File location on the head side of the diff. Absent for deletions. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("structuredContent")] - public JsonElement? StructuredContent { get; set; } + [JsonPropertyName("head")] + public AttachmentGitHubFileDiffSide? Head { get; set; } - /// MCP Apps UI resource content for rendering in a sandboxed iframe. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("uiResource")] - public ToolExecutionCompleteUIResource? UiResource { 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; } } -/// MCP Apps tool `_meta.ui` resource URI and visibility captured on `tool.execution_complete`. -/// Nested data type for ToolExecutionCompleteToolDescriptionMetaUI. -public sealed partial class ToolExecutionCompleteToolDescriptionMetaUI +/// One side of a tree comparison (head or base). +/// Nested data type for AttachmentGitHubTreeComparisonSide. +public sealed partial class AttachmentGitHubTreeComparisonSide { - /// URI of the UI resource. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("resourceUri")] - public string? ResourceUri { get; set; } - - /// Who can access this tool. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("visibility")] - public ToolExecutionCompleteToolDescriptionMetaUIVisibility[]? Visibility { get; set; } -} - -/// MCP Apps metadata for UI resource association. -/// Nested data type for ToolExecutionCompleteToolDescriptionMeta. -public sealed partial class ToolExecutionCompleteToolDescriptionMeta -{ - /// MCP Apps tool `_meta.ui` resource URI and visibility captured on `tool.execution_complete`. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("ui")] - public ToolExecutionCompleteToolDescriptionMetaUI? Ui { get; set; } -} - -/// Tool definition metadata, present for MCP tools with MCP Apps support. -/// Nested data type for ToolExecutionCompleteToolDescription. -public sealed partial class ToolExecutionCompleteToolDescription -{ - /// MCP Apps metadata for UI resource association. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("_meta")] - public ToolExecutionCompleteToolDescriptionMeta? Meta { get; set; } - - /// Tool description. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("description")] - public string? Description { get; set; } + /// Repository the revision belongs to. + [JsonPropertyName("repo")] + public required GitHubRepoRef Repo { get; set; } - /// Tool name. - [JsonPropertyName("name")] - public required string Name { get; set; } + /// Git revision (branch, tag, or commit SHA). + [JsonPropertyName("revision")] + public required string Revision { get; set; } } -/// Error details when the hook failed. -/// Nested data type for HookEndError. -public sealed partial class HookEndError +/// Pointer to a comparison between two git revisions. +/// The github_tree_comparison variant of . +public sealed partial class AttachmentGitHubTreeComparison : Attachment { - /// Human-readable error message. - [JsonPropertyName("message")] - public required string Message { get; set; } - - /// Source label of the hook that errored (e.g. the plugin it was loaded from), when known. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("source")] - public string? Source { get; set; } + /// + [JsonIgnore] + public override string Type => "github_tree_comparison"; - /// Error stack trace, when available. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("stack")] - public string? Stack { get; set; } -} + /// Base side of the comparison. + [JsonPropertyName("base")] + public required AttachmentGitHubTreeComparisonSide Base { get; set; } -/// Metadata about the prompt template and its construction. -/// Nested data type for SystemMessageMetadata. -public sealed partial class SystemMessageMetadata -{ - /// Version identifier of the prompt template used. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("promptVersion")] - public string? PromptVersion { get; set; } + /// Head side of the comparison. + [JsonPropertyName("head")] + public required AttachmentGitHubTreeComparisonSide Head { get; set; } - /// Template variables used when constructing the prompt. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("variables")] - public IDictionary? Variables { get; set; } + /// URL to the comparison on GitHub. + [JsonPropertyName("url")] + public required string Url { get; set; } } -/// System notification metadata for a background agent that completed or failed, including agent ID, type, status, description, and prompt. -/// The agent_completed variant of . -public sealed partial class SystemNotificationAgentCompleted : SystemNotification +/// Generic GitHub URL reference. +/// The github_url variant of . +public sealed partial class AttachmentGitHubUrl : Attachment { /// [JsonIgnore] - public override string Type => "agent_completed"; - - /// Unique identifier of the background agent. - [JsonPropertyName("agentId")] - public required string AgentId { get; set; } - - /// Type of the agent (e.g., explore, task, general-purpose). - [JsonPropertyName("agentType")] - public required string AgentType { get; set; } - - /// Human-readable description of the agent task. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("description")] - public string? Description { get; set; } - - /// The full prompt given to the background agent. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("prompt")] - public string? Prompt { get; set; } + public override string Type => "github_url"; - /// Whether the agent completed successfully or failed. - [JsonPropertyName("status")] - public required SystemNotificationAgentCompletedStatus Status { get; set; } + /// URL to the GitHub resource. + [JsonPropertyName("url")] + public required string Url { get; set; } } -/// System notification metadata for a background agent that became idle, including agent ID, type, and description. -/// The agent_idle variant of . -public sealed partial class SystemNotificationAgentIdle : SystemNotification +/// Pointer to a file in a GitHub repository at a specific ref. +/// The github_file variant of . +public sealed partial class AttachmentGitHubFile : Attachment { /// [JsonIgnore] - public override string Type => "agent_idle"; + public override string Type => "github_file"; - /// Unique identifier of the background agent. - [JsonPropertyName("agentId")] - public required string AgentId { get; set; } + /// Repository-relative path to the file. + [JsonPropertyName("path")] + public required string Path { get; set; } - /// Type of the agent (e.g., explore, task, general-purpose). - [JsonPropertyName("agentType")] - public required string AgentType { get; set; } + /// Git ref the file is read at (branch, tag, or commit SHA). + [JsonPropertyName("ref")] + public required string Ref { get; set; } - /// Human-readable description of the agent task. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("description")] - public string? Description { get; set; } + /// Repository the file lives in. + [JsonPropertyName("repo")] + public required GitHubRepoRef Repo { get; set; } + + /// URL to the file on GitHub. + [JsonPropertyName("url")] + public required string Url { get; set; } } -/// System notification metadata for a new inbox message, including entry ID, sender details, and summary. -/// The new_inbox_message variant of . -public sealed partial class SystemNotificationNewInboxMessage : SystemNotification +/// Pointer to a line range inside a file in a GitHub repository. +/// The github_snippet variant of . +public sealed partial class AttachmentGitHubSnippet : Attachment { /// [JsonIgnore] - public override string Type => "new_inbox_message"; + public override string Type => "github_snippet"; - /// Unique identifier of the inbox entry. - [JsonPropertyName("entryId")] - public required string EntryId { get; set; } + /// Line range the snippet covers. + [JsonPropertyName("lineRange")] + public required AttachmentFileLineRange LineRange { get; set; } - /// Human-readable name of the sender. - [JsonPropertyName("senderName")] - public required string SenderName { get; set; } + /// Repository-relative path to the file. + [JsonPropertyName("path")] + public required string Path { get; set; } - /// Category of the sender (e.g., sidekick-agent, plugin, hook). - [JsonPropertyName("senderType")] - public required string SenderType { get; set; } + /// Git ref the file is read at (branch, tag, or commit SHA). + [JsonPropertyName("ref")] + public required string Ref { get; set; } - /// Short summary shown before the agent decides whether to read the inbox. - [JsonPropertyName("summary")] - public required string Summary { get; set; } + /// Repository the file lives in. + [JsonPropertyName("repo")] + public required GitHubRepoRef Repo { get; set; } + + /// URL to the snippet on GitHub (with line anchor). + [JsonPropertyName("url")] + public required string Url { get; set; } } -/// System notification metadata for a shell session that completed, including shell ID, optional exit code, and description. -/// The shell_completed variant of . -public sealed partial class SystemNotificationShellCompleted : SystemNotification +/// Blob attachment with inline base64-encoded data. +/// The blob variant of . +public sealed partial class AttachmentBlob : Attachment { /// [JsonIgnore] - public override string Type => "shell_completed"; + public override string Type => "blob"; - /// Human-readable description of the command. + /// Internal: content-addressed id of the session.binary_asset event holding this attachment's model-facing bytes (e.g. "sha256:..."). Absent externally. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("description")] - public string? Description { get; set; } + [JsonPropertyName("assetId")] + public string? AssetId { get; set; } - /// Exit code of the shell command, if available. + /// Internal: decoded byte length of the attachment's model-facing bytes. Absent externally. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("exitCode")] - public long? ExitCode { get; set; } - - /// Unique identifier of the shell session. - [JsonPropertyName("shellId")] - public required string ShellId { get; set; } -} + [JsonPropertyName("byteLength")] + public long? ByteLength { get; set; } -/// System notification metadata for a detached shell session that completed, including shell ID and description. -/// The shell_detached_completed variant of . -public sealed partial class SystemNotificationShellDetachedCompleted : SystemNotification -{ - /// - [JsonIgnore] - public override string Type => "shell_detached_completed"; + /// Base64-encoded content. Present on input and for external consumers; replaced by an internal `assetId` reference in persisted events when interned to a content-addressed asset. + [Base64String] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("data")] + public string? Data { get; set; } - /// Human-readable description of the command. + /// User-facing display name for the attachment. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("description")] - public string? Description { get; set; } + [JsonPropertyName("displayName")] + public string? DisplayName { get; set; } - /// Unique identifier of the detached shell session. - [JsonPropertyName("shellId")] - public required string ShellId { get; set; } + /// MIME type of the inline data. + [JsonPropertyName("mimeType")] + public required string MimeType { get; set; } + + /// Internal: why model-facing bytes are absent from persistence. Absent externally. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("omittedReason")] + public OmittedBinaryOmittedReason? OmittedReason { get; set; } } -/// System notification metadata for an instruction file discovered during tool access, including source, trigger file, and tool. -/// The instruction_discovered variant of . -public sealed partial class SystemNotificationInstructionDiscovered : SystemNotification +/// Structured context contributed by an extension. Composer pills displayed in the host are forwarded back through session.send.attachments, then rendered into the model prompt as an <extension_context> XML block. +/// The extension_context variant of . +public sealed partial class AttachmentExtensionContext : Attachment { /// [JsonIgnore] - public override string Type => "instruction_discovered"; + public override string Type => "extension_context"; - /// Human-readable label for the timeline (e.g., 'AGENTS.md from packages/billing/'). + /// Provider-local canvas identifier when the push was bound to a canvas instance. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("description")] - public string? Description { get; set; } - - /// Relative path to the discovered instruction file. - [JsonPropertyName("sourcePath")] - public required string SourcePath { get; set; } + [JsonPropertyName("canvasId")] + public string? CanvasId { get; set; } - /// Path of the file access that triggered discovery. - [JsonPropertyName("triggerFile")] - public required string TriggerFile { get; set; } + /// ISO 8601 timestamp captured by the runtime when the push was accepted. + [JsonPropertyName("capturedAt")] + public required DateTimeOffset CapturedAt { get; set; } - /// Tool command that triggered discovery (currently always 'view'). - [JsonPropertyName("triggerTool")] - 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; } + /// Owning extension identifier. Runtime-derived from the caller's connection when produced via session.extensions.sendAttachmentsToMessage; preserved verbatim on subsequent transports. + [JsonPropertyName("extensionId")] + public required string ExtensionId { 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)] + /// Open canvas instance identifier when the push was bound to a canvas instance. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("resultPreview")] - public string? ResultPreview { get; set; } + [JsonPropertyName("instanceId")] + public string? InstanceId { get; set; } - /// Actionable run_factory resume guidance for a resource-limit failure. + /// Caller-supplied JSON payload. [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"; + [JsonPropertyName("payload")] + public JsonElement? Payload { get; set; } - /// Opaque metadata supplied by the external host, when present. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("metadata")] - public JsonElement? Metadata { 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; } } -/// Structured metadata identifying what triggered this notification. +/// A user message attachment — a file, directory, code selection, blob, GitHub reference, GitHub-anchored pointer, or extension-supplied context payload. /// Polymorphic base type discriminated by type. [JsonPolymorphic( TypeDiscriminatorPropertyName = "type", UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(SystemNotificationAgentCompleted), "agent_completed")] -[JsonDerivedType(typeof(SystemNotificationAgentIdle), "agent_idle")] -[JsonDerivedType(typeof(SystemNotificationNewInboxMessage), "new_inbox_message")] -[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 +[JsonDerivedType(typeof(AttachmentFile), "file")] +[JsonDerivedType(typeof(AttachmentDirectory), "directory")] +[JsonDerivedType(typeof(AttachmentSelection), "selection")] +[JsonDerivedType(typeof(AttachmentGitHubReference), "github_reference")] +[JsonDerivedType(typeof(AttachmentGitHubCommit), "github_commit")] +[JsonDerivedType(typeof(AttachmentGitHubRelease), "github_release")] +[JsonDerivedType(typeof(AttachmentGitHubActionsJob), "github_actions_job")] +[JsonDerivedType(typeof(AttachmentGitHubRepository), "github_repository")] +[JsonDerivedType(typeof(AttachmentGitHubFileDiff), "github_file_diff")] +[JsonDerivedType(typeof(AttachmentGitHubTreeComparison), "github_tree_comparison")] +[JsonDerivedType(typeof(AttachmentGitHubUrl), "github_url")] +[JsonDerivedType(typeof(AttachmentGitHubFile), "github_file")] +[JsonDerivedType(typeof(AttachmentGitHubSnippet), "github_snippet")] +[JsonDerivedType(typeof(AttachmentBlob), "blob")] +[JsonDerivedType(typeof(AttachmentExtensionContext), "extension_context")] +public partial class Attachment { /// The type discriminator. [JsonPropertyName("type")] @@ -7015,1812 +7024,4636 @@ public partial class SystemNotification } -/// A parsed command identifier in a shell permission request, including whether it is read-only. -/// Nested data type for PermissionRequestShellCommand. -public sealed partial class PermissionRequestShellCommand +/// Internal durable terminal request staged by a HydraFusion phase until an idempotent final commit selects it. +/// Nested data type for FusionStagedTerminal. +[Experimental(Diagnostics.Experimental)] +internal sealed partial class FusionStagedTerminal { - /// Command identifier (e.g., executable name). - [JsonPropertyName("identifier")] - public required string Identifier { get; set; } + /// Gets or sets the arguments value. + [JsonPropertyName("arguments")] + public required string Arguments { get; set; } - /// Whether this command is read-only (no side effects). - [JsonPropertyName("readOnly")] - public required bool ReadOnly { get; set; } -} + /// Gets or sets the assistantMessage value. + [JsonPropertyName("assistantMessage")] + public required JsonElement AssistantMessage { 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; } + /// Gets or sets the phaseId value. + [JsonPropertyName("phaseId")] + public required string PhaseId { get; set; } - /// Command identifier (e.g., executable name). - [JsonPropertyName("identifier")] - public required string Identifier { get; set; } -} + /// Gets or sets the toolCallId value. + [JsonPropertyName("toolCallId")] + public required string ToolCallId { 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 -{ - /// URL that may be accessed by the command. - [JsonPropertyName("url")] - public required string Url { get; set; } + /// Gets or sets the toolName value. + [JsonPropertyName("toolName")] + public required string ToolName { get; set; } } -/// Shell command permission request. -/// The shell variant of . -public sealed partial class PermissionRequestShell : PermissionRequest +/// Aggregate concrete-model usage for one HydraFusion phase. +/// Nested data type for FusionPhaseUsage. +[Experimental(Diagnostics.Experimental)] +public sealed partial class FusionPhaseUsage { - /// - [JsonIgnore] - public override string Kind => "shell"; - - /// Whether the UI can offer session-wide approval for this command pattern. - [JsonPropertyName("canOfferSessionApproval")] - public required bool CanOfferSessionApproval { get; set; } + /// Total cached input tokens reported for the phase. + [JsonPropertyName("cachedTokens")] + public required long CachedTokens { get; set; } - /// Parsed command identifiers found in the command text. - [JsonPropertyName("commands")] - public required PermissionRequestShellCommand[] Commands { get; set; } - - /// Parsed command segments, including arguments, used for managed policy matching. + /// Total tokens written to prompt cache during the phase. [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; } + [JsonPropertyName("cacheWriteTokens")] + public long? CacheWriteTokens { get; set; } - /// Whether the command includes a file write redirection (e.g., > or >>). - [JsonPropertyName("hasWriteFileRedirection")] - public required bool HasWriteFileRedirection { get; set; } + /// Total input tokens consumed by the phase. + [JsonPropertyName("inputTokens")] + public required long InputTokens { get; set; } - /// Human-readable description of what the command intends to do. - [JsonPropertyName("intention")] - public required string Intention { get; set; } + /// Total output tokens produced by the phase. + [JsonPropertyName("outputTokens")] + public required long OutputTokens { get; set; } - /// - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("managedApprovalRequired")] - public override bool? ManagedApprovalRequired - { - get => base.ManagedApprovalRequired; - set => base.ManagedApprovalRequired = value; - } + /// Number of concrete model requests made by the phase. + [JsonPropertyName("requestCount")] + public required long RequestCount { get; set; } - /// File paths that may be read or written by the command. - [JsonPropertyName("possiblePaths")] - public required string[] PossiblePaths { get; set; } + /// Total normalized AI-unit cost reported for the phase, in nano-AIU. + [JsonPropertyName("totalNanoAiu")] + public required double TotalNanoAiu { get; set; } +} - /// URLs that may be accessed by the command. - [JsonPropertyName("possibleUrls")] - public required PermissionRequestShellPossibleUrl[] PossibleUrls { get; set; } +/// A source that backs one or more cited spans in the assistant's response. +/// Nested data type for CitationSource. +[Experimental(Diagnostics.Experimental)] +public sealed partial class CitationSource +{ + /// Stable, turn-scoped identifier for this source, referenced by CitationReference.sourceId. + [JsonPropertyName("id")] + public required string Id { get; set; } - /// True when the model has requested to run this command outside the sandbox (it set requestSandboxBypass: true and the host opted in via sandbox.allowBypass). This is a request, not a grant: the command runs unsandboxed only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI. + /// File path relative to the agent's workspace root, when the source is a file. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("requestSandboxBypass")] - public bool? RequestSandboxBypass { get; set; } + [JsonPropertyName("path")] + public string? Path { get; set; } - /// Model-provided justification for the sandbox-bypass request. Only meaningful when requestSandboxBypass is true. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("requestSandboxBypassReason")] - public string? RequestSandboxBypassReason { get; set; } + /// The system that produced this citation. + [JsonPropertyName("provider")] + public required CitationProvider Provider { get; set; } - /// Tool call ID that triggered this permission request. + /// Human-readable title of the source. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } + [JsonPropertyName("title")] + public string? Title { get; set; } - /// Optional warning message about risks of running this command. + /// URL of the source, when it is a web resource. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("warning")] - public string? Warning { get; set; } + [JsonPropertyName("url")] + public string? Url { get; set; } } -/// File write permission request. -/// The write variant of . -public sealed partial class PermissionRequestWrite : PermissionRequest +/// A character range within the source's text content. +/// The char variant of . +[Experimental(Diagnostics.Experimental)] +public sealed partial class CitationLocationChar : CitationLocation { /// [JsonIgnore] - public override string Kind => "write"; - - /// Whether the UI can offer session-wide approval for file write operations. - [JsonPropertyName("canOfferSessionApproval")] - public required bool CanOfferSessionApproval { get; set; } - - /// Unified diff showing the proposed changes. - [JsonPropertyName("diff")] - public required string Diff { get; set; } + public override string Type => "char"; - /// Path of the file being written to. - [JsonPropertyName("fileName")] - public required string FileName { get; set; } + /// End character offset within the source text (zero-based, exclusive). + [JsonPropertyName("endIndex")] + public required long EndIndex { get; set; } - /// Human-readable description of the intended file change. - [JsonPropertyName("intention")] - public required string Intention { get; set; } + /// Start character offset within the source text (zero-based, inclusive). + [JsonPropertyName("startIndex")] + public required long StartIndex { 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")] - public string? NewFileContents { get; set; } - - /// 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. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("requestSandboxBypass")] - public bool? RequestSandboxBypass { get; set; } +/// A page range within a paginated source document. +/// The page variant of . +[Experimental(Diagnostics.Experimental)] +public sealed partial class CitationLocationPage : CitationLocation +{ + /// + [JsonIgnore] + public override string Type => "page"; - /// Justification for the sandbox-bypass request. Only meaningful when requestSandboxBypass is true. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("requestSandboxBypassReason")] - public string? RequestSandboxBypassReason { get; set; } + /// Last page number of the cited range (inclusive). + [JsonPropertyName("endPage")] + public required long EndPage { get; set; } - /// Tool call ID that triggered this permission request. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } + /// First page number of the cited range. + [JsonPropertyName("startPage")] + public required long StartPage { get; set; } } -/// File or directory read permission request. -/// The read variant of . -public sealed partial class PermissionRequestRead : PermissionRequest +/// A content-block range within a structured source document. +/// The block variant of . +[Experimental(Diagnostics.Experimental)] +public sealed partial class CitationLocationBlock : CitationLocation { /// [JsonIgnore] - public override string Kind => "read"; + public override string Type => "block"; - /// Human-readable description of why the file is being read. - [JsonPropertyName("intention")] - public required string Intention { get; set; } + /// Index of the last content block of the cited range (zero-based, exclusive). + [JsonPropertyName("endBlock")] + public required long EndBlock { get; set; } - /// - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("managedApprovalRequired")] - public override bool? ManagedApprovalRequired - { - get => base.ManagedApprovalRequired; - set => base.ManagedApprovalRequired = value; - } + /// Index of the first content block of the cited range (zero-based, inclusive). + [JsonPropertyName("startBlock")] + public required long StartBlock { get; set; } +} - /// Path of the file or directory being read. - [JsonPropertyName("path")] - public required string Path { get; set; } +/// Location within a cited source (character, page, or content-block range) that supports a span. +/// Polymorphic base type discriminated by type. +[Experimental(Diagnostics.Experimental)] +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "type", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(CitationLocationChar), "char")] +[JsonDerivedType(typeof(CitationLocationPage), "page")] +[JsonDerivedType(typeof(CitationLocationBlock), "block")] +public partial class CitationLocation +{ + /// The type discriminator. + [JsonPropertyName("type")] + public virtual string Type { get; set; } = string.Empty; +} - /// 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. + +/// A single citation occurrence linking a span of generated text to a supporting source. +/// Nested data type for CitationReference. +[Experimental(Diagnostics.Experimental)] +public sealed partial class CitationReference +{ + /// The exact text from the source that supports the cited span, when provided by the model. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("requestSandboxBypass")] - public bool? RequestSandboxBypass { get; set; } + [JsonPropertyName("citedText")] + public string? CitedText { get; set; } - /// Model-provided justification for the sandbox-bypass request. Only meaningful when requestSandboxBypass is true. + /// Location within the source that supports the cited span, when the provider reports one. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("requestSandboxBypassReason")] - public string? RequestSandboxBypassReason { get; set; } + [JsonPropertyName("location")] + public CitationLocation? Location { get; set; } - /// Tool call ID that triggered this permission request. + /// Provider-native citation correlation data (e.g. Anthropic search_result_index / document_index), passed through opaquely for debugging and forward compatibility. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } + [JsonPropertyName("providerMetadata")] + public JsonElement? ProviderMetadata { get; set; } + + /// Identifier of the CitationSource this reference points to (CitationSource.id). + [JsonPropertyName("sourceId")] + public required string SourceId { get; set; } } -/// MCP tool invocation permission request. -/// The mcp variant of . -public sealed partial class PermissionRequestMcp : PermissionRequest +/// A contiguous span of generated assistant text and the source references that support it. +/// Nested data type for CitationSpan. +[Experimental(Diagnostics.Experimental)] +public sealed partial class CitationSpan { - /// - [JsonIgnore] - public override string Kind => "mcp"; - - /// Arguments to pass to the MCP tool. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("args")] - public JsonElement? Args { get; set; } - - /// Whether this MCP tool is read-only (no side effects). - [JsonPropertyName("readOnly")] - public required bool ReadOnly { get; set; } + /// End offset of the cited span within the final assistant message content (UTF-16 code units, zero-based, exclusive). + [JsonPropertyName("endIndex")] + public required long EndIndex { get; set; } - /// Name of the MCP server providing the tool. - [JsonPropertyName("serverName")] - public required string ServerName { get; set; } + /// The sources that support this span of generated text. + [JsonPropertyName("references")] + public required CitationReference[] References { get; set; } - /// Tool call ID that triggered this permission request. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } + /// Start offset of the cited span within the final assistant message content (UTF-16 code units, zero-based, inclusive). + [JsonPropertyName("startIndex")] + public required long StartIndex { get; set; } +} - /// Internal name of the MCP tool. - [JsonPropertyName("toolName")] - public required string ToolName { get; set; } +/// Provider-agnostic citations linking spans of the assistant's response to their supporting sources. +/// Nested data type for Citations. +[Experimental(Diagnostics.Experimental)] +public sealed partial class Citations +{ + /// Deduplicated set of sources referenced by the citation spans. + [JsonPropertyName("sources")] + public required CitationSource[] Sources { get; set; } - /// Human-readable title of the MCP tool. - [JsonPropertyName("toolTitle")] - public required string ToolTitle { get; set; } + /// Spans of generated text annotated with the sources that support them. + [JsonPropertyName("spans")] + public required CitationSpan[] Spans { get; set; } } -/// URL access permission request. -/// The url variant of . -public sealed partial class PermissionRequestUrl : PermissionRequest +/// Experimental attribution linking an ordinary event to the HydraFusion turn, phase, and concrete source that produced it. +/// Nested data type for FusionAttribution. +[Experimental(Diagnostics.Experimental)] +public sealed partial class FusionAttribution { - /// - [JsonIgnore] - public override string Kind => "url"; + /// Idempotency identifier for the authoritative commit, when the event belongs to the selected output. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("commitId")] + public string? CommitId { get; set; } - /// Human-readable description of why the URL is being accessed. - [JsonPropertyName("intention")] - public required string Intention { get; set; } + /// Conversation scope in which the concrete phase executed. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("conversationScope")] + public string? ConversationScope { get; set; } - /// + /// Stable identifier for the HydraFusion turn that produced the event. + [JsonPropertyName("fusionId")] + public required string FusionId { get; set; } + + /// HydraFusion orchestration pattern selected for the turn. + [JsonPropertyName("pattern")] + public required string Pattern { get; set; } + + /// Identifier of the concrete phase that produced the event. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("managedApprovalRequired")] - public override bool? ManagedApprovalRequired - { - get => base.ManagedApprovalRequired; - set => base.ManagedApprovalRequired = value; - } + [JsonPropertyName("phaseId")] + public string? PhaseId { get; set; } - /// Immediately preceding URL when this request is for a redirect target. + /// Kind of concrete phase that produced the event. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("redirectedFrom")] - public string? RedirectedFrom { get; set; } + [JsonPropertyName("phaseKind")] + public string? PhaseKind { 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. + /// HydraFusion routing policy used for the turn. + [JsonPropertyName("policy")] + public required string Policy { get; set; } + + /// Semantic role assigned to the concrete phase. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("requestSandboxBypass")] - public bool? RequestSandboxBypass { get; set; } + [JsonPropertyName("role")] + public string? Role { get; set; } - /// Model-provided justification for the sandbox-bypass request. Only meaningful when requestSandboxBypass is true. + /// Concrete model that produced the attributed event. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("requestSandboxBypassReason")] - public string? RequestSandboxBypassReason { get; set; } + [JsonPropertyName("sourceModel")] + public string? SourceModel { get; set; } - /// Tool call ID that triggered this permission request. + /// Phase whose output supplied the authoritative content, when different from the executing phase. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } + [JsonPropertyName("sourcePhaseId")] + public string? SourcePhaseId { get; set; } - /// URL to be fetched. - [JsonPropertyName("url")] - public required string Url { get; set; } + /// Synthetic HydraFusion model selected for the session. + [JsonPropertyName("syntheticModel")] + public required string SyntheticModel { get; set; } } -/// Memory operation permission request. -/// The memory variant of . -public sealed partial class PermissionRequestMemory : PermissionRequest +/// Neutral provider-tagged reasoning content blocks preserved verbatim for round-tripping. +/// Nested data type for AssistantMessageReasoningBlocks. +[Experimental(Diagnostics.Experimental)] +public sealed partial class AssistantMessageReasoningBlocks { - /// - [JsonIgnore] - public override string Kind => "memory"; - - /// Whether this is a store or vote memory operation. + /// Provider-native reasoning items or content blocks preserved verbatim, in order. A single response can carry several, and provider signatures or identifiers may depend on their exact content and ordering. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("action")] - public PermissionRequestMemoryAction? Action { get; set; } + [JsonPropertyName("blocks")] + public JsonElement[]? Blocks { get; set; } - /// Source references for the stored fact (store only). - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("citations")] - public string? Citations { get; set; } + /// Model provider that produced these reasoning blocks. + [JsonPropertyName("provider")] + public required string Provider { get; set; } +} - /// Vote direction (vote only). +/// Neutral provider-tagged server-side tool-use payload (tool search, advisor) for verbatim round-tripping. +/// Nested data type for AssistantMessageServerTools. +[Experimental(Diagnostics.Experimental)] +public sealed partial class AssistantMessageServerTools +{ + /// Advisor model identifier associated with the server-tool payload. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("direction")] - public PermissionRequestMemoryDirection? Direction { get; set; } - - /// The fact being stored or voted on. - [JsonPropertyName("fact")] - public required string Fact { get; set; } + [JsonPropertyName("advisorModel")] + public string? AdvisorModel { get; set; } - /// Reason for the vote (vote only). + /// Provider function-call namespaces keyed by function-call identifier. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("reason")] - public string? Reason { get; set; } + [JsonPropertyName("functionCallNamespaces")] + public IDictionary? FunctionCallNamespaces { get; set; } - /// Topic or subject of the memory (store only). + /// Provider-native server-tool call and output items preserved verbatim for replay. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("subject")] - public string? Subject { get; set; } + [JsonPropertyName("items")] + public JsonElement[]? Items { get; set; } - /// Tool call ID that triggered this permission request. + /// Model provider that produced this server-tool payload. + [JsonPropertyName("provider")] + public required string Provider { get; set; } + + /// Raw provider content blocks retained for verbatim round-tripping. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } + [JsonPropertyName("rawContentBlocks")] + public JsonElement[]? RawContentBlocks { get; set; } } -/// Custom tool invocation permission request. -/// The custom-tool variant of . -public sealed partial class PermissionRequestCustomTool : PermissionRequest +/// Hosted program that requested this client tool call. +/// Nested data type for AssistantMessageToolRequestCaller. +public sealed partial class AssistantMessageToolRequestCaller { - /// - [JsonIgnore] - public override string Kind => "custom-tool"; - - /// Arguments to pass to the custom tool. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("args")] - public JsonElement? Args { get; set; } - - /// Tool call ID that triggered this permission request. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } - - /// Description of what the custom tool does. - [JsonPropertyName("toolDescription")] - public required string ToolDescription { get; set; } + /// Provider-assigned identifier for the hosted caller. + [JsonPropertyName("callerId")] + public required string CallerId { get; set; } - /// Name of the custom tool. - [JsonPropertyName("toolName")] - public required string ToolName { get; set; } + /// Kind of hosted caller that requested the client tool call. + [JsonPropertyName("type")] + public required AssistantMessageToolRequestCallerType Type { get; set; } } -/// Hook confirmation permission request. -/// The hook variant of . -public sealed partial class PermissionRequestHook : PermissionRequest +/// A tool invocation request from the assistant. +/// Nested data type for AssistantMessageToolRequest. +public sealed partial class AssistantMessageToolRequest { - /// - [JsonIgnore] - public override string Kind => "hook"; + /// Arguments to pass to the tool, format depends on the tool. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("arguments")] + public JsonElement? Arguments { get; set; } - /// Optional message from the hook explaining why confirmation is needed. + /// Hosted program that requested this client tool call. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("hookMessage")] - public string? HookMessage { get; set; } + [JsonPropertyName("caller")] + public AssistantMessageToolRequestCaller? Caller { get; set; } - /// Arguments of the tool call being gated. + /// Resolved intention summary describing what this specific call does. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolArgs")] - public JsonElement? ToolArgs { get; set; } + [JsonPropertyName("intentionSummary")] + public string? IntentionSummary { get; set; } - /// Tool call ID that triggered this permission request. + /// Name of the MCP server hosting this tool, when the tool is an MCP tool. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("mcpServerName")] + public string? McpServerName { get; set; } + + /// Original tool name on the MCP server, when the tool is an MCP tool. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("mcpToolName")] + public string? McpToolName { get; set; } + + /// Name of the tool being invoked. + [JsonPropertyName("name")] + public required string Name { get; set; } + + /// Unique identifier for this tool call. [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } + public required string ToolCallId { get; set; } - /// Name of the tool the hook is gating. - [JsonPropertyName("toolName")] - public required string ToolName { get; set; } + /// Human-readable display title for the tool. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolTitle")] + public string? ToolTitle { get; set; } + + /// Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("type")] + public AssistantMessageToolRequestType? Type { get; set; } } -/// Extension management permission request. -/// The extension-management variant of . -public sealed partial class PermissionRequestExtensionManagement : PermissionRequest +/// Token usage detail for a single billing category. +/// Nested data type for AssistantUsageCopilotUsageTokenDetail. +public sealed partial class AssistantUsageCopilotUsageTokenDetail { - /// - [JsonIgnore] - public override string Kind => "extension-management"; + /// Number of tokens in this billing batch. + [JsonPropertyName("batchSize")] + public required long BatchSize { get; set; } - /// Name of the extension being managed. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("extensionName")] - public string? ExtensionName { get; set; } + /// Cost per batch of tokens. + [JsonPropertyName("costPerBatch")] + public required long CostPerBatch { get; set; } - /// The extension management operation (scaffold, reload). - [JsonPropertyName("operation")] - public required string Operation { get; set; } + /// Total token count for this entry. + [JsonPropertyName("tokenCount")] + public required long TokenCount { get; set; } - /// Tool call ID that triggered this permission request. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } + /// Token category (e.g., "input", "output"). + [JsonPropertyName("tokenType")] + public required string TokenType { get; set; } } -/// A declared phase shown in a factory permission prompt. -/// Nested data type for FactoryPermissionPhase. -public sealed partial class FactoryPermissionPhase +/// Per-request cost and usage data from the CAPI copilot_usage response field. +/// Nested data type for AssistantUsageCopilotUsage. +public sealed partial class AssistantUsageCopilotUsage { - /// Optional phase detail. + /// Itemized token usage breakdown. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("detail")] - public string? Detail { get; set; } + [JsonInclude] + [JsonPropertyName("tokenDetails")] + internal AssistantUsageCopilotUsageTokenDetail[]? TokenDetails { get; set; } - /// Phase title. - [JsonPropertyName("title")] - public required string Title { get; set; } + /// Total cost in nano-AI units for this request. + [JsonPropertyName("totalNanoAiu")] + public required double TotalNanoAiu { get; set; } } -/// Factory run or authoring permission request. -/// The factory variant of . -public sealed partial class PermissionRequestFactory : PermissionRequest +/// Internal per-quota snapshot for assistant usage, including entitlement, consumed requests, overage, reset date, and remaining quota. +/// Nested data type for AssistantUsageQuotaSnapshot. +internal sealed partial class AssistantUsageQuotaSnapshot { - /// - [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; } + /// Total requests allowed by the entitlement. + [JsonInclude] + [JsonPropertyName("entitlementRequests")] + internal required long EntitlementRequests { get; set; } - /// Gets or sets the declaredMaxConcurrentSubagents value. + /// Whether the user currently has quota available for use. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("declaredMaxConcurrentSubagents")] - public long? DeclaredMaxConcurrentSubagents { get; set; } + [JsonInclude] + [JsonPropertyName("hasQuota")] + internal bool? HasQuota { get; set; } - /// Gets or sets the declaredMaxTotalSubagents value. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("declaredMaxTotalSubagents")] - public long? DeclaredMaxTotalSubagents { get; set; } + /// Whether the user has an unlimited usage entitlement. + [JsonInclude] + [JsonPropertyName("isUnlimitedEntitlement")] + internal required bool IsUnlimitedEntitlement { get; set; } - /// Gets or sets the declaredTimeoutSeconds value. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("declaredTimeoutSeconds")] - public double? DeclaredTimeoutSeconds { get; set; } + /// Number of additional usage requests made this period. + [JsonInclude] + [JsonPropertyName("overage")] + internal required double Overage { get; set; } - /// Factory description. - [JsonPropertyName("description")] - public required string Description { get; set; } + /// Whether additional usage is allowed when quota is exhausted. + [JsonInclude] + [JsonPropertyName("overageAllowedWithExhaustedQuota")] + internal required bool OverageAllowedWithExhaustedQuota { get; set; } - /// Effective AI-credit limit; omitted means unlimited. + /// Pay-as-you-go additional-usage budget cap in AI credits (1 credit = $0.01); present only when CAPI emits a finite value. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("maxAiCredits")] - public double? MaxAiCredits { get; set; } + [JsonInclude] + [JsonPropertyName("overageEntitlement")] + internal double? OverageEntitlement { get; set; } - /// Effective concurrent-subagent limit; omitted means unlimited. + /// Percentage of quota remaining (0 to 100). + [JsonInclude] + [JsonPropertyName("remainingPercentage")] + internal required double RemainingPercentage { get; set; } + + /// Date when the quota resets. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("maxConcurrentSubagents")] - public long? MaxConcurrentSubagents { get; set; } + [JsonInclude] + [JsonPropertyName("resetDate")] + internal DateTimeOffset? ResetDate { get; set; } - /// Effective total-subagent limit; omitted means unlimited. + /// Whether this snapshot uses token-based billing (AI-credits allocation). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("maxTotalSubagents")] - public long? MaxTotalSubagents { get; set; } + [JsonInclude] + [JsonPropertyName("tokenBasedBilling")] + internal bool? TokenBasedBilling { get; set; } - /// Factory name. - [JsonPropertyName("name")] - public required string Name { get; set; } + /// Whether usage is still permitted after quota exhaustion. + [JsonInclude] + [JsonPropertyName("usageAllowedWithExhaustedQuota")] + internal required bool UsageAllowedWithExhaustedQuota { get; set; } - /// Factory operation, either run or author. - [JsonPropertyName("operation")] - public required FactoryPermissionOperation Operation { get; set; } + /// Number of requests already consumed. + [JsonInclude] + [JsonPropertyName("usedRequests")] + internal required long UsedRequests { get; set; } +} - /// Declared factory phases. - [JsonPropertyName("phases")] - public required FactoryPermissionPhase[] Phases { get; set; } +/// Content-free structural summary of the failing request for diagnosing malformed 4xx calls. +/// Nested data type for ModelCallFailureRequestFingerprint. +public sealed partial class ModelCallFailureRequestFingerprint +{ + /// Total number of image content parts. + [JsonPropertyName("imagePartCount")] + public required long ImagePartCount { get; set; } - /// Effective active-time limit in seconds; omitted means unlimited. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("timeoutSeconds")] - public double? TimeoutSeconds { get; set; } + /// Image parts whose media type cannot be determined (rejected by strict providers). + [JsonPropertyName("imagePartsMissingMediaType")] + public required long ImagePartsMissingMediaType { get; set; } - /// Tool call ID that triggered this permission request. + /// Role of the final message in the request. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } -} + [JsonPropertyName("lastMessageRole")] + public string? LastMessageRole { get; set; } -/// Extension permission access request. -/// The extension-permission-access variant of . -public sealed partial class PermissionRequestExtensionPermissionAccess : PermissionRequest -{ - /// - [JsonIgnore] - public override string Kind => "extension-permission-access"; + /// Total number of messages in the request. + [JsonPropertyName("messageCount")] + public required long MessageCount { get; set; } - /// Capabilities the extension is requesting. - [JsonPropertyName("capabilities")] - public required string[] Capabilities { get; set; } + /// Tool calls whose name is missing or empty (rejected by strict providers). + [JsonPropertyName("namelessToolCallCount")] + public required long NamelessToolCallCount { get; set; } - /// Name of the extension requesting permission access. - [JsonPropertyName("extensionName")] - public required string ExtensionName { get; set; } + /// Total number of tool calls across assistant messages. + [JsonPropertyName("toolCallCount")] + public required long ToolCallCount { get; set; } - /// Tool call ID that triggered this permission request. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } + /// Number of "tool" result messages in the request. + [JsonPropertyName("toolResultMessageCount")] + public required long ToolResultMessageCount { get; set; } } -/// Details of the permission being requested. -/// Polymorphic base type discriminated by kind. -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(PermissionRequestShell), "shell")] -[JsonDerivedType(typeof(PermissionRequestWrite), "write")] -[JsonDerivedType(typeof(PermissionRequestRead), "read")] -[JsonDerivedType(typeof(PermissionRequestMcp), "mcp")] -[JsonDerivedType(typeof(PermissionRequestUrl), "url")] -[JsonDerivedType(typeof(PermissionRequestMemory), "memory")] -[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 +/// 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. +/// Nested data type for ToolExecutionStartShellToolInfo. +public sealed partial class ToolExecutionStartShellToolInfo { - /// 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. + /// 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("managedApprovalRequired")] - public virtual bool? ManagedApprovalRequired { get; set; } -} + [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; } + /// 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. + [JsonPropertyName("possiblePaths")] + public required string[] PossiblePaths { get; set; } +} -/// 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. -/// Nested data type for PermissionAutoApproval. -[Experimental(Diagnostics.Experimental)] -public sealed partial class PermissionAutoApproval +/// MCP Apps tool `_meta.ui` resource URI and visibility captured on `tool.execution_start`. +/// Nested data type for ToolExecutionStartToolDescriptionMetaUI. +public sealed partial class ToolExecutionStartToolDescriptionMetaUI { - /// Classified cause of an `error` recommendation. Absent for every other recommendation. + /// URI of the UI resource. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("failureReason")] - public AutoApprovalJudgeFailureReason? FailureReason { get; set; } + [JsonPropertyName("resourceUri")] + public string? ResourceUri { 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. + /// Who can access this tool. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("model")] - public string? Model { get; set; } + [JsonPropertyName("visibility")] + public ToolExecutionStartToolDescriptionMetaUIVisibility[]? Visibility { get; set; } +} - /// Human-readable reason for the judge's recommendation, when available. +/// MCP Apps metadata for UI resource association. +/// Nested data type for ToolExecutionStartToolDescriptionMeta. +public sealed partial class ToolExecutionStartToolDescriptionMeta +{ + /// MCP Apps tool `_meta.ui` resource URI and visibility captured on `tool.execution_start`. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("reason")] - public string? Reason { get; set; } - - /// The auto-approval safety judge's outcome for this request. - [JsonPropertyName("recommendation")] - public required AutoApprovalRecommendation Recommendation { get; set; } + [JsonPropertyName("ui")] + public ToolExecutionStartToolDescriptionMetaUI? Ui { get; set; } } -/// Shell command permission prompt. -/// The commands variant of . -public sealed partial class PermissionPromptRequestCommands : PermissionPromptRequest +/// Tool definition metadata, present for MCP tools with MCP Apps support. +/// Nested data type for ToolExecutionStartToolDescription. +public sealed partial class ToolExecutionStartToolDescription { - /// - [JsonIgnore] - public override string Kind => "commands"; + /// MCP Apps metadata for UI resource association. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("_meta")] + public ToolExecutionStartToolDescriptionMeta? Meta { get; set; } - /// Auto-approval judge information for this request; present only when auto mode is enabled. - [Experimental(Diagnostics.Experimental)] + /// Tool description. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("autoApproval")] - public PermissionAutoApproval? AutoApproval { get; set; } + [JsonPropertyName("description")] + public string? Description { get; set; } - /// Whether the UI can offer session-wide approval for this command pattern. - [JsonPropertyName("canOfferSessionApproval")] - public required bool CanOfferSessionApproval { get; set; } + /// Tool name. + [JsonPropertyName("name")] + public required string Name { get; set; } +} - /// Command identifiers covered by this approval prompt. - [JsonPropertyName("commandIdentifiers")] - public required string[] CommandIdentifiers { get; set; } +/// Error details when the tool execution failed. +/// Nested data type for ToolExecutionCompleteError. +public sealed partial class ToolExecutionCompleteError +{ + /// Machine-readable error code. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("code")] + public string? Code { get; set; } - /// The complete shell command text to be executed. - [JsonPropertyName("fullCommandText")] - public required string FullCommandText { get; set; } + /// Human-readable error message. + [JsonPropertyName("message")] + public required string Message { get; set; } +} - /// Human-readable description of what the command intends to do. - [JsonPropertyName("intention")] - public required string Intention { get; set; } +/// Binary result returned by a tool for the model. +/// Nested data type for PersistedBinaryImage. +public sealed partial class PersistedBinaryImage +{ + /// Base64-encoded binary data. + [Base64String] + [JsonPropertyName("data")] + public required string Data { get; set; } - /// Whether managed policy requires a human response and forbids host auto-approval. + /// Human-readable description of the binary data. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("managedApprovalRequired")] - public bool? ManagedApprovalRequired { get; set; } + [JsonPropertyName("description")] + public string? Description { get; set; } - /// Tool call ID that triggered this permission request. + /// Optional metadata from the producing tool. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } + [JsonPropertyName("metadata")] + public IDictionary? Metadata { get; set; } - /// Optional warning message about risks of running this command. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("warning")] - public string? Warning { get; set; } + /// MIME type of the binary data. + [JsonPropertyName("mimeType")] + public required string MimeType { get; set; } + + /// Binary result type discriminator. Use "image" for images and "resource" for other binary data. + [JsonPropertyName("type")] + public required PersistedBinaryImageType Type { get; set; } } -/// File write permission prompt. -/// The write variant of . -public sealed partial class PermissionPromptRequestWrite : PermissionPromptRequest +/// A binary result whose data was omitted from persistence due to the inline size limit. +/// Nested data type for OmittedBinaryResult. +[Experimental(Diagnostics.Experimental)] +public sealed partial class OmittedBinaryResult { - /// - [JsonIgnore] - public override string Kind => "write"; + /// Decoded byte length of the omitted binary data. + [JsonPropertyName("byteLength")] + public required long ByteLength { get; set; } - /// Auto-approval judge information for this request; present only when auto mode is enabled. - [Experimental(Diagnostics.Experimental)] + /// Human-readable description of the binary data. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("autoApproval")] - public PermissionAutoApproval? AutoApproval { get; set; } + [JsonPropertyName("description")] + public string? Description { get; set; } - /// Whether the UI can offer session-wide approval for file write operations. - [JsonPropertyName("canOfferSessionApproval")] - public required bool CanOfferSessionApproval { get; set; } + /// Optional metadata from the producing tool. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("metadata")] + public IDictionary? Metadata { get; set; } - /// Unified diff showing the proposed changes. - [JsonPropertyName("diff")] - public required string Diff { get; set; } + /// MIME type of the omitted binary data. + [JsonPropertyName("mimeType")] + public required string MimeType { get; set; } - /// Path of the file being written to. - [JsonPropertyName("fileName")] - public required string FileName { get; set; } + /// Why the binary data is absent: it exceeded the inline size limit, or its asset was unavailable. + [JsonPropertyName("omittedReason")] + public required OmittedBinaryOmittedReason OmittedReason { get; set; } - /// Human-readable description of the intended file change. - [JsonPropertyName("intention")] - public required string Intention { get; set; } + /// Binary result type discriminator. Use "image" for images and "resource" for other binary data. + [JsonPropertyName("type")] + public required OmittedBinaryType Type { 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; } +/// A reference to binary data persisted once on a session.binary_asset event and shared by id. +/// Nested data type for BinaryAssetReference. +[Experimental(Diagnostics.Experimental)] +public sealed partial class BinaryAssetReference +{ + /// Content-addressed id of the session.binary_asset event that holds this binary's bytes (e.g. "sha256:..."). + [JsonPropertyName("assetId")] + public required string AssetId { get; set; } - /// Complete new file contents for newly created files. + /// Decoded byte length of the referenced binary data. + [JsonPropertyName("byteLength")] + public required long ByteLength { get; set; } + + /// Human-readable description of the binary data. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("newFileContents")] - public string? NewFileContents { get; set; } + [JsonPropertyName("description")] + public string? Description { get; set; } - /// Tool call ID that triggered this permission request. + /// Optional metadata from the producing tool. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } + [JsonPropertyName("metadata")] + public IDictionary? Metadata { get; set; } + + /// MIME type of the referenced binary data. + [JsonPropertyName("mimeType")] + public required string MimeType { get; set; } + + /// Binary result type discriminator. Use "image" for images and "resource" for other binary data. + [JsonPropertyName("type")] + public required BinaryAssetReferenceType Type { get; set; } } -/// File read permission prompt. -/// The read variant of . -public sealed partial class PermissionPromptRequestRead : PermissionPromptRequest +/// A model-facing binary result as persisted: full inline data, a size-omitted marker, or a deduplicated asset reference. +/// JSON union data type for PersistedBinaryResult. +[JsonConverter(typeof(Converter))] +public sealed partial class PersistedBinaryResult { - /// - [JsonIgnore] - public override string Kind => "read"; - - /// 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; } + /// Gets the value when this instance contains . + public PersistedBinaryImage? PersistedBinaryImage { get; } - /// Human-readable description of why the file is being read. - [JsonPropertyName("intention")] - public required string Intention { get; set; } + /// Gets the value when this instance contains . + public OmittedBinaryResult? OmittedBinaryResult { get; } - /// Whether managed policy requires a human response and forbids host auto-approval. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("managedApprovalRequired")] - public bool? ManagedApprovalRequired { get; set; } + /// Gets the value when this instance contains . + public BinaryAssetReference? BinaryAssetReference { get; } - /// Path of the file or directory being read. - [JsonPropertyName("path")] - public required string Path { get; set; } + /// Initializes a new instance of the class from . + public PersistedBinaryResult(PersistedBinaryImage value) + { + ArgumentNullException.ThrowIfNull(value); + PersistedBinaryImage = value; + } - /// Tool call ID that triggered this permission request. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } -} + /// Converts to . + public static implicit operator PersistedBinaryResult(PersistedBinaryImage value) => new(value); -/// MCP tool invocation permission prompt. -/// The mcp variant of . -public sealed partial class PermissionPromptRequestMcp : PermissionPromptRequest -{ - /// - [JsonIgnore] - public override string Kind => "mcp"; + /// Initializes a new instance of the class from . + public PersistedBinaryResult(OmittedBinaryResult value) + { + ArgumentNullException.ThrowIfNull(value); + OmittedBinaryResult = value; + } - /// Arguments to pass to the MCP tool. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("args")] - public JsonElement? Args { get; set; } + /// Converts to . + public static implicit operator PersistedBinaryResult(OmittedBinaryResult value) => new(value); - /// 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; } + /// Initializes a new instance of the class from . + public PersistedBinaryResult(BinaryAssetReference value) + { + ArgumentNullException.ThrowIfNull(value); + BinaryAssetReference = value; + } - /// Name of the MCP server providing the tool. - [JsonPropertyName("serverName")] - public required string ServerName { get; set; } + /// Converts to . + public static implicit operator PersistedBinaryResult(BinaryAssetReference value) => new(value); - /// Tool call ID that triggered this permission request. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override PersistedBinaryResult Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + { + throw new JsonException("Expected JSON object for PersistedBinaryResult."); + } - /// Internal name of the MCP tool. - [JsonPropertyName("toolName")] - public required string ToolName { get; set; } + using var document = JsonDocument.ParseValue(ref reader); + var element = document.RootElement; + if (element.ValueKind == JsonValueKind.Object && element.TryGetProperty("data", out _) && !element.TryGetProperty("assetId", out _) && !element.TryGetProperty("byteLength", out _) && !element.TryGetProperty("omittedReason", out _)) + { + var persistedBinaryImage = JsonSerializer.Deserialize(element, SessionEventsJsonContext.Default.PersistedBinaryImage); + return persistedBinaryImage is null ? throw new JsonException("Expected PersistedBinaryImage value.") : new PersistedBinaryResult(persistedBinaryImage); + } + if (element.ValueKind == JsonValueKind.Object && element.TryGetProperty("omittedReason", out _) && !element.TryGetProperty("assetId", out _) && !element.TryGetProperty("data", out _)) + { + var omittedBinaryResult = JsonSerializer.Deserialize(element, SessionEventsJsonContext.Default.OmittedBinaryResult); + return omittedBinaryResult is null ? throw new JsonException("Expected OmittedBinaryResult value.") : new PersistedBinaryResult(omittedBinaryResult); + } + if (element.ValueKind == JsonValueKind.Object && element.TryGetProperty("assetId", out _) && !element.TryGetProperty("data", out _) && !element.TryGetProperty("omittedReason", out _)) + { + var binaryAssetReference = JsonSerializer.Deserialize(element, SessionEventsJsonContext.Default.BinaryAssetReference); + return binaryAssetReference is null ? throw new JsonException("Expected BinaryAssetReference value.") : new PersistedBinaryResult(binaryAssetReference); + } - /// Human-readable title of the MCP tool. - [JsonPropertyName("toolTitle")] - public required string ToolTitle { get; set; } -} + throw new JsonException("JSON value did not match any PersistedBinaryResult variant."); + } -/// URL access permission prompt. -/// The url variant of . -public sealed partial class PermissionPromptRequestUrl : PermissionPromptRequest -{ - /// - [JsonIgnore] - public override string Kind => "url"; + /// + public override void Write(Utf8JsonWriter writer, PersistedBinaryResult value, JsonSerializerOptions options) + { + if (value.PersistedBinaryImage is { } persistedBinaryImage) + { + JsonSerializer.Serialize(writer, persistedBinaryImage, SessionEventsJsonContext.Default.PersistedBinaryImage); + return; + } + if (value.OmittedBinaryResult is { } omittedBinaryResult) + { + JsonSerializer.Serialize(writer, omittedBinaryResult, SessionEventsJsonContext.Default.OmittedBinaryResult); + return; + } + if (value.BinaryAssetReference is { } binaryAssetReference) + { + JsonSerializer.Serialize(writer, binaryAssetReference, SessionEventsJsonContext.Default.BinaryAssetReference); + return; + } - /// 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; } + throw new JsonException("No PersistedBinaryResult variant value is set."); + } + } +} - /// Human-readable description of why the URL is being accessed. - [JsonPropertyName("intention")] - public required string Intention { get; set; } +/// A source supplied by a tool that should be made available to the model as citable content. +/// Nested data type for CitableSource. +[Experimental(Diagnostics.Experimental)] +public sealed partial class CitableSource +{ + /// The source text made available to the model as citable content. + [JsonPropertyName("content")] + public required string Content { 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; } + /// Stable identifier for this source within the tool result. Used for deduplication and may be used by future provider integrations to correlate response citations back to the originating source. + [JsonPropertyName("id")] + public required string Id { get; set; } - /// Immediately preceding URL when this prompt is for a redirect target. + /// File path relative to the agent's workspace root, when the source is a file. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("redirectedFrom")] - public string? RedirectedFrom { get; set; } + [JsonPropertyName("path")] + public string? Path { 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. + /// Human-readable title of the source. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("requestSandboxBypass")] - public bool? RequestSandboxBypass { get; set; } + [JsonPropertyName("title")] + public string? Title { get; set; } - /// Model-provided justification for the sandbox-bypass request. Only meaningful when requestSandboxBypass is true. + /// URL of the source, when it is a web resource. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("requestSandboxBypassReason")] - public string? RequestSandboxBypassReason { get; set; } + [JsonPropertyName("url")] + public string? Url { get; set; } +} - /// Tool call ID that triggered this permission request. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } +/// Plain text content block. +/// The text variant of . +public sealed partial class ToolExecutionCompleteContentText : ToolExecutionCompleteContent +{ + /// + [JsonIgnore] + public override string Type => "text"; - /// URL to be fetched. - [JsonPropertyName("url")] - public required string Url { get; set; } + /// The text content. + [JsonPropertyName("text")] + public required string Text { get; set; } } -/// Memory operation permission prompt. -/// The memory variant of . -public sealed partial class PermissionPromptRequestMemory : PermissionPromptRequest +/// Deprecated for shell command exit metadata. Use ToolExecutionCompleteContentShellExit instead. +/// The terminal variant of . +[EditorBrowsable(EditorBrowsableState.Never)] +#if NET5_0_OR_GREATER +[Obsolete("This member is deprecated and will be removed in a future version.", DiagnosticId = "GHCP001")] +#endif +public sealed partial class ToolExecutionCompleteContentTerminal : ToolExecutionCompleteContent { /// [JsonIgnore] - public override string Kind => "memory"; + public override string Type => "terminal"; - /// Whether this is a store or vote memory operation. + /// Working directory where the command was executed. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("action")] - public PermissionRequestMemoryAction? Action { get; set; } + [JsonPropertyName("cwd")] + public string? Cwd { get; set; } - /// Auto-approval judge information for this request; present only when auto mode is enabled. - [Experimental(Diagnostics.Experimental)] + /// Process exit code, if the command has completed. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("autoApproval")] - public PermissionAutoApproval? AutoApproval { get; set; } + [JsonPropertyName("exitCode")] + public long? ExitCode { get; set; } - /// Source references for the stored fact (store only). - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("citations")] - public string? Citations { get; set; } + /// Terminal/shell output text. + [JsonPropertyName("text")] + public required string Text { get; set; } +} - /// Vote direction (vote only). +/// Shell command exit metadata with optional output preview. +/// The shell_exit variant of . +public sealed partial class ToolExecutionCompleteContentShellExit : ToolExecutionCompleteContent +{ + /// + [JsonIgnore] + public override string Type => "shell_exit"; + + /// Working directory where the shell command was executed. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("direction")] - public PermissionRequestMemoryDirection? Direction { get; set; } + [JsonPropertyName("cwd")] + public string? Cwd { get; set; } - /// The fact being stored or voted on. - [JsonPropertyName("fact")] - public required string Fact { get; set; } + /// Exit code from the completed shell command. + [JsonPropertyName("exitCode")] + public required long ExitCode { get; set; } - /// Reason for the vote (vote only). + /// Path reported in the shell session's filesystem namespace when shell output exceeded the configured large-output threshold. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("reason")] - public string? Reason { get; set; } + [JsonPropertyName("outputFilePath")] + public string? OutputFilePath { get; set; } - /// Topic or subject of the memory (store only). + /// Output associated with this shell command, if available. May be partial, truncated, or a preview; not guaranteed to be full output. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("subject")] - public string? Subject { get; set; } + [JsonPropertyName("outputPreview")] + public string? OutputPreview { get; set; } - /// Tool call ID that triggered this permission request. + /// Whether outputPreview is known to be incomplete or truncated. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } + [JsonPropertyName("outputTruncated")] + public bool? OutputTruncated { get; set; } + + /// Shell id, as assigned by Copilot runtime. + [JsonPropertyName("shellId")] + public required string ShellId { get; set; } } -/// Custom tool invocation permission prompt. -/// The custom-tool variant of . -public sealed partial class PermissionPromptRequestCustomTool : PermissionPromptRequest +/// Image content block with base64-encoded data. +/// The image variant of . +public sealed partial class ToolExecutionCompleteContentImage : ToolExecutionCompleteContent { /// [JsonIgnore] - public override string Kind => "custom-tool"; - - /// Arguments to pass to the custom tool. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("args")] - public JsonElement? Args { get; set; } - - /// 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; } - - /// Tool call ID that triggered this permission request. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } + public override string Type => "image"; - /// Description of what the custom tool does. - [JsonPropertyName("toolDescription")] - public required string ToolDescription { get; set; } + /// Base64-encoded image data. + [Base64String] + [JsonPropertyName("data")] + public required string Data { get; set; } - /// Name of the custom tool. - [JsonPropertyName("toolName")] - public required string ToolName { get; set; } + /// MIME type of the image (e.g., image/png, image/jpeg). + [JsonPropertyName("mimeType")] + public required string MimeType { get; set; } } -/// Path access permission prompt. -/// The path variant of . -public sealed partial class PermissionPromptRequestPath : PermissionPromptRequest +/// Audio content block with base64-encoded data. +/// The audio variant of . +public sealed partial class ToolExecutionCompleteContentAudio : ToolExecutionCompleteContent { /// [JsonIgnore] - public override string Kind => "path"; - - /// Underlying permission kind that needs path approval. - [JsonPropertyName("accessKind")] - public required PermissionPromptRequestPathAccessKind AccessKind { get; set; } - - /// 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; } + public override string Type => "audio"; - /// File paths that require explicit approval. - [JsonPropertyName("paths")] - public required string[] Paths { get; set; } + /// Base64-encoded audio data. + [Base64String] + [JsonPropertyName("data")] + public required string Data { get; set; } - /// Tool call ID that triggered this permission request. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } + /// MIME type of the audio (e.g., audio/wav, audio/mpeg). + [JsonPropertyName("mimeType")] + public required string MimeType { get; set; } } -/// Hook confirmation permission prompt. -/// The hook variant of . -public sealed partial class PermissionPromptRequestHook : PermissionPromptRequest +/// Icon image for a resource. +/// Nested data type for ToolExecutionCompleteContentResourceLinkIcon. +public sealed partial class ToolExecutionCompleteContentResourceLinkIcon { - /// - [JsonIgnore] - public override string Kind => "hook"; - - /// Auto-approval judge information for this request; present only when auto mode is enabled. - [Experimental(Diagnostics.Experimental)] + /// MIME type of the icon image. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("autoApproval")] - public PermissionAutoApproval? AutoApproval { get; set; } + [JsonPropertyName("mimeType")] + public string? MimeType { get; set; } - /// Optional message from the hook explaining why confirmation is needed. + /// Available icon sizes (e.g., ['16x16', '32x32']). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("hookMessage")] - public string? HookMessage { get; set; } + [JsonPropertyName("sizes")] + public string[]? Sizes { get; set; } - /// Arguments of the tool call being gated. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolArgs")] - public JsonElement? ToolArgs { get; set; } + /// URL or path to the icon image. + [JsonPropertyName("src")] + public required string Src { get; set; } - /// Tool call ID that triggered this permission request. + /// Theme variant this icon is intended for. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } - - /// Name of the tool the hook is gating. - [JsonPropertyName("toolName")] - public required string ToolName { get; set; } + [JsonPropertyName("theme")] + public ToolExecutionCompleteContentResourceLinkIconTheme? Theme { get; set; } } -/// Extension management permission prompt. -/// The extension-management variant of . -public sealed partial class PermissionPromptRequestExtensionManagement : PermissionPromptRequest +/// Resource link content block referencing an external resource. +/// The resource_link variant of . +public sealed partial class ToolExecutionCompleteContentResourceLink : ToolExecutionCompleteContent { /// [JsonIgnore] - public override string Kind => "extension-management"; + public override string Type => "resource_link"; - /// Auto-approval judge information for this request; present only when auto mode is enabled. - [Experimental(Diagnostics.Experimental)] + /// Human-readable description of the resource. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("autoApproval")] - public PermissionAutoApproval? AutoApproval { get; set; } + [JsonPropertyName("description")] + public string? Description { get; set; } - /// Name of the extension being managed. + /// Icons associated with this resource. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("extensionName")] - public string? ExtensionName { get; set; } - - /// The extension management operation (scaffold, reload). - [JsonPropertyName("operation")] - public required string Operation { get; set; } + [JsonPropertyName("icons")] + public ToolExecutionCompleteContentResourceLinkIcon[]? Icons { get; set; } - /// Tool call ID that triggered this permission request. + /// MIME type of the resource content. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } -} - -/// Factory run or authoring permission prompt. -/// The factory variant of . -public sealed partial class PermissionPromptRequestFactory : PermissionPromptRequest -{ - /// - [JsonIgnore] - public override string Kind => "factory"; + [JsonPropertyName("mimeType")] + public string? MimeType { get; set; } - /// Canonical key used for scoped factory approvals. - [JsonPropertyName("approvalKey")] - public required string ApprovalKey { get; set; } + /// Resource name identifier. + [JsonPropertyName("name")] + public required string Name { get; set; } - /// Auto-approval judge information for this request; present only when auto mode is enabled. - [Experimental(Diagnostics.Experimental)] + /// Size of the resource in bytes. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("autoApproval")] - public PermissionAutoApproval? AutoApproval { get; set; } - - /// Whether this factory is eligible for persistent approval. - [JsonPropertyName("canPersistApproval")] - public required bool CanPersistApproval { get; set; } + [JsonPropertyName("size")] + public long? Size { get; set; } - /// Gets or sets the declaredMaxAiCredits value. + /// Human-readable display title for the resource. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("declaredMaxAiCredits")] - public double? DeclaredMaxAiCredits { get; set; } + [JsonPropertyName("title")] + public string? Title { get; set; } - /// Gets or sets the declaredMaxConcurrentSubagents value. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("declaredMaxConcurrentSubagents")] - public long? DeclaredMaxConcurrentSubagents { get; set; } + /// URI identifying the resource. + [JsonPropertyName("uri")] + public required string Uri { get; set; } +} - /// Gets or sets the declaredMaxTotalSubagents value. +/// Embedded text resource contents identified by a URI, with an optional MIME type and a text payload. +/// Nested data type for EmbeddedTextResourceContents. +public sealed partial class EmbeddedTextResourceContents +{ + /// MIME type of the text content. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("declaredMaxTotalSubagents")] - public long? DeclaredMaxTotalSubagents { get; set; } + [JsonPropertyName("mimeType")] + public string? MimeType { get; set; } - /// Gets or sets the declaredTimeoutSeconds value. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("declaredTimeoutSeconds")] - public double? DeclaredTimeoutSeconds { get; set; } + /// Text content of the resource. + [JsonPropertyName("text")] + public required string Text { get; set; } - /// Factory description. - [JsonPropertyName("description")] - public required string Description { get; set; } + /// URI identifying the resource. + [JsonPropertyName("uri")] + public required string Uri { 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; } +/// Embedded binary resource contents identified by a URI, with an optional MIME type and a base64-encoded blob. +/// Nested data type for EmbeddedBlobResourceContents. +public sealed partial class EmbeddedBlobResourceContents +{ + /// Base64-encoded binary content of the resource. + [Base64String] + [JsonPropertyName("blob")] + public required string Blob { get; set; } - /// Effective AI-credit limit; omitted means unlimited. + /// MIME type of the blob content. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("maxAiCredits")] - public double? MaxAiCredits { get; set; } + [JsonPropertyName("mimeType")] + public string? MimeType { get; set; } - /// Effective concurrent-subagent limit; omitted means unlimited. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("maxConcurrentSubagents")] - public long? MaxConcurrentSubagents { get; set; } + /// URI identifying the resource. + [JsonPropertyName("uri")] + public required string Uri { get; set; } +} - /// Effective total-subagent limit; omitted means unlimited. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("maxTotalSubagents")] - public long? MaxTotalSubagents { get; set; } +/// The embedded resource contents, either text or base64-encoded binary. +/// JSON union data type for ToolExecutionCompleteContentResourceDetails. +[JsonConverter(typeof(Converter))] +public sealed partial class ToolExecutionCompleteContentResourceDetails +{ + /// Gets the value when this instance contains . + public EmbeddedTextResourceContents? EmbeddedTextResourceContents { get; } - /// Factory name. - [JsonPropertyName("name")] - public required string Name { get; set; } + /// Gets the value when this instance contains . + public EmbeddedBlobResourceContents? EmbeddedBlobResourceContents { get; } - /// Factory operation, either run or author. - [JsonPropertyName("operation")] - public required FactoryPermissionOperation Operation { get; set; } + /// Initializes a new instance of the class from . + public ToolExecutionCompleteContentResourceDetails(EmbeddedTextResourceContents value) + { + ArgumentNullException.ThrowIfNull(value); + EmbeddedTextResourceContents = value; + } - /// Declared factory phases. - [JsonPropertyName("phases")] - public required FactoryPermissionPhase[] Phases { get; set; } + /// Converts to . + public static implicit operator ToolExecutionCompleteContentResourceDetails(EmbeddedTextResourceContents value) => new(value); - /// Effective active-time limit in seconds; omitted means unlimited. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("timeoutSeconds")] - public double? TimeoutSeconds { get; set; } + /// Initializes a new instance of the class from . + public ToolExecutionCompleteContentResourceDetails(EmbeddedBlobResourceContents value) + { + ArgumentNullException.ThrowIfNull(value); + EmbeddedBlobResourceContents = value; + } - /// Tool call ID that triggered this permission request. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } -} + /// Converts to . + public static implicit operator ToolExecutionCompleteContentResourceDetails(EmbeddedBlobResourceContents value) => new(value); -/// Extension permission access prompt. -/// The extension-permission-access variant of . -public sealed partial class PermissionPromptRequestExtensionPermissionAccess : PermissionPromptRequest -{ - /// - [JsonIgnore] - public override string Kind => "extension-permission-access"; + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override ToolExecutionCompleteContentResourceDetails Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + { + throw new JsonException("Expected JSON object for ToolExecutionCompleteContentResourceDetails."); + } - /// 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; } + using var document = JsonDocument.ParseValue(ref reader); + var element = document.RootElement; + if (element.ValueKind == JsonValueKind.Object && element.TryGetProperty("text", out _) && !element.TryGetProperty("blob", out _)) + { + var embeddedTextResourceContents = JsonSerializer.Deserialize(element, SessionEventsJsonContext.Default.EmbeddedTextResourceContents); + return embeddedTextResourceContents is null ? throw new JsonException("Expected EmbeddedTextResourceContents value.") : new ToolExecutionCompleteContentResourceDetails(embeddedTextResourceContents); + } + if (element.ValueKind == JsonValueKind.Object && element.TryGetProperty("blob", out _) && !element.TryGetProperty("text", out _)) + { + var embeddedBlobResourceContents = JsonSerializer.Deserialize(element, SessionEventsJsonContext.Default.EmbeddedBlobResourceContents); + return embeddedBlobResourceContents is null ? throw new JsonException("Expected EmbeddedBlobResourceContents value.") : new ToolExecutionCompleteContentResourceDetails(embeddedBlobResourceContents); + } - /// Capabilities the extension is requesting. - [JsonPropertyName("capabilities")] - public required string[] Capabilities { get; set; } + throw new JsonException("JSON value did not match any ToolExecutionCompleteContentResourceDetails variant."); + } - /// Name of the extension requesting permission access. - [JsonPropertyName("extensionName")] - public required string ExtensionName { get; set; } + /// + public override void Write(Utf8JsonWriter writer, ToolExecutionCompleteContentResourceDetails value, JsonSerializerOptions options) + { + if (value.EmbeddedTextResourceContents is { } embeddedTextResourceContents) + { + JsonSerializer.Serialize(writer, embeddedTextResourceContents, SessionEventsJsonContext.Default.EmbeddedTextResourceContents); + return; + } + if (value.EmbeddedBlobResourceContents is { } embeddedBlobResourceContents) + { + JsonSerializer.Serialize(writer, embeddedBlobResourceContents, SessionEventsJsonContext.Default.EmbeddedBlobResourceContents); + return; + } - /// Tool call ID that triggered this permission request. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } + throw new JsonException("No ToolExecutionCompleteContentResourceDetails variant value is set."); + } + } } -/// Derived user-facing permission prompt details for UI consumers. -/// Polymorphic base type discriminated by kind. +/// Embedded resource content block with inline text or binary data. +/// The resource variant of . +public sealed partial class ToolExecutionCompleteContentResource : ToolExecutionCompleteContent +{ + /// + [JsonIgnore] + public override string Type => "resource"; + + /// The embedded resource contents, either text or base64-encoded binary. + [JsonPropertyName("resource")] + public required ToolExecutionCompleteContentResourceDetails Resource { get; set; } +} + +/// A content block within a tool result, which may be text, terminal output, image, audio, or a resource. +/// Polymorphic base type discriminated by type. [JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", + TypeDiscriminatorPropertyName = "type", UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(PermissionPromptRequestCommands), "commands")] -[JsonDerivedType(typeof(PermissionPromptRequestWrite), "write")] -[JsonDerivedType(typeof(PermissionPromptRequestRead), "read")] -[JsonDerivedType(typeof(PermissionPromptRequestMcp), "mcp")] -[JsonDerivedType(typeof(PermissionPromptRequestUrl), "url")] -[JsonDerivedType(typeof(PermissionPromptRequestMemory), "memory")] -[JsonDerivedType(typeof(PermissionPromptRequestCustomTool), "custom-tool")] -[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 +[JsonDerivedType(typeof(ToolExecutionCompleteContentText), "text")] +[JsonDerivedType(typeof(ToolExecutionCompleteContentTerminal), "terminal")] +[JsonDerivedType(typeof(ToolExecutionCompleteContentShellExit), "shell_exit")] +[JsonDerivedType(typeof(ToolExecutionCompleteContentImage), "image")] +[JsonDerivedType(typeof(ToolExecutionCompleteContentAudio), "audio")] +[JsonDerivedType(typeof(ToolExecutionCompleteContentResourceLink), "resource_link")] +[JsonDerivedType(typeof(ToolExecutionCompleteContentResource), "resource")] +public partial class ToolExecutionCompleteContent { /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; + [JsonPropertyName("type")] + public virtual string Type { get; set; } = string.Empty; } -/// Permission response variant indicating the request was approved without persisting an approval rule. -/// The approved variant of . -public sealed partial class PermissionResultApproved : PermissionResult +/// CSP domain allowlists for an MCP Apps UI resource, including connect, resource, frame, and base URI domains. +/// Nested data type for ToolExecutionCompleteUIResourceMetaUICsp. +public sealed partial class ToolExecutionCompleteUIResourceMetaUICsp { - /// - [JsonIgnore] - public override string Kind => "approved"; -} + /// Domains the UI resource may use as document base URIs. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("baseUriDomains")] + public string[]? BaseUriDomains { get; set; } -/// Session-scoped tool-approval rule for specific shell command identifiers. -/// The commands variant of . -public sealed partial class UserToolSessionApprovalCommands : UserToolSessionApproval -{ - /// - [JsonIgnore] - public override string Kind => "commands"; + /// Domains the UI resource may connect to. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("connectDomains")] + public string[]? ConnectDomains { get; set; } - /// Command identifiers approved by the user. - [JsonPropertyName("commandIdentifiers")] - public required string[] CommandIdentifiers { get; set; } + /// Domains the UI resource may embed as nested frames. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("frameDomains")] + public string[]? FrameDomains { get; set; } + + /// Domains from which the UI resource may load scripts, styles, images, and other resources. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("resourceDomains")] + public string[]? ResourceDomains { get; set; } } -/// Session-scoped tool-approval rule for read-only filesystem operations. -/// The read variant of . -public sealed partial class UserToolSessionApprovalRead : UserToolSessionApproval +/// Marker object for camera permission on an MCP Apps UI resource. +/// Nested data type for ToolExecutionCompleteUIResourceMetaUIPermissionsCamera. +public sealed partial class ToolExecutionCompleteUIResourceMetaUIPermissionsCamera { - /// - [JsonIgnore] - public override string Kind => "read"; } -/// Session-scoped tool-approval rule for filesystem write operations. -/// The write variant of . -public sealed partial class UserToolSessionApprovalWrite : UserToolSessionApproval +/// Marker object for clipboard-write permission on an MCP Apps UI resource. +/// Nested data type for ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite. +public sealed partial class ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite { - /// - [JsonIgnore] - public override string Kind => "write"; } -/// Session-scoped tool-approval rule for an MCP server tool, or all tools on the server when `toolName` is null. -/// The mcp variant of . -public sealed partial class UserToolSessionApprovalMcp : UserToolSessionApproval +/// Marker object for geolocation permission on an MCP Apps UI resource. +/// Nested data type for ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation. +public sealed partial class ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation { - /// - [JsonIgnore] - public override string Kind => "mcp"; - - /// MCP server name. - [JsonPropertyName("serverName")] - public required string ServerName { get; set; } - - /// Optional MCP tool name, or null for all tools on the server. - [JsonPropertyName("toolName")] - public string? ToolName { get; set; } } -/// Session-scoped tool-approval rule for writes to long-term memory. -/// The memory variant of . -public sealed partial class UserToolSessionApprovalMemory : UserToolSessionApproval +/// Marker object for microphone permission on an MCP Apps UI resource. +/// Nested data type for ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone. +public sealed partial class ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone { - /// - [JsonIgnore] - public override string Kind => "memory"; } -/// Session-scoped tool-approval rule for a custom tool, keyed by tool name. -/// The custom-tool variant of . -public sealed partial class UserToolSessionApprovalCustomTool : UserToolSessionApproval +/// Browser permission metadata for an MCP Apps UI resource, including camera, microphone, geolocation, and clipboard-write. +/// Nested data type for ToolExecutionCompleteUIResourceMetaUIPermissions. +public sealed partial class ToolExecutionCompleteUIResourceMetaUIPermissions { - /// - [JsonIgnore] - public override string Kind => "custom-tool"; + /// Marker object for camera permission on an MCP Apps UI resource. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("camera")] + public ToolExecutionCompleteUIResourceMetaUIPermissionsCamera? Camera { get; set; } - /// Custom tool name. - [JsonPropertyName("toolName")] - public required string ToolName { get; set; } -} + /// Marker object for clipboard-write permission on an MCP Apps UI resource. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("clipboardWrite")] + public ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite? ClipboardWrite { get; set; } -/// Session-scoped tool-approval rule for extension-management operations, optionally narrowed by operation. -/// The extension-management variant of . -public sealed partial class UserToolSessionApprovalExtensionManagement : UserToolSessionApproval -{ - /// - [JsonIgnore] - public override string Kind => "extension-management"; + /// Marker object for geolocation permission on an MCP Apps UI resource. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("geolocation")] + public ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation? Geolocation { get; set; } - /// Optional operation identifier. + /// Marker object for microphone permission on an MCP Apps UI resource. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("operation")] - public string? Operation { get; set; } + [JsonPropertyName("microphone")] + public ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone? Microphone { get; set; } } -/// Session-scoped factory approval, optionally narrowed by approval key. -/// The factory variant of . -public sealed partial class UserToolSessionApprovalFactory : UserToolSessionApproval +/// MCP Apps UI resource metadata for a completed tool result, including CSP, permissions, domain, and border preference. +/// Nested data type for ToolExecutionCompleteUIResourceMetaUI. +public sealed partial class ToolExecutionCompleteUIResourceMetaUI { - /// - [JsonIgnore] - public override string Kind => "factory"; + /// CSP domain allowlists for an MCP Apps UI resource, including connect, resource, frame, and base URI domains. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("csp")] + public ToolExecutionCompleteUIResourceMetaUICsp? Csp { get; set; } - /// Optional factory operation name or canonical approval key. + /// Optional dedicated origin for the rendered MCP Apps UI resource. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("approvalKey")] - public string? ApprovalKey { get; set; } -} + [JsonPropertyName("domain")] + public string? Domain { 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 -{ - /// - [JsonIgnore] - public override string Kind => "extension-permission-access"; + /// Browser permission metadata for an MCP Apps UI resource, including camera, microphone, geolocation, and clipboard-write. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("permissions")] + public ToolExecutionCompleteUIResourceMetaUIPermissions? Permissions { get; set; } - /// Extension name. - [JsonPropertyName("extensionName")] - public required string ExtensionName { get; set; } + /// Whether the host should render a border around the MCP Apps UI resource. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("prefersBorder")] + public bool? PrefersBorder { get; set; } } -/// The approval to add as a session-scoped rule. -/// Polymorphic base type discriminated by kind. -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(UserToolSessionApprovalCommands), "commands")] -[JsonDerivedType(typeof(UserToolSessionApprovalRead), "read")] -[JsonDerivedType(typeof(UserToolSessionApprovalWrite), "write")] -[JsonDerivedType(typeof(UserToolSessionApprovalMcp), "mcp")] -[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 +/// Resource-level UI metadata (CSP, permissions, visual preferences). +/// Nested data type for ToolExecutionCompleteUIResourceMeta. +public sealed partial class ToolExecutionCompleteUIResourceMeta { - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; + /// MCP Apps UI resource metadata for a completed tool result, including CSP, permissions, domain, and border preference. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("ui")] + public ToolExecutionCompleteUIResourceMetaUI? Ui { get; set; } } - -/// Permission response variant that approves a request and remembers the provided approval for the rest of the session. -/// The approved-for-session variant of . -public sealed partial class PermissionResultApprovedForSession : PermissionResult +/// MCP Apps UI resource content for rendering in a sandboxed iframe. +/// Nested data type for ToolExecutionCompleteUIResource. +public sealed partial class ToolExecutionCompleteUIResource { - /// - [JsonIgnore] - public override string Kind => "approved-for-session"; + /// Resource-level UI metadata (CSP, permissions, visual preferences). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("_meta")] + public ToolExecutionCompleteUIResourceMeta? Meta { get; set; } - /// The approval to add as a session-scoped rule. - [JsonPropertyName("approval")] - public required UserToolSessionApproval Approval { get; set; } -} + /// Base64-encoded HTML content. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("blob")] + public string? Blob { get; set; } -/// Permission response variant that approves a request and persists the provided approval to a project location key. -/// The approved-for-location variant of . -public sealed partial class PermissionResultApprovedForLocation : PermissionResult -{ - /// - [JsonIgnore] - public override string Kind => "approved-for-location"; + /// MIME type of the content. + [JsonPropertyName("mimeType")] + public required string MimeType { get; set; } - /// The approval to persist for this location. - [JsonPropertyName("approval")] - public required UserToolSessionApproval Approval { get; set; } + /// HTML content as a string. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("text")] + public string? Text { get; set; } - /// The location key (git root or cwd) to persist the approval to. - [JsonPropertyName("locationKey")] - public required string LocationKey { get; set; } + /// The ui:// URI of the resource. + [JsonPropertyName("uri")] + public required string Uri { get; set; } } -/// Permission response variant indicating the request was cancelled before use, with an optional reason. -/// The cancelled variant of . -public sealed partial class PermissionResultCancelled : PermissionResult +/// Tool execution result on success. +/// Nested data type for ToolExecutionCompleteResult. +public sealed partial class ToolExecutionCompleteResult { - /// - [JsonIgnore] - public override string Kind => "cancelled"; + /// Model-facing binary results (base64 inline or size-omitted markers) sent to the LLM for this tool call. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("binaryResultsForLlm")] + public PersistedBinaryResult[]? BinaryResultsForLlm { get; set; } - /// Optional explanation of why the request was cancelled. + /// Provider-neutral source material this tool makes available to the model as citable content. Persisted so it survives session resume. Experimental. + [Experimental(Diagnostics.Experimental)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("reason")] - public string? Reason { get; set; } -} + [JsonPropertyName("citableSources")] + public CitableSource[]? CitableSources { get; set; } -/// A permission approval or denial rule matched against a tool request, identified by a rule kind with an optional argument value. -/// Nested data type for PermissionRule. -public sealed partial class PermissionRule -{ - /// Argument value matched against the request, or null when the rule kind has no argument (e.g. 'read', 'write', 'memory'). - [JsonPropertyName("argument")] - public string? Argument { get; set; } + /// Concise tool result text sent to the LLM for chat completion, potentially truncated for token efficiency. + [JsonPropertyName("content")] + public required string Content { get; set; } - /// The rule kind, such as Shell or GitHubMCP. - [JsonPropertyName("kind")] - public required string Kind { get; set; } + /// Structured content blocks (text, images, audio, resources) returned by the tool in their native format. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("contents")] + public ToolExecutionCompleteContent[]? Contents { get; set; } + + /// Full detailed tool result for UI/timeline display, preserving complete content such as diffs. Falls back to content when absent. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("detailedContent")] + public string? DetailedContent { get; set; } + + /// FIDES IFC label projected from tool ingress metadata (MCP `CallToolResult._meta` or synthesized built-in ingress labels) — persisted as `{ ifc: ... }` (only the `ifc` key, not the whole `_meta`). Persisted so the FIDES IFC label survives session resume: the engine rehydrates accumulated taint by replaying these on load. Populated for ingress sources when FIDES IFC is on. Experimental. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("mcpMeta")] + public JsonElement? McpMeta { get; set; } + + /// Structured content (arbitrary JSON) returned verbatim by the MCP tool. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("structuredContent")] + public JsonElement? StructuredContent { get; set; } + + /// MCP Apps UI resource content for rendering in a sandboxed iframe. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("uiResource")] + public ToolExecutionCompleteUIResource? UiResource { get; set; } } -/// Permission response variant denied because matching approval rules explicitly blocked the request. -/// The denied-by-rules variant of . -public sealed partial class PermissionResultDeniedByRules : PermissionResult +/// MCP Apps tool `_meta.ui` resource URI and visibility captured on `tool.execution_complete`. +/// Nested data type for ToolExecutionCompleteToolDescriptionMetaUI. +public sealed partial class ToolExecutionCompleteToolDescriptionMetaUI { - /// - [JsonIgnore] - public override string Kind => "denied-by-rules"; + /// URI of the UI resource. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("resourceUri")] + public string? ResourceUri { get; set; } - /// Rules that denied the request. - [JsonPropertyName("rules")] - public required PermissionRule[] Rules { get; set; } + /// Who can access this tool. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("visibility")] + public ToolExecutionCompleteToolDescriptionMetaUIVisibility[]? Visibility { get; set; } } -/// Permission response variant denied because no approval rule matched and user confirmation was unavailable. -/// The denied-no-approval-rule-and-could-not-request-from-user variant of . -public sealed partial class PermissionResultDeniedNoApprovalRuleAndCouldNotRequestFromUser : PermissionResult +/// MCP Apps metadata for UI resource association. +/// Nested data type for ToolExecutionCompleteToolDescriptionMeta. +public sealed partial class ToolExecutionCompleteToolDescriptionMeta { - /// - [JsonIgnore] - public override string Kind => "denied-no-approval-rule-and-could-not-request-from-user"; + /// MCP Apps tool `_meta.ui` resource URI and visibility captured on `tool.execution_complete`. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("ui")] + public ToolExecutionCompleteToolDescriptionMetaUI? Ui { get; set; } } -/// Permission response variant denied in an interactive user prompt, with optional feedback and force-reject flag. -/// The denied-interactively-by-user variant of . -public sealed partial class PermissionResultDeniedInteractivelyByUser : PermissionResult +/// Tool definition metadata, present for MCP tools with MCP Apps support. +/// Nested data type for ToolExecutionCompleteToolDescription. +public sealed partial class ToolExecutionCompleteToolDescription { - /// - [JsonIgnore] - public override string Kind => "denied-interactively-by-user"; - - /// Optional feedback from the user explaining the denial. + /// MCP Apps metadata for UI resource association. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("feedback")] - public string? Feedback { get; set; } + [JsonPropertyName("_meta")] + public ToolExecutionCompleteToolDescriptionMeta? Meta { get; set; } - /// Whether to force-reject the current agent turn. + /// Tool description. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("forceReject")] - public bool? ForceReject { get; set; } + [JsonPropertyName("description")] + public string? Description { get; set; } + + /// Tool name. + [JsonPropertyName("name")] + public required string Name { get; set; } } -/// Permission response variant denying a path under content exclusion policy, with the path and message. -/// The denied-by-content-exclusion-policy variant of . -public sealed partial class PermissionResultDeniedByContentExclusionPolicy : PermissionResult +/// Error details when the hook failed. +/// Nested data type for HookEndError. +public sealed partial class HookEndError { - /// - [JsonIgnore] - public override string Kind => "denied-by-content-exclusion-policy"; - - /// Human-readable explanation of why the path was excluded. + /// Human-readable error message. [JsonPropertyName("message")] public required string Message { get; set; } - /// File path that triggered the exclusion. - [JsonPropertyName("path")] - public required string Path { get; set; } + /// Source label of the hook that errored (e.g. the plugin it was loaded from), when known. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("source")] + public string? Source { get; set; } + + /// Error stack trace, when available. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("stack")] + public string? Stack { get; set; } } -/// Permission response variant denied by a permission-request hook, with optional message and interrupt flag. -/// The denied-by-permission-request-hook variant of . -public sealed partial class PermissionResultDeniedByPermissionRequestHook : PermissionResult +/// Metadata about the prompt template and its construction. +/// Nested data type for SystemMessageMetadata. +public sealed partial class SystemMessageMetadata { - /// - [JsonIgnore] - public override string Kind => "denied-by-permission-request-hook"; - - /// Whether to interrupt the current agent turn. + /// Version identifier of the prompt template used. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("interrupt")] - public bool? Interrupt { get; set; } + [JsonPropertyName("promptVersion")] + public string? PromptVersion { get; set; } - /// Optional message from the hook explaining the denial. + /// Template variables used when constructing the prompt. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("message")] - public string? Message { get; set; } + [JsonPropertyName("variables")] + public IDictionary? Variables { get; set; } } -/// The result of the permission request. -/// Polymorphic base type discriminated by kind. -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(PermissionResultApproved), "approved")] -[JsonDerivedType(typeof(PermissionResultApprovedForSession), "approved-for-session")] -[JsonDerivedType(typeof(PermissionResultApprovedForLocation), "approved-for-location")] -[JsonDerivedType(typeof(PermissionResultCancelled), "cancelled")] -[JsonDerivedType(typeof(PermissionResultDeniedByRules), "denied-by-rules")] -[JsonDerivedType(typeof(PermissionResultDeniedNoApprovalRuleAndCouldNotRequestFromUser), "denied-no-approval-rule-and-could-not-request-from-user")] -[JsonDerivedType(typeof(PermissionResultDeniedInteractivelyByUser), "denied-interactively-by-user")] -[JsonDerivedType(typeof(PermissionResultDeniedByContentExclusionPolicy), "denied-by-content-exclusion-policy")] -[JsonDerivedType(typeof(PermissionResultDeniedByPermissionRequestHook), "denied-by-permission-request-hook")] -public partial class PermissionResult +/// System notification metadata for a background agent that completed or failed, including agent ID, type, status, description, and prompt. +/// The agent_completed variant of . +public sealed partial class SystemNotificationAgentCompleted : SystemNotification { - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; -} + /// + [JsonIgnore] + public override string Type => "agent_completed"; + /// Unique task identifier. + [JsonPropertyName("agentId")] + public required string AgentId { get; set; } -/// JSON Schema describing the form fields to present to the user (form mode only). -/// Nested data type for ElicitationRequestedSchema. -public sealed partial class ElicitationRequestedSchema -{ - /// Form field definitions, keyed by field name. - [JsonPropertyName("properties")] - public required IDictionary Properties { get; set; } + /// Type of the agent (e.g., explore, task, general-purpose). + [JsonPropertyName("agentType")] + public required string AgentType { get; set; } - /// List of required field names. + /// Human-readable description of the agent task. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("required")] - public string[]? Required { get; set; } - - /// Schema type indicator (always 'object'). - [JsonPropertyName("type")] - public required string Type { get; set; } -} - -/// Single HTTP header entry as a name/value pair. -/// Nested data type for HeaderEntry. -public sealed partial class HeaderEntry -{ - /// HTTP response header name as observed by the runtime. - [JsonPropertyName("name")] - public required string Name { get; set; } - - /// HTTP response header value as observed by the runtime. - [JsonPropertyName("value")] - public required string Value { get; set; } -} + [JsonPropertyName("description")] + public string? Description { get; set; } -/// Raw HTTP response details from the OAuth auth challenge, as observed by the runtime. -/// Nested data type for McpOauthHttpResponse. -public sealed partial class McpOauthHttpResponse -{ - /// Complete UTF-8 response body for host-specific challenge handling, including an empty string for an empty body. Omitted when the complete body is not valid UTF-8; body read failures fail the HTTP operation rather than exposing a partial response. + /// Friendly, non-unique name intended for display. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("body")] - public string? Body { get; set; } + [JsonPropertyName("displayName")] + public string? DisplayName { get; set; } - /// HTTP response headers as observed by the runtime. Order and casing are transport-dependent, and duplicate header names may appear multiple times. - [JsonPropertyName("headers")] - public required HeaderEntry[] Headers { get; set; } + /// The full prompt given to the background agent. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("prompt")] + public string? Prompt { get; set; } - /// HTTP status code returned with the auth challenge. - [JsonPropertyName("statusCode")] - public required int StatusCode { get; set; } + /// Whether the agent completed successfully or failed. + [JsonPropertyName("status")] + public required SystemNotificationAgentCompletedStatus Status { get; set; } } -/// Static OAuth client configuration, if the server specifies one. -/// Nested data type for McpOauthRequiredStaticClientConfig. -public sealed partial class McpOauthRequiredStaticClientConfig +/// System notification metadata for a background agent that became idle, including agent ID, type, and description. +/// The agent_idle variant of . +public sealed partial class SystemNotificationAgentIdle : SystemNotification { - /// OAuth client ID for the server. - [JsonPropertyName("clientId")] - public required string ClientId { get; set; } + /// + [JsonIgnore] + public override string Type => "agent_idle"; - /// Optional OAuth client secret for confidential static clients, when the runtime can resolve one. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("clientSecret")] - public string? ClientSecret { get; set; } + /// Unique task identifier. + [JsonPropertyName("agentId")] + public required string AgentId { get; set; } - /// Optional non-default OAuth grant type. When set to 'client_credentials', the OAuth flow runs headlessly using the client_id + keychain-stored secret (no browser, no callback server). + /// Type of the agent (e.g., explore, task, general-purpose). + [JsonPropertyName("agentType")] + public required string AgentType { get; set; } + + /// Human-readable description of the agent task. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("grantType")] - public string? GrantType { get; set; } + [JsonPropertyName("description")] + public string? Description { get; set; } - /// Whether this is a public OAuth client. + /// Friendly, non-unique name intended for display. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("publicClient")] - public bool? PublicClient { get; set; } + [JsonPropertyName("displayName")] + public string? DisplayName { get; set; } } -/// OAuth WWW-Authenticate parameters parsed from an MCP auth challenge. -/// Nested data type for McpOauthWWWAuthenticateParams. -public sealed partial class McpOauthWWWAuthenticateParams +/// System notification metadata for a new inbox message, including entry ID, sender details, and summary. +/// The new_inbox_message variant of . +public sealed partial class SystemNotificationNewInboxMessage : SystemNotification { - /// OAuth error from the WWW-Authenticate error parameter, if present. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("error")] - public string? Error { get; set; } + /// + [JsonIgnore] + public override string Type => "new_inbox_message"; - /// Protected resource metadata URL from the WWW-Authenticate resource_metadata parameter, if present. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("resourceMetadataUrl")] - public string? ResourceMetadataUrl { get; set; } + /// Unique identifier of the inbox entry. + [JsonPropertyName("entryId")] + public required string EntryId { get; set; } - /// Requested OAuth scopes from the WWW-Authenticate scope parameter, if present. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("scope")] - public string? Scope { get; set; } + /// Human-readable name of the sender. + [JsonPropertyName("senderName")] + public required string SenderName { get; set; } + + /// Category of the sender (e.g., sidekick-agent, plugin, hook). + [JsonPropertyName("senderType")] + public required string SenderType { get; set; } + + /// Short summary shown before the agent decides whether to read the inbox. + [JsonPropertyName("summary")] + public required string Summary { get; set; } } -/// The user's selected action for an exhausted session limit. -/// Nested data type for SessionLimitsExhaustedResponse. -public sealed partial class SessionLimitsExhaustedResponse +/// System notification metadata for a shell session that completed, including shell ID, optional exit code, and description. +/// The shell_completed variant of . +public sealed partial class SystemNotificationShellCompleted : SystemNotification { - /// Action selected by the user. - [JsonPropertyName("action")] - public required SessionLimitsExhaustedResponseAction Action { get; set; } + /// + [JsonIgnore] + public override string Type => "shell_completed"; - /// AI Credits to add to the current max when action is 'add'. + /// Human-readable description of the command. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("additionalAiCredits")] - public double? AdditionalAiCredits { get; set; } + [JsonPropertyName("description")] + public string? Description { get; set; } - /// New absolute max AI Credits when action is 'set'. + /// Exit code of the shell command, if available. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("maxAiCredits")] - public double? MaxAiCredits { get; set; } + [JsonPropertyName("exitCode")] + public long? ExitCode { get; set; } + + /// Unique identifier of the shell session. + [JsonPropertyName("shellId")] + public required string ShellId { get; set; } } -/// A single slash command available in the session, as listed by the `commands.changed` event. -/// Nested data type for CommandsChangedCommand. -public sealed partial class CommandsChangedCommand +/// System notification metadata for a detached shell session that completed, including shell ID and description. +/// The shell_detached_completed variant of . +public sealed partial class SystemNotificationShellDetachedCompleted : SystemNotification { - /// Optional human-readable command description. + /// + [JsonIgnore] + public override string Type => "shell_detached_completed"; + + /// Human-readable description of the command. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("description")] public string? Description { get; set; } - /// Slash command name without the leading slash. - [JsonPropertyName("name")] - public required string Name { get; set; } + /// Unique identifier of the detached shell session. + [JsonPropertyName("shellId")] + public required string ShellId { get; set; } } -/// UI capability changes. -/// Nested data type for CapabilitiesChangedUI. -public sealed partial class CapabilitiesChangedUI +/// System notification metadata for an instruction file discovered during tool access, including source, trigger file, and tool. +/// The instruction_discovered variant of . +public sealed partial class SystemNotificationInstructionDiscovered : SystemNotification { - /// Whether canvas rendering is now supported. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("canvases")] - public bool? Canvases { get; set; } + /// + [JsonIgnore] + public override string Type => "instruction_discovered"; - /// Whether elicitation is now supported. + /// Human-readable label for the timeline (e.g., 'AGENTS.md from packages/billing/'). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("elicitation")] - public bool? Elicitation { get; set; } + [JsonPropertyName("description")] + public string? Description { get; set; } - /// Whether MCP Apps (SEP-1865) UI passthrough is now supported. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("mcpApps")] - public bool? McpApps { get; set; } + /// Relative path to the discovered instruction file. + [JsonPropertyName("sourcePath")] + public required string SourcePath { get; set; } + + /// Path of the file access that triggered discovery. + [JsonPropertyName("triggerFile")] + public required string TriggerFile { get; set; } + + /// Tool command that triggered discovery (currently always 'view'). + [JsonPropertyName("triggerTool")] + public required string TriggerTool { get; set; } } -/// A single resolved skill in `session.skills_loaded`, including source, invocability, enabled state, path, and argument hint. -/// Nested data type for SkillsLoadedSkill. -public sealed partial class SkillsLoadedSkill +/// System notification metadata for a factory execution attempt that reached a terminal state. +/// The factory_completed variant of . +public sealed partial class SystemNotificationFactoryCompleted : SystemNotification { - /// Optional freeform hint describing the skill's expected arguments, from the `argument-hint` frontmatter field. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("argumentHint")] - public string? ArgumentHint { get; set; } + /// + [JsonIgnore] + public override string Type => "factory_completed"; - /// Description of what the skill does. - [JsonPropertyName("description")] - public required string Description { get; set; } + /// Execution attempt that reached this terminal state. + [JsonPropertyName("attempt")] + public required long Attempt { get; set; } - /// Whether the skill is currently enabled. - [JsonPropertyName("enabled")] - public required bool Enabled { get; set; } + /// Consumed AI usage in nano-AIU. + [JsonPropertyName("consumedNanoAiu")] + public required long ConsumedNanoAiu { get; set; } - /// Unique identifier for the skill. - [JsonPropertyName("name")] - public required string Name { get; set; } + /// Subagents consumed by the run across all attempts. + [JsonPropertyName("consumedSubagents")] + public required long ConsumedSubagents { get; set; } - /// Absolute path to the skill file, if available. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("path")] - public string? Path { get; set; } + /// Accumulated active execution time in milliseconds. + [JsonPropertyName("elapsedMs")] + public required long ElapsedMs { get; set; } - /// Source location type (e.g., project, personal-copilot, plugin, builtin). - [JsonPropertyName("source")] - public required SkillSource Source { get; set; } + /// Persisted factory name. + [JsonPropertyName("factoryName")] + public required string FactoryName { get; set; } - /// Whether the skill can be invoked by the user as a slash command. - [JsonPropertyName("userInvocable")] - public required bool UserInvocable { get; set; } -} + /// Machine-readable terminal failure details, when present. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("failure")] + public JsonElement? Failure { get; set; } -/// A single loaded custom agent in `session.custom_agents_updated`, with identity, source, tools, invocability, and model override. -/// Nested data type for CustomAgentsUpdatedAgent. -public sealed partial class CustomAgentsUpdatedAgent -{ - /// Description of what the agent does. - [JsonPropertyName("description")] - public required string Description { 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; } - /// Human-readable display name. - [JsonPropertyName("displayName")] - public required string DisplayName { get; set; } + /// Actionable run_factory resume guidance for a resource-limit failure. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("retryGuidance")] + public string? RetryGuidance { get; set; } - /// Unique identifier for the agent. - [JsonPropertyName("id")] - public required string Id { get; set; } + /// Factory run identifier. + [JsonPropertyName("runId")] + public required string RunId { get; set; } - /// Model override for this agent, if 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("model")] - public string? Model { get; set; } + [JsonPropertyName("metadata")] + public JsonElement? Metadata { get; set; } +} - /// Internal name of the agent. - [JsonPropertyName("name")] - public required string Name { get; set; } +/// Structured metadata identifying what triggered this notification. +/// Polymorphic base type discriminated by type. +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "type", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(SystemNotificationAgentCompleted), "agent_completed")] +[JsonDerivedType(typeof(SystemNotificationAgentIdle), "agent_idle")] +[JsonDerivedType(typeof(SystemNotificationNewInboxMessage), "new_inbox_message")] +[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. + [JsonPropertyName("type")] + public virtual string Type { get; set; } = string.Empty; +} - /// Source location: user, project, inherited, remote, or plugin. - [JsonPropertyName("source")] - public required string Source { get; set; } - /// List of tool names available to this agent, or null when all tools are available. - [JsonPropertyName("tools")] - public string[]? Tools { get; set; } +/// A parsed command identifier in a shell permission request, including whether it is read-only. +/// Nested data type for PermissionRequestShellCommand. +public sealed partial class PermissionRequestShellCommand +{ + /// Command identifier (e.g., executable name). + [JsonPropertyName("identifier")] + public required string Identifier { get; set; } - /// Whether the agent can be selected by the user. - [JsonPropertyName("userInvocable")] - public required bool UserInvocable { get; set; } + /// Whether this command is read-only (no side effects). + [JsonPropertyName("readOnly")] + public required bool ReadOnly { get; set; } } -/// A single MCP server status summary in `session.mcp_servers_loaded`, including name, status, source, transport, and plugin metadata. -/// Nested data type for McpServersLoadedServer. -public sealed partial class McpServersLoadedServer +/// A parsed shell command segment used for argument-aware managed policy matching. +/// Nested data type for PermissionRequestShellCommandSegment. +public sealed partial class PermissionRequestShellCommandSegment { - /// Error message if the server failed to connect. + /// 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 +{ + /// URL that may be accessed by the command. + [JsonPropertyName("url")] + public required string Url { get; set; } +} + +/// Shell command permission request. +/// The shell variant of . +public sealed partial class PermissionRequestShell : PermissionRequest +{ + /// + [JsonIgnore] + public override string Kind => "shell"; + + /// Whether the UI can offer session-wide approval for this command pattern. + [JsonPropertyName("canOfferSessionApproval")] + public required bool CanOfferSessionApproval { get; set; } + + /// Parsed command identifiers found in the command text. + [JsonPropertyName("commands")] + public required PermissionRequestShellCommand[] Commands { get; set; } + + /// Parsed command segments, including arguments, used for managed policy matching. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("error")] - public string? Error { get; set; } + [JsonPropertyName("commandSegments")] + public PermissionRequestShellCommandSegment[]? CommandSegments { get; set; } - /// Server name (config key). - [JsonPropertyName("name")] - public required string Name { get; set; } + /// The complete shell command text to be executed. + [JsonPropertyName("fullCommandText")] + public required string FullCommandText { get; set; } - /// Name of the plugin that supplied the effective MCP server config, only when source is plugin. + /// Whether the command includes a file write redirection (e.g., > or >>). + [JsonPropertyName("hasWriteFileRedirection")] + public required bool HasWriteFileRedirection { get; set; } + + /// Human-readable description of what the command intends to do. + [JsonPropertyName("intention")] + public required string Intention { get; set; } + + /// [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("pluginName")] - public string? PluginName { get; set; } + [JsonPropertyName("managedApprovalRequired")] + public override bool? ManagedApprovalRequired + { + get => base.ManagedApprovalRequired; + set => base.ManagedApprovalRequired = value; + } - /// Version of the plugin that supplied the effective MCP server config, only when source is plugin. + /// File paths that may be read or written by the command. + [JsonPropertyName("possiblePaths")] + public required string[] PossiblePaths { get; set; } + + /// URLs that may be accessed by the command. + [JsonPropertyName("possibleUrls")] + public required PermissionRequestShellPossibleUrl[] PossibleUrls { get; set; } + + /// True when the model has requested to run this command outside the sandbox (it set requestSandboxBypass: true and the host opted in via sandbox.allowBypass). This is a request, not a grant: the command runs unsandboxed only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("pluginVersion")] - public string? PluginVersion { get; set; } + [JsonPropertyName("requestSandboxBypass")] + public bool? RequestSandboxBypass { get; set; } - /// Configuration source: user, workspace, plugin, or builtin. + /// Model-provided justification for the sandbox-bypass request. Only meaningful when requestSandboxBypass is true. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("source")] - public McpServerSource? Source { get; set; } + [JsonPropertyName("requestSandboxBypassReason")] + public string? RequestSandboxBypassReason { get; set; } - /// Connection status: connected, failed, needs-auth, pending, disabled, stopped, or not_configured. - [JsonPropertyName("status")] - public required McpServerStatus Status { get; set; } + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } - /// Transport mechanism: stdio, http, sse (deprecated), or memory (in-process MCP server). + /// Optional warning message about risks of running this command. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("transport")] - public McpServerTransport? Transport { get; set; } + [JsonPropertyName("warning")] + public string? Warning { get; set; } } -/// A single extension discovered by `session.extensions_loaded`, including qualified ID, source, and current status. -/// Nested data type for ExtensionsLoadedExtension. -public sealed partial class ExtensionsLoadedExtension +/// File write permission request. +/// The write variant of . +public sealed partial class PermissionRequestWrite : PermissionRequest { - /// Source-qualified extension ID (e.g., 'project:my-ext', 'user:auth-helper', 'plugin:my-plugin:my-ext'). - [JsonPropertyName("id")] - public required string Id { get; set; } + /// + [JsonIgnore] + public override string Kind => "write"; - /// Extension name (directory name). - [JsonPropertyName("name")] - public required string Name { get; set; } + /// Whether the UI can offer session-wide approval for file write operations. + [JsonPropertyName("canOfferSessionApproval")] + public required bool CanOfferSessionApproval { get; set; } - /// Discovery source. - [JsonPropertyName("source")] - public required ExtensionsLoadedExtensionSource Source { get; set; } + /// Unified diff showing the proposed changes. + [JsonPropertyName("diff")] + public required string Diff { get; set; } - /// Current status: running, disabled, failed, or starting. - [JsonPropertyName("status")] - public required ExtensionsLoadedExtensionStatus Status { get; set; } + /// Path of the file being written to. + [JsonPropertyName("fileName")] + public required string FileName { get; set; } + + /// Human-readable description of the intended file change. + [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")] + public string? NewFileContents { get; set; } + + /// 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. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("requestSandboxBypass")] + public bool? RequestSandboxBypass { get; set; } + + /// Justification for the sandbox-bypass request. Only meaningful when requestSandboxBypass is true. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("requestSandboxBypassReason")] + public string? RequestSandboxBypassReason { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } +} + +/// File or directory read permission request. +/// The read variant of . +public sealed partial class PermissionRequestRead : PermissionRequest +{ + /// + [JsonIgnore] + public override string Kind => "read"; + + /// Human-readable description of why the file is being read. + [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; } + + /// 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. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("requestSandboxBypass")] + public bool? RequestSandboxBypass { get; set; } + + /// Model-provided justification for the sandbox-bypass request. Only meaningful when requestSandboxBypass is true. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("requestSandboxBypassReason")] + public string? RequestSandboxBypassReason { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } +} + +/// MCP tool invocation permission request. +/// The mcp variant of . +public sealed partial class PermissionRequestMcp : PermissionRequest +{ + /// + [JsonIgnore] + public override string Kind => "mcp"; + + /// Arguments to pass to the MCP tool. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("args")] + public JsonElement? Args { get; set; } + + /// Advisory runtime permission recommendation. The SDK host remains responsible for deciding the request and may reject it. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("permissionRecommendation")] + public PermissionRecommendation? PermissionRecommendation { get; set; } + + /// Whether this MCP tool is read-only (no side effects). + [JsonPropertyName("readOnly")] + public required bool ReadOnly { get; set; } + + /// Name of the MCP server providing the tool. + [JsonPropertyName("serverName")] + public required string ServerName { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } + + /// Internal name of the MCP tool. + [JsonPropertyName("toolName")] + public required string ToolName { get; set; } + + /// Human-readable title of the MCP tool. + [JsonPropertyName("toolTitle")] + public required string ToolTitle { get; set; } +} + +/// URL access permission request. +/// The url variant of . +public sealed partial class PermissionRequestUrl : PermissionRequest +{ + /// + [JsonIgnore] + public override string Kind => "url"; + + /// Human-readable description of why the URL is being accessed. + [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")] + public bool? RequestSandboxBypass { get; set; } + + /// Model-provided justification for the sandbox-bypass request. Only meaningful when requestSandboxBypass is true. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("requestSandboxBypassReason")] + public string? RequestSandboxBypassReason { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } + + /// URL to be fetched. + [JsonPropertyName("url")] + public required string Url { get; set; } +} + +/// Assisted-approval judge information attached to a permission request. Present only in assisted mode; its absence means the judge did not evaluate the request. The `recommendation` conveys the judge's disposition for this request. +/// Nested data type for PermissionAssistedApproval. +[Experimental(Diagnostics.Experimental)] +public sealed partial class PermissionAssistedApproval +{ + /// Classified cause of an `error` recommendation. Absent for every other recommendation. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("failureReason")] + public AssistedApprovalJudgeFailureReason? 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")] + public string? Reason { get; set; } + + /// The assisted-approval safety judge's outcome for this request. + [JsonPropertyName("recommendation")] + public required AssistedApprovalRecommendation Recommendation { get; set; } +} + +/// Memory operation permission request. +/// The memory variant of . +public sealed partial class PermissionRequestMemory : PermissionRequest +{ + /// + [JsonIgnore] + public override string Kind => "memory"; + + /// Whether this is a store or vote memory operation. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("action")] + public PermissionRequestMemoryAction? Action { get; set; } + + /// Assisted-approval judge information for this request; present only in assisted mode. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("assistedApproval")] + public PermissionAssistedApproval? AssistedApproval { get; set; } + + /// Source references for the stored fact (store only). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("citations")] + public string? Citations { get; set; } + + /// Vote direction (vote only). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("direction")] + public PermissionRequestMemoryDirection? Direction { get; set; } + + /// The fact being stored or voted on. + [JsonPropertyName("fact")] + public required string Fact { get; set; } + + /// Reason for the vote (vote only). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("reason")] + public string? Reason { get; set; } + + /// Repository name with owner associated with the stored memory (store only). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("repoNwo")] + public string? RepoNwo { get; set; } + + /// Scope of the stored memory (store only). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("scope")] + public PermissionRequestMemoryScope? Scope { get; set; } + + /// Topic or subject of the memory (store only). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("subject")] + public string? Subject { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } +} + +/// Custom tool invocation permission request. +/// The custom-tool variant of . +public sealed partial class PermissionRequestCustomTool : PermissionRequest +{ + /// + [JsonIgnore] + public override string Kind => "custom-tool"; + + /// Arguments to pass to the custom tool. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("args")] + public JsonElement? Args { get; set; } + + /// Whether the tool declared that permission may be skipped unless a deny rule matches. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("skipPermission")] + public bool? SkipPermission { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } + + /// Description of what the custom tool does. + [JsonPropertyName("toolDescription")] + public required string ToolDescription { get; set; } + + /// Name of the custom tool. + [JsonPropertyName("toolName")] + public required string ToolName { get; set; } +} + +/// Hook confirmation permission request. +/// The hook variant of . +public sealed partial class PermissionRequestHook : PermissionRequest +{ + /// + [JsonIgnore] + public override string Kind => "hook"; + + /// Optional message from the hook explaining why confirmation is needed. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("hookMessage")] + public string? HookMessage { get; set; } + + /// Arguments of the tool call being gated. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolArgs")] + public JsonElement? ToolArgs { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } + + /// Name of the tool the hook is gating. + [JsonPropertyName("toolName")] + public required string ToolName { get; set; } +} + +/// Extension management permission request. +/// The extension-management variant of . +public sealed partial class PermissionRequestExtensionManagement : PermissionRequest +{ + /// + [JsonIgnore] + public override string Kind => "extension-management"; + + /// Name of the extension being managed. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("extensionName")] + public string? ExtensionName { get; set; } + + /// The extension management operation (scaffold, reload). + [JsonPropertyName("operation")] + public required string Operation { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + 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; } + + /// Factory-declared AI-credit limit before any run/resume caller override is applied. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("declaredMaxAiCredits")] + public double? DeclaredMaxAiCredits { get; set; } + + /// Factory-declared concurrent-subagent limit before any run/resume caller override is applied. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("declaredMaxConcurrentSubagents")] + public long? DeclaredMaxConcurrentSubagents { get; set; } + + /// Factory-declared total-subagent limit before any run/resume caller override is applied. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("declaredMaxTotalSubagents")] + public long? DeclaredMaxTotalSubagents { get; set; } + + /// Factory-declared active-time limit in seconds before any run/resume caller override is applied. + [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 +{ + /// + [JsonIgnore] + public override string Kind => "extension-permission-access"; + + /// Capabilities the extension is requesting. + [JsonPropertyName("capabilities")] + public required string[] Capabilities { get; set; } + + /// Name of the extension requesting permission access. + [JsonPropertyName("extensionName")] + public required string ExtensionName { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } +} + +/// Extension sensitive environment variable access request. +/// The extension-env-access variant of . +public sealed partial class PermissionRequestExtensionEnvAccess : PermissionRequest +{ + /// + [JsonIgnore] + public override string Kind => "extension-env-access"; + + /// Names of the sensitive environment variables the extension is requesting. Values never appear here. + [JsonPropertyName("environmentVariables")] + public required string[] EnvironmentVariables { get; set; } + + /// Name of the extension requesting environment variable access. + [JsonPropertyName("extensionName")] + public required string ExtensionName { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } +} + +/// Details of the permission being requested. +/// Polymorphic base type discriminated by kind. +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(PermissionRequestShell), "shell")] +[JsonDerivedType(typeof(PermissionRequestWrite), "write")] +[JsonDerivedType(typeof(PermissionRequestRead), "read")] +[JsonDerivedType(typeof(PermissionRequestMcp), "mcp")] +[JsonDerivedType(typeof(PermissionRequestUrl), "url")] +[JsonDerivedType(typeof(PermissionRequestMemory), "memory")] +[JsonDerivedType(typeof(PermissionRequestCustomTool), "custom-tool")] +[JsonDerivedType(typeof(PermissionRequestHook), "hook")] +[JsonDerivedType(typeof(PermissionRequestExtensionManagement), "extension-management")] +[JsonDerivedType(typeof(PermissionRequestFactory), "factory")] +[JsonDerivedType(typeof(PermissionRequestExtensionPermissionAccess), "extension-permission-access")] +[JsonDerivedType(typeof(PermissionRequestExtensionEnvAccess), "extension-env-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; } +} + + +/// Shell command permission prompt. +/// The commands variant of . +public sealed partial class PermissionPromptRequestCommands : PermissionPromptRequest +{ + /// + [JsonIgnore] + public override string Kind => "commands"; + + /// Assisted-approval judge information for this request; present only in assisted mode. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("assistedApproval")] + public PermissionAssistedApproval? AssistedApproval { get; set; } + + /// Whether the UI can offer session-wide approval for this command pattern. + [JsonPropertyName("canOfferSessionApproval")] + public required bool CanOfferSessionApproval { get; set; } + + /// Command identifiers covered by this approval prompt. + [JsonPropertyName("commandIdentifiers")] + public required string[] CommandIdentifiers { get; set; } + + /// The complete shell command text to be executed. + [JsonPropertyName("fullCommandText")] + public required string FullCommandText { get; set; } + + /// Human-readable description of what the command intends to do. + [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")] + public string? ToolCallId { get; set; } + + /// Optional warning message about risks of running this command. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("warning")] + public string? Warning { get; set; } +} + +/// File write permission prompt. +/// The write variant of . +public sealed partial class PermissionPromptRequestWrite : PermissionPromptRequest +{ + /// + [JsonIgnore] + public override string Kind => "write"; + + /// Assisted-approval judge information for this request; present only in assisted mode. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("assistedApproval")] + public PermissionAssistedApproval? AssistedApproval { get; set; } + + /// Whether the UI can offer session-wide approval for file write operations. + [JsonPropertyName("canOfferSessionApproval")] + public required bool CanOfferSessionApproval { get; set; } + + /// Unified diff showing the proposed changes. + [JsonPropertyName("diff")] + public required string Diff { get; set; } + + /// Path of the file being written to. + [JsonPropertyName("fileName")] + public required string FileName { get; set; } + + /// Human-readable description of the intended file change. + [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")] + public string? NewFileContents { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } +} + +/// File read permission prompt. +/// The read variant of . +public sealed partial class PermissionPromptRequestRead : PermissionPromptRequest +{ + /// + [JsonIgnore] + public override string Kind => "read"; + + /// Assisted-approval judge information for this request; present only in assisted mode. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("assistedApproval")] + public PermissionAssistedApproval? AssistedApproval { get; set; } + + /// Human-readable description of why the file is being read. + [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; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } +} + +/// MCP tool invocation permission prompt. +/// The mcp variant of . +public sealed partial class PermissionPromptRequestMcp : PermissionPromptRequest +{ + /// + [JsonIgnore] + public override string Kind => "mcp"; + + /// Arguments to pass to the MCP tool. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("args")] + public JsonElement? Args { get; set; } + + /// Assisted-approval judge information for this request; present only in assisted mode. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("assistedApproval")] + public PermissionAssistedApproval? AssistedApproval { get; set; } + + /// Whether the host may offer a server-wide "approve all tools from this server" blanket. Absent is treated as true; the runtime sends false when managed policy disables bypass-permissions mode, which forbids the server-wide escalation while still allowing per-tool approval. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("canOfferServerWideApproval")] + public bool? CanOfferServerWideApproval { get; set; } + + /// Advisory runtime permission recommendation. The host remains responsible for deciding the request and may reject it. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("permissionRecommendation")] + public PermissionRecommendation? PermissionRecommendation { get; set; } + + /// Name of the MCP server providing the tool. + [JsonPropertyName("serverName")] + public required string ServerName { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } + + /// Internal name of the MCP tool. + [JsonPropertyName("toolName")] + public required string ToolName { get; set; } + + /// Human-readable title of the MCP tool. + [JsonPropertyName("toolTitle")] + public required string ToolTitle { get; set; } +} + +/// URL access permission prompt. +/// The url variant of . +public sealed partial class PermissionPromptRequestUrl : PermissionPromptRequest +{ + /// + [JsonIgnore] + public override string Kind => "url"; + + /// Assisted-approval judge information for this request; present only in assisted mode. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("assistedApproval")] + public PermissionAssistedApproval? AssistedApproval { get; set; } + + /// Human-readable description of why the URL is being accessed. + [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")] + public bool? RequestSandboxBypass { get; set; } + + /// Model-provided justification for the sandbox-bypass request. Only meaningful when requestSandboxBypass is true. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("requestSandboxBypassReason")] + public string? RequestSandboxBypassReason { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } + + /// URL to be fetched. + [JsonPropertyName("url")] + public required string Url { get; set; } +} + +/// Memory operation permission prompt. +/// The memory variant of . +public sealed partial class PermissionPromptRequestMemory : PermissionPromptRequest +{ + /// + [JsonIgnore] + public override string Kind => "memory"; + + /// Whether this is a store or vote memory operation. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("action")] + public PermissionRequestMemoryAction? Action { get; set; } + + /// Assisted-approval judge information for this request; present only in assisted mode. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("assistedApproval")] + public PermissionAssistedApproval? AssistedApproval { get; set; } + + /// Source references for the stored fact (store only). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("citations")] + public string? Citations { get; set; } + + /// Vote direction (vote only). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("direction")] + public PermissionRequestMemoryDirection? Direction { get; set; } + + /// The fact being stored or voted on. + [JsonPropertyName("fact")] + public required string Fact { get; set; } + + /// Reason for the vote (vote only). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("reason")] + public string? Reason { get; set; } + + /// Topic or subject of the memory (store only). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("subject")] + public string? Subject { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } +} + +/// Custom tool invocation permission prompt. +/// The custom-tool variant of . +public sealed partial class PermissionPromptRequestCustomTool : PermissionPromptRequest +{ + /// + [JsonIgnore] + public override string Kind => "custom-tool"; + + /// Arguments to pass to the custom tool. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("args")] + public JsonElement? Args { get; set; } + + /// Assisted-approval judge information for this request; present only in assisted mode. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("assistedApproval")] + public PermissionAssistedApproval? AssistedApproval { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } + + /// Description of what the custom tool does. + [JsonPropertyName("toolDescription")] + public required string ToolDescription { get; set; } + + /// Name of the custom tool. + [JsonPropertyName("toolName")] + public required string ToolName { get; set; } +} + +/// Path access permission prompt. +/// The path variant of . +public sealed partial class PermissionPromptRequestPath : PermissionPromptRequest +{ + /// + [JsonIgnore] + public override string Kind => "path"; + + /// Underlying permission kind that needs path approval. + [JsonPropertyName("accessKind")] + public required PermissionPromptRequestPathAccessKind AccessKind { get; set; } + + /// Assisted-approval judge information for this request; present only in assisted mode. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("assistedApproval")] + public PermissionAssistedApproval? AssistedApproval { get; set; } + + /// File paths that require explicit approval. + [JsonPropertyName("paths")] + public required string[] Paths { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } +} + +/// Hook confirmation permission prompt. +/// The hook variant of . +public sealed partial class PermissionPromptRequestHook : PermissionPromptRequest +{ + /// + [JsonIgnore] + public override string Kind => "hook"; + + /// Assisted-approval judge information for this request; present only in assisted mode. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("assistedApproval")] + public PermissionAssistedApproval? AssistedApproval { get; set; } + + /// Optional message from the hook explaining why confirmation is needed. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("hookMessage")] + public string? HookMessage { get; set; } + + /// Arguments of the tool call being gated. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolArgs")] + public JsonElement? ToolArgs { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } + + /// Name of the tool the hook is gating. + [JsonPropertyName("toolName")] + public required string ToolName { get; set; } +} + +/// Extension management permission prompt. +/// The extension-management variant of . +public sealed partial class PermissionPromptRequestExtensionManagement : PermissionPromptRequest +{ + /// + [JsonIgnore] + public override string Kind => "extension-management"; + + /// Assisted-approval judge information for this request; present only in assisted mode. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("assistedApproval")] + public PermissionAssistedApproval? AssistedApproval { get; set; } + + /// Name of the extension being managed. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("extensionName")] + public string? ExtensionName { get; set; } + + /// The extension management operation (scaffold, reload). + [JsonPropertyName("operation")] + public required string Operation { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } +} + +/// Factory run or authoring permission prompt. +/// The factory variant of . +public sealed partial class PermissionPromptRequestFactory : PermissionPromptRequest +{ + /// + [JsonIgnore] + public override string Kind => "factory"; + + /// Canonical key used for scoped factory approvals. + [JsonPropertyName("approvalKey")] + public required string ApprovalKey { get; set; } + + /// Assisted-approval judge information for this request; present only in assisted mode. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("assistedApproval")] + public PermissionAssistedApproval? AssistedApproval { get; set; } + + /// Whether this factory is eligible for persistent approval. + [JsonPropertyName("canPersistApproval")] + public required bool CanPersistApproval { get; set; } + + /// Factory-declared AI-credit limit before any run/resume caller override is applied. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("declaredMaxAiCredits")] + public double? DeclaredMaxAiCredits { get; set; } + + /// Factory-declared concurrent-subagent limit before any run/resume caller override is applied. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("declaredMaxConcurrentSubagents")] + public long? DeclaredMaxConcurrentSubagents { get; set; } + + /// Factory-declared total-subagent limit before any run/resume caller override is applied. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("declaredMaxTotalSubagents")] + public long? DeclaredMaxTotalSubagents { get; set; } + + /// Factory-declared active-time limit in seconds before any run/resume caller override is applied. + [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"; + + /// Assisted-approval judge information for this request; present only in assisted mode. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("assistedApproval")] + public PermissionAssistedApproval? AssistedApproval { get; set; } + + /// Capabilities the extension is requesting. + [JsonPropertyName("capabilities")] + public required string[] Capabilities { get; set; } + + /// Name of the extension requesting permission access. + [JsonPropertyName("extensionName")] + public required string ExtensionName { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } +} + +/// Extension sensitive environment variable access prompt. +/// The extension-env-access variant of . +public sealed partial class PermissionPromptRequestExtensionEnvAccess : PermissionPromptRequest +{ + /// + [JsonIgnore] + public override string Kind => "extension-env-access"; + + /// Assisted-approval judge information for this request; present only in assisted mode. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("assistedApproval")] + public PermissionAssistedApproval? AssistedApproval { get; set; } + + /// Names of the sensitive environment variables the extension is requesting. Values never appear here. + [JsonPropertyName("environmentVariables")] + public required string[] EnvironmentVariables { get; set; } + + /// Name of the extension requesting environment variable access. + [JsonPropertyName("extensionName")] + public required string ExtensionName { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } +} + +/// Derived user-facing permission prompt details for UI consumers. +/// Polymorphic base type discriminated by kind. +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(PermissionPromptRequestCommands), "commands")] +[JsonDerivedType(typeof(PermissionPromptRequestWrite), "write")] +[JsonDerivedType(typeof(PermissionPromptRequestRead), "read")] +[JsonDerivedType(typeof(PermissionPromptRequestMcp), "mcp")] +[JsonDerivedType(typeof(PermissionPromptRequestUrl), "url")] +[JsonDerivedType(typeof(PermissionPromptRequestMemory), "memory")] +[JsonDerivedType(typeof(PermissionPromptRequestCustomTool), "custom-tool")] +[JsonDerivedType(typeof(PermissionPromptRequestPath), "path")] +[JsonDerivedType(typeof(PermissionPromptRequestHook), "hook")] +[JsonDerivedType(typeof(PermissionPromptRequestExtensionManagement), "extension-management")] +[JsonDerivedType(typeof(PermissionPromptRequestFactory), "factory")] +[JsonDerivedType(typeof(PermissionPromptRequestExtensionPermissionAccess), "extension-permission-access")] +[JsonDerivedType(typeof(PermissionPromptRequestExtensionEnvAccess), "extension-env-access")] +public partial class PermissionPromptRequest +{ + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} + + +/// Permission response variant indicating the request was approved without persisting an approval rule. +/// The approved variant of . +public sealed partial class PermissionResultApproved : PermissionResult +{ + /// + [JsonIgnore] + public override string Kind => "approved"; + + /// Whether a managed approval policy already handled this request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("managedApprovalHandled")] + public bool? ManagedApprovalHandled { get; set; } +} + +/// Session-scoped tool-approval rule for specific shell command identifiers. +/// The commands variant of . +public sealed partial class UserToolSessionApprovalCommands : UserToolSessionApproval +{ + /// + [JsonIgnore] + public override string Kind => "commands"; + + /// Command identifiers approved by the user. + [JsonPropertyName("commandIdentifiers")] + public required string[] CommandIdentifiers { get; set; } +} + +/// Session-scoped tool-approval rule for read-only filesystem operations. +/// The read variant of . +public sealed partial class UserToolSessionApprovalRead : UserToolSessionApproval +{ + /// + [JsonIgnore] + public override string Kind => "read"; +} + +/// Session-scoped tool-approval rule for filesystem write operations. +/// The write variant of . +public sealed partial class UserToolSessionApprovalWrite : UserToolSessionApproval +{ + /// + [JsonIgnore] + public override string Kind => "write"; +} + +/// Session-scoped tool-approval rule for an MCP server tool, or all tools on the server when `toolName` is null. +/// The mcp variant of . +public sealed partial class UserToolSessionApprovalMcp : UserToolSessionApproval +{ + /// + [JsonIgnore] + public override string Kind => "mcp"; + + /// MCP server name. + [JsonPropertyName("serverName")] + public required string ServerName { get; set; } + + /// Optional MCP tool name, or null for all tools on the server. + [JsonPropertyName("toolName")] + public string? ToolName { get; set; } +} + +/// Session-scoped tool-approval rule for writes to long-term memory. +/// The memory variant of . +public sealed partial class UserToolSessionApprovalMemory : UserToolSessionApproval +{ + /// + [JsonIgnore] + public override string Kind => "memory"; +} + +/// Session-scoped tool-approval rule for a custom tool, keyed by tool name. +/// The custom-tool variant of . +public sealed partial class UserToolSessionApprovalCustomTool : UserToolSessionApproval +{ + /// + [JsonIgnore] + public override string Kind => "custom-tool"; + + /// Custom tool name. + [JsonPropertyName("toolName")] + public required string ToolName { get; set; } +} + +/// Session-scoped tool-approval rule for extension-management operations, optionally narrowed by operation. +/// The extension-management variant of . +public sealed partial class UserToolSessionApprovalExtensionManagement : UserToolSessionApproval +{ + /// + [JsonIgnore] + public override string Kind => "extension-management"; + + /// Optional operation identifier. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("operation")] + 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 +{ + /// + [JsonIgnore] + public override string Kind => "extension-permission-access"; + + /// Extension name. + [JsonPropertyName("extensionName")] + public required string ExtensionName { get; set; } +} + +/// Session-scoped tool-approval rule for an extension's access to sensitive environment variables, keyed by extension name and the exact set of variable names. +/// The extension-env-access variant of . +public sealed partial class UserToolSessionApprovalExtensionEnvAccess : UserToolSessionApproval +{ + /// + [JsonIgnore] + public override string Kind => "extension-env-access"; + + /// Names of the sensitive environment variables this approval covers. Values are never persisted. + [JsonPropertyName("environmentVariables")] + public required string[] EnvironmentVariables { get; set; } + + /// Extension name. + [JsonPropertyName("extensionName")] + public required string ExtensionName { get; set; } +} + +/// The approval to add as a session-scoped rule. +/// Polymorphic base type discriminated by kind. +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(UserToolSessionApprovalCommands), "commands")] +[JsonDerivedType(typeof(UserToolSessionApprovalRead), "read")] +[JsonDerivedType(typeof(UserToolSessionApprovalWrite), "write")] +[JsonDerivedType(typeof(UserToolSessionApprovalMcp), "mcp")] +[JsonDerivedType(typeof(UserToolSessionApprovalMemory), "memory")] +[JsonDerivedType(typeof(UserToolSessionApprovalCustomTool), "custom-tool")] +[JsonDerivedType(typeof(UserToolSessionApprovalExtensionManagement), "extension-management")] +[JsonDerivedType(typeof(UserToolSessionApprovalFactory), "factory")] +[JsonDerivedType(typeof(UserToolSessionApprovalExtensionPermissionAccess), "extension-permission-access")] +[JsonDerivedType(typeof(UserToolSessionApprovalExtensionEnvAccess), "extension-env-access")] +public partial class UserToolSessionApproval +{ + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} + + +/// Permission response variant that approves a request and remembers the provided approval for the rest of the session. +/// The approved-for-session variant of . +public sealed partial class PermissionResultApprovedForSession : PermissionResult +{ + /// + [JsonIgnore] + public override string Kind => "approved-for-session"; + + /// The approval to add as a session-scoped rule. + [JsonPropertyName("approval")] + public required UserToolSessionApproval Approval { get; set; } + + /// Whether a managed approval policy already handled this request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("managedApprovalHandled")] + public bool? ManagedApprovalHandled { get; set; } +} + +/// Permission response variant that approves a request and persists the provided approval to a project location key. +/// The approved-for-location variant of . +public sealed partial class PermissionResultApprovedForLocation : PermissionResult +{ + /// + [JsonIgnore] + public override string Kind => "approved-for-location"; + + /// The approval to persist for this location. + [JsonPropertyName("approval")] + public required UserToolSessionApproval Approval { get; set; } + + /// The location key (git root or cwd) to persist the approval to. + [JsonPropertyName("locationKey")] + public required string LocationKey { get; set; } + + /// Whether a managed approval policy already handled this request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("managedApprovalHandled")] + public bool? ManagedApprovalHandled { get; set; } +} + +/// Permission response variant indicating the request was cancelled before use, with an optional reason. +/// The cancelled variant of . +public sealed partial class PermissionResultCancelled : PermissionResult +{ + /// + [JsonIgnore] + public override string Kind => "cancelled"; + + /// Optional explanation of why the request was cancelled. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("reason")] + public string? Reason { get; set; } +} + +/// A permission approval or denial rule matched against a tool request, identified by a rule kind with an optional argument value. +/// Nested data type for PermissionRule. +public sealed partial class PermissionRule +{ + /// Argument value matched against the request, or null when the rule kind has no argument (e.g. 'read', 'write', 'memory'). + [JsonPropertyName("argument")] + public string? Argument { get; set; } + + /// The rule kind, such as Shell or GitHubMCP. + [JsonPropertyName("kind")] + public required string Kind { get; set; } +} + +/// Permission response variant denied because matching approval rules explicitly blocked the request. +/// The denied-by-rules variant of . +public sealed partial class PermissionResultDeniedByRules : PermissionResult +{ + /// + [JsonIgnore] + public override string Kind => "denied-by-rules"; + + /// Rules that denied the request. + [JsonPropertyName("rules")] + public required PermissionRule[] Rules { get; set; } +} + +/// Permission response variant denied because no approval rule matched and user confirmation was unavailable. +/// The denied-no-approval-rule-and-could-not-request-from-user variant of . +public sealed partial class PermissionResultDeniedNoApprovalRuleAndCouldNotRequestFromUser : PermissionResult +{ + /// + [JsonIgnore] + public override string Kind => "denied-no-approval-rule-and-could-not-request-from-user"; +} + +/// Permission response variant denied in an interactive user prompt, with optional feedback and force-reject flag. +/// The denied-interactively-by-user variant of . +public sealed partial class PermissionResultDeniedInteractivelyByUser : PermissionResult +{ + /// + [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; } +} + +/// Permission response variant denying a path under content exclusion policy, with the path and message. +/// The denied-by-content-exclusion-policy variant of . +public sealed partial class PermissionResultDeniedByContentExclusionPolicy : PermissionResult +{ + /// + [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; } +} + +/// Permission response variant denied by a permission-request hook, with optional message and interrupt flag. +/// The denied-by-permission-request-hook variant of . +public sealed partial class PermissionResultDeniedByPermissionRequestHook : PermissionResult +{ + /// + [JsonIgnore] + public override string Kind => "denied-by-permission-request-hook"; + + /// 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; } +} + +/// The result of the permission request. +/// Polymorphic base type discriminated by kind. +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(PermissionResultApproved), "approved")] +[JsonDerivedType(typeof(PermissionResultApprovedForSession), "approved-for-session")] +[JsonDerivedType(typeof(PermissionResultApprovedForLocation), "approved-for-location")] +[JsonDerivedType(typeof(PermissionResultCancelled), "cancelled")] +[JsonDerivedType(typeof(PermissionResultDeniedByRules), "denied-by-rules")] +[JsonDerivedType(typeof(PermissionResultDeniedNoApprovalRuleAndCouldNotRequestFromUser), "denied-no-approval-rule-and-could-not-request-from-user")] +[JsonDerivedType(typeof(PermissionResultDeniedInteractivelyByUser), "denied-interactively-by-user")] +[JsonDerivedType(typeof(PermissionResultDeniedByContentExclusionPolicy), "denied-by-content-exclusion-policy")] +[JsonDerivedType(typeof(PermissionResultDeniedByPermissionRequestHook), "denied-by-permission-request-hook")] +public partial class PermissionResult +{ + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} + + +/// JSON Schema describing the form fields to present to the user (form mode only). +/// Nested data type for ElicitationRequestedSchema. +public sealed partial class ElicitationRequestedSchema +{ + /// Form field definitions, keyed by field name. + [JsonPropertyName("properties")] + public required IDictionary Properties { get; set; } + + /// List of required field names. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("required")] + public string[]? Required { get; set; } + + /// Schema type indicator (always 'object'). + [JsonPropertyName("type")] + public required string Type { get; set; } +} + +/// Single HTTP header entry as a name/value pair. +/// Nested data type for HeaderEntry. +public sealed partial class HeaderEntry +{ + /// HTTP response header name as observed by the runtime. + [JsonPropertyName("name")] + public required string Name { get; set; } + + /// HTTP response header value as observed by the runtime. + [JsonPropertyName("value")] + public required string Value { get; set; } +} + +/// Raw HTTP response details from the OAuth auth challenge, as observed by the runtime. +/// Nested data type for McpOauthHttpResponse. +public sealed partial class McpOauthHttpResponse +{ + /// Complete UTF-8 response body for host-specific challenge handling, including an empty string for an empty body. Omitted when the complete body is not valid UTF-8; body read failures fail the HTTP operation rather than exposing a partial response. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("body")] + public string? Body { get; set; } + + /// HTTP response headers as observed by the runtime. Order and casing are transport-dependent, and duplicate header names may appear multiple times. + [JsonPropertyName("headers")] + public required HeaderEntry[] Headers { get; set; } + + /// HTTP status code returned with the auth challenge. + [JsonPropertyName("statusCode")] + public required int StatusCode { get; set; } +} + +/// Static OAuth client configuration, if the server specifies one. +/// Nested data type for McpOauthRequiredStaticClientConfig. +public sealed partial class McpOauthRequiredStaticClientConfig +{ + /// OAuth client ID for the server. + [JsonPropertyName("clientId")] + public required string ClientId { get; set; } + + /// Optional OAuth client secret for confidential static clients, when the runtime can resolve one. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("clientSecret")] + public string? ClientSecret { get; set; } + + /// Optional non-default OAuth grant type. When set to 'client_credentials', the OAuth flow runs headlessly using the client_id + keychain-stored secret (no browser, no callback server). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("grantType")] + public string? GrantType { get; set; } + + /// Whether this is a public OAuth client. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("publicClient")] + public bool? PublicClient { get; set; } +} + +/// OAuth WWW-Authenticate parameters parsed from an MCP auth challenge. +/// Nested data type for McpOauthWWWAuthenticateParams. +public sealed partial class McpOauthWWWAuthenticateParams +{ + /// OAuth error from the WWW-Authenticate error parameter, if present. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("error")] + public string? Error { get; set; } + + /// Protected resource metadata URL from the WWW-Authenticate resource_metadata parameter, if present. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("resourceMetadataUrl")] + public string? ResourceMetadataUrl { get; set; } + + /// Requested OAuth scopes from the WWW-Authenticate scope parameter, if present. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("scope")] + public string? Scope { get; set; } +} + +/// The user's selected action for an exhausted session limit. +/// Nested data type for SessionLimitsExhaustedResponse. +public sealed partial class SessionLimitsExhaustedResponse +{ + /// Action selected by the user. + [JsonPropertyName("action")] + public required SessionLimitsExhaustedResponseAction Action { get; set; } + + /// AI Credits to add to the current max when action is 'add'. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("additionalAiCredits")] + public double? AdditionalAiCredits { get; set; } + + /// New absolute max AI Credits when action is 'set'. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("maxAiCredits")] + public double? MaxAiCredits { get; set; } +} + +/// A single slash command available in the session, as listed by the `commands.changed` event. +/// Nested data type for CommandsChangedCommand. +public sealed partial class CommandsChangedCommand +{ + /// Optional human-readable command description. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("description")] + public string? Description { get; set; } + + /// Slash command name without the leading slash. + [JsonPropertyName("name")] + public required string Name { get; set; } +} + +/// UI capability changes. +/// Nested data type for CapabilitiesChangedUI. +public sealed partial class CapabilitiesChangedUI +{ + /// Whether canvas rendering is now supported. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("canvases")] + public bool? Canvases { get; set; } + + /// Whether elicitation is now supported. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("elicitation")] + public bool? Elicitation { get; set; } + + /// Whether MCP Apps (SEP-1865) UI passthrough is now supported. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("mcpApps")] + public bool? McpApps { get; set; } +} + +/// A single resolved skill in `session.skills_loaded`, including source, invocability, enabled state, path, and argument hint. +/// Nested data type for SkillsLoadedSkill. +public sealed partial class SkillsLoadedSkill +{ + /// Optional freeform hint describing the skill's expected arguments, from the `argument-hint` frontmatter field. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [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; } + + /// Whether the skill is currently enabled. + [JsonPropertyName("enabled")] + public required bool Enabled { get; set; } + + /// Unique identifier for the skill. + [JsonPropertyName("name")] + public required string Name { get; set; } + + /// Absolute path to the skill file, if available. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("path")] + public string? Path { get; set; } + + /// Source location type (e.g., project, personal-copilot, plugin, builtin, remote, sdk). + [JsonPropertyName("source")] + public required SkillSource Source { get; set; } + + /// Whether the skill can be invoked by the user as a slash command. + [JsonPropertyName("userInvocable")] + public required bool UserInvocable { get; set; } +} + +/// A single loaded custom agent in `session.custom_agents_updated`, with identity, source, tools, invocability, and authored model configuration. +/// Nested data type for CustomAgentsUpdatedAgent. +public sealed partial class CustomAgentsUpdatedAgent +{ + /// Description of what the agent does. + [JsonPropertyName("description")] + public required string Description { get; set; } + + /// Human-readable display name. + [JsonPropertyName("displayName")] + public required string DisplayName { get; set; } + + /// Unique identifier for the agent. + [JsonPropertyName("id")] + public required string Id { get; set; } + + /// Model override for this agent, if set. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("model")] + public string? Model { get; set; } + + /// Whether authored models are preferences or required constraints. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("modelPolicy")] + public AgentModelPolicy? ModelPolicy { get; set; } + + /// Authored model ids in priority order, if configured. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("models")] + public string[]? Models { get; set; } + + /// Internal name of the agent. + [JsonPropertyName("name")] + public required string Name { get; set; } + + /// Source location: user, project, inherited, remote, or plugin. + [JsonPropertyName("source")] + public required string Source { get; set; } + + /// List of tool names available to this agent, or null when all tools are available. + [JsonPropertyName("tools")] + public string[]? Tools { get; set; } + + /// Whether the agent can be selected by the user. + [JsonPropertyName("userInvocable")] + public required bool UserInvocable { get; set; } +} + +/// A single MCP server status summary in `session.mcp_servers_loaded`, including name, status, source, transport, and plugin metadata. +/// Nested data type for McpServersLoadedServer. +public sealed partial class McpServersLoadedServer +{ + /// Error message if the server failed to connect. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("error")] + public string? Error { get; set; } + + /// Server name (config key). + [JsonPropertyName("name")] + public required string Name { get; set; } + + /// Name of the plugin that supplied the effective MCP server config, only when source is plugin. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("pluginName")] + public string? PluginName { get; set; } + + /// Version of the plugin that supplied the effective MCP server config, only when source is plugin. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("pluginVersion")] + public string? PluginVersion { get; set; } + + /// Configuration source: user, workspace, plugin, or builtin. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("source")] + public McpServerSource? Source { get; set; } + + /// Connection status: connected, failed, needs-auth, pending, disabled, stopped, or not_configured. + [JsonPropertyName("status")] + public required McpServerStatus Status { get; set; } + + /// Transport mechanism: stdio, http, sse (deprecated), or memory (in-process MCP server). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("transport")] + public McpServerTransport? Transport { get; set; } +} + +/// A single extension discovered by `session.extensions_loaded`, including qualified ID, source, and current status. +/// Nested data type for ExtensionsLoadedExtension. +public sealed partial class ExtensionsLoadedExtension +{ + /// Source-qualified extension ID (e.g., 'project:my-ext', 'user:auth-helper', 'plugin:my-plugin:my-ext'). + [JsonPropertyName("id")] + public required string Id { get; set; } + + /// Extension name (directory name). + [JsonPropertyName("name")] + public required string Name { get; set; } + + /// Discovery source. + [JsonPropertyName("source")] + public required ExtensionsLoadedExtensionSource Source { get; set; } + + /// Current status: running, disabled, failed, or starting. + [JsonPropertyName("status")] + public required ExtensionsLoadedExtensionStatus Status { get; set; } +} + +/// A single action within a canvas declaration, with its name, optional description, and optional input schema. +/// Nested data type for CanvasRegistryChangedCanvasAction. +[Experimental(Diagnostics.Experimental)] +public sealed partial class CanvasRegistryChangedCanvasAction +{ + /// Action description. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("description")] + public string? Description { get; set; } + + /// JSON Schema for action input. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("inputSchema")] + public JsonElement? InputSchema { get; set; } + + /// Action name. + [JsonPropertyName("name")] + public required string Name { get; set; } +} + +/// A single canvas declaration in `session.canvas.registry_changed`, including provider IDs, display metadata, input schema, and actions. +/// Nested data type for CanvasRegistryChangedCanvas. +[Experimental(Diagnostics.Experimental)] +public sealed partial class CanvasRegistryChangedCanvas +{ + /// Actions the agent or host may invoke. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("actions")] + public CanvasRegistryChangedCanvasAction[]? Actions { get; set; } + + /// Provider-local canvas identifier. + [JsonPropertyName("canvasId")] + public required string CanvasId { get; set; } + + /// Short, single-sentence description shown to the agent in canvas catalogs. + [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("description")] + public required string Description { get; set; } + + /// Human-readable canvas name. + [JsonPropertyName("displayName")] + public required string DisplayName { get; set; } + + /// Owning provider identifier. + [JsonPropertyName("extensionId")] + public required string ExtensionId { get; set; } + + /// Owning extension display name, when available. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("extensionName")] + public string? ExtensionName { get; set; } + + /// Host-local PNG path for the canvas icon, when supplied. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("icon")] + public string? Icon { get; set; } + + /// JSON Schema for canvas open input. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("inputSchema")] + public JsonElement? InputSchema { get; set; } +} + +/// Set when the underlying tools/call threw an error before returning a CallToolResult. +/// Nested data type for McpAppToolCallCompleteError. +public sealed partial class McpAppToolCallCompleteError +{ + /// Human-readable error message. + [JsonPropertyName("message")] + public required string Message { get; set; } +} + +/// MCP App tool `_meta.ui` resource URI and SEP-1865 visibility captured with an `mcp_app.tool_call_complete` result. +/// Nested data type for McpAppToolCallCompleteToolMetaUI. +public sealed partial class McpAppToolCallCompleteToolMetaUI +{ + /// `ui://` URI declared by the tool's `_meta.ui.resourceUri`. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("resourceUri")] + public string? ResourceUri { get; set; } + + /// Tool visibility per SEP-1865 (typically a subset of `["model","app"]`). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("visibility")] + public string[]? Visibility { get; set; } +} + +/// The tool's `_meta.ui` block at the time of the call, so consumers can decide whether to forward the result to the model without re-listing tools. +/// Nested data type for McpAppToolCallCompleteToolMeta. +public sealed partial class McpAppToolCallCompleteToolMeta +{ + /// MCP App tool `_meta.ui` resource URI and SEP-1865 visibility captured with an `mcp_app.tool_call_complete` result. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("ui")] + public McpAppToolCallCompleteToolMetaUI? Ui { get; set; } +} + +/// Routing preference used when the session model is `auto`. +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct AutoTier : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public AutoTier(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Optimize for efficiency. + public static AutoTier Efficiency { get; } = new("efficiency"); + + /// Balance efficiency and intelligence. + public static AutoTier Balance { get; } = new("balance"); + + /// Optimize for intelligence. + public static AutoTier Intelligence { get; } = new("intelligence"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(AutoTier left, AutoTier right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AutoTier left, AutoTier right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is AutoTier other && Equals(other); + + /// + public bool Equals(AutoTier 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 AutoTier Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, AutoTier value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AutoTier)); + } + } +} + +/// Hosting platform type of the repository (github or ado). +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct WorkingDirectoryContextHostType : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public WorkingDirectoryContextHostType(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Repository is hosted on GitHub. + public static WorkingDirectoryContextHostType GitHub { get; } = new("github"); + + /// Repository is hosted on Azure DevOps. + public static WorkingDirectoryContextHostType Ado { get; } = new("ado"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(WorkingDirectoryContextHostType left, WorkingDirectoryContextHostType right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(WorkingDirectoryContextHostType left, WorkingDirectoryContextHostType right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is WorkingDirectoryContextHostType other && Equals(other); + + /// + public bool Equals(WorkingDirectoryContextHostType 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 WorkingDirectoryContextHostType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, WorkingDirectoryContextHostType value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(WorkingDirectoryContextHostType)); + } + } +} + +/// Allowed values for the `ContextTier` enumeration. +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct ContextTier : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public ContextTier(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Default context tier with standard context window size. + public static ContextTier Default { get; } = new("default"); + + /// Extended context tier with a larger context window. + public static ContextTier LongContext { get; } = new("long_context"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ContextTier left, ContextTier right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ContextTier left, ContextTier right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is ContextTier other && Equals(other); + + /// + public bool Equals(ContextTier 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 ContextTier Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, ContextTier value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ContextTier)); + } + } +} + +/// Reasoning summary mode used for model calls, if applicable (e.g. "none", "concise", "detailed"). +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct ReasoningSummary : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public ReasoningSummary(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Do not request reasoning summaries from the model. + public static ReasoningSummary None { get; } = new("none"); + + /// Request a concise summary of the model's reasoning. + public static ReasoningSummary Concise { get; } = new("concise"); + + /// Request a detailed summary of the model's reasoning. + public static ReasoningSummary Detailed { get; } = new("detailed"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ReasoningSummary left, ReasoningSummary right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ReasoningSummary left, ReasoningSummary right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is ReasoningSummary other && Equals(other); + + /// + public bool Equals(ReasoningSummary 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 ReasoningSummary Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, ReasoningSummary value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ReasoningSummary)); + } + } +} + +/// Output verbosity level used for supported model calls (e.g. "low", "medium", "high"). +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct Verbosity : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public Verbosity(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// A terse response was requested. + public static Verbosity Low { get; } = new("low"); + + /// A medium amount of response detail was requested. + public static Verbosity Medium { get; } = new("medium"); + + /// A more detailed response was requested. + public static Verbosity High { get; } = new("high"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(Verbosity left, Verbosity right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(Verbosity left, Verbosity right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is Verbosity other && Equals(other); + + /// + public bool Equals(Verbosity 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 Verbosity Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, Verbosity value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(Verbosity)); + } + } +} + +/// The session mode the agent is operating in. +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct SessionMode : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public SessionMode(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The agent is responding interactively to the user. + public static SessionMode Interactive { get; } = new("interactive"); + + /// The agent is preparing a plan before making changes. + public static SessionMode Plan { get; } = new("plan"); + + /// The agent is working autonomously toward task completion. + public static SessionMode Autopilot { get; } = new("autopilot"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SessionMode left, SessionMode right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SessionMode left, SessionMode right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is SessionMode other && Equals(other); + + /// + public bool Equals(SessionMode 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 SessionMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, SessionMode value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionMode)); + } + } +} + +/// 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}")] +public readonly struct AutopilotObjectiveChangedOperation : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public AutopilotObjectiveChangedOperation(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Autopilot objective state file was created for a new objective. + public static AutopilotObjectiveChangedOperation Create { get; } = new("create"); + + /// Autopilot objective state file was updated for an existing objective. + public static AutopilotObjectiveChangedOperation Update { get; } = new("update"); + + /// Autopilot objective state file was deleted or cleared. + public static AutopilotObjectiveChangedOperation Delete { get; } = new("delete"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(AutopilotObjectiveChangedOperation left, AutopilotObjectiveChangedOperation right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AutopilotObjectiveChangedOperation left, AutopilotObjectiveChangedOperation right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is AutopilotObjectiveChangedOperation other && Equals(other); + + /// + public bool Equals(AutopilotObjectiveChangedOperation 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 AutopilotObjectiveChangedOperation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, AutopilotObjectiveChangedOperation value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AutopilotObjectiveChangedOperation)); + } + } +} + +/// Current autopilot objective status, if one exists. +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct AutopilotObjectiveChangedStatus : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public AutopilotObjectiveChangedStatus(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Objective is active and can drive autopilot continuations. + public static AutopilotObjectiveChangedStatus Active { get; } = new("active"); + + /// Objective is paused and will not drive autopilot continuations. + public static AutopilotObjectiveChangedStatus Paused { get; } = new("paused"); + + /// Legacy objective state indicating the previous continuation cap was reached. + public static AutopilotObjectiveChangedStatus CapReached { get; } = new("cap_reached"); + + /// Objective was completed by the agent. + public static AutopilotObjectiveChangedStatus Completed { get; } = new("completed"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(AutopilotObjectiveChangedStatus left, AutopilotObjectiveChangedStatus right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AutopilotObjectiveChangedStatus left, AutopilotObjectiveChangedStatus right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is AutopilotObjectiveChangedStatus other && Equals(other); + + /// + public bool Equals(AutopilotObjectiveChangedStatus 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 AutopilotObjectiveChangedStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, AutopilotObjectiveChangedStatus value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AutopilotObjectiveChangedStatus)); + } + } +} + +/// Origin of an effective session model change. +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct ModelChangeSource : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public ModelChangeSource(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The user selected a model directly with `/model <id>`. + public static ModelChangeSource ModelCommand { get; } = new("model_command"); + + /// The user selected the model with `/settings`. + public static ModelChangeSource SettingsCommand { get; } = new("settings_command"); + + /// The user selected the model with the `/config` alias. + public static ModelChangeSource ConfigCommand { get; } = new("config_command"); + + /// The user selected the model in the model picker, including the picker opened by bare `/model`. + public static ModelChangeSource ModelPicker { get; } = new("model_picker"); + + /// Organization-managed settings selected the model. + public static ModelChangeSource ManagedSettings { get; } = new("managed_settings"); + + /// Repository settings selected the model. + public static ModelChangeSource RepoSettings { get; } = new("repo_settings"); + + /// Startup model resolution selected the model. + public static ModelChangeSource Startup { get; } = new("startup"); + + /// Selecting an agent selected its configured model. + public static ModelChangeSource Agent { get; } = new("agent"); + + /// Entering, leaving, or reconfiguring plan mode selected the model. + public static ModelChangeSource PlanMode { get; } = new("plan_mode"); + + /// The runtime selected the model automatically, such as rate-limit recovery or refusal fallback. + public static ModelChangeSource Automatic { get; } = new("automatic"); + + /// An SDK or RPC caller selected the model. + public static ModelChangeSource Sdk { get; } = new("sdk"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ModelChangeSource left, ModelChangeSource right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ModelChangeSource left, ModelChangeSource right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is ModelChangeSource other && Equals(other); + + /// + public bool Equals(ModelChangeSource 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 ModelChangeSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, ModelChangeSource value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ModelChangeSource)); + } + } +} + +/// Permission mode for the session. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct PermissionMode : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public PermissionMode(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Permission requests follow the normal approval flow. + public static PermissionMode Manual { get; } = new("manual"); + + /// Permission requests include an LLM safety recommendation; clients may automatically approve requests judged acceptable. + public static PermissionMode Assisted { get; } = new("assisted"); + + /// Tool, path, and URL permission requests are automatically approved. + public static PermissionMode AllowAll { get; } = new("allow-all"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(PermissionMode left, PermissionMode right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(PermissionMode left, PermissionMode right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is PermissionMode other && Equals(other); + + /// + public bool Equals(PermissionMode 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 PermissionMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, PermissionMode value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionMode)); + } + } +} + +/// The type of operation performed on the plan file. +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct PlanChangedOperation : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public PlanChangedOperation(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The plan file was created. + public static PlanChangedOperation Create { get; } = new("create"); + + /// The plan file was updated. + public static PlanChangedOperation Update { get; } = new("update"); + + /// The plan file was deleted. + public static PlanChangedOperation Delete { get; } = new("delete"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(PlanChangedOperation left, PlanChangedOperation right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(PlanChangedOperation left, PlanChangedOperation right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is PlanChangedOperation other && Equals(other); + + /// + public bool Equals(PlanChangedOperation 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 PlanChangedOperation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, PlanChangedOperation value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PlanChangedOperation)); + } + } +} + +/// Whether the file was newly created or updated. +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct WorkspaceFileChangedOperation : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public WorkspaceFileChangedOperation(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The workspace file was created. + public static WorkspaceFileChangedOperation Create { get; } = new("create"); + + /// The workspace file was updated. + public static WorkspaceFileChangedOperation Update { get; } = new("update"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(WorkspaceFileChangedOperation left, WorkspaceFileChangedOperation right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(WorkspaceFileChangedOperation left, WorkspaceFileChangedOperation right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is WorkspaceFileChangedOperation other && Equals(other); + + /// + public bool Equals(WorkspaceFileChangedOperation 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 WorkspaceFileChangedOperation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, WorkspaceFileChangedOperation value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(WorkspaceFileChangedOperation)); + } + } +} + +/// Origin type of the session being handed off. +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct HandoffSourceType : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public HandoffSourceType(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The handoff originated from a remote session. + public static HandoffSourceType Remote { get; } = new("remote"); + + /// The handoff originated from a local session. + public static HandoffSourceType Local { get; } = new("local"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(HandoffSourceType left, HandoffSourceType right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(HandoffSourceType left, HandoffSourceType right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is HandoffSourceType other && Equals(other); + + /// + public bool Equals(HandoffSourceType 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 HandoffSourceType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, HandoffSourceType value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(HandoffSourceType)); + } + } +} + +/// Whether the session ended normally ("routine") or due to a crash/fatal error ("error"). +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct ShutdownType : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public ShutdownType(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The session ended normally. + public static ShutdownType Routine { get; } = new("routine"); + + /// The session ended because of a crash or fatal error. + public static ShutdownType Error { get; } = new("error"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ShutdownType left, ShutdownType right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ShutdownType left, ShutdownType right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is ShutdownType other && Equals(other); + + /// + public bool Equals(ShutdownType 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 ShutdownType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, ShutdownType value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ShutdownType)); + } + } } -/// A single action within a canvas declaration, with its name, optional description, and optional input schema. -/// Nested data type for CanvasRegistryChangedCanvasAction. -[Experimental(Diagnostics.Experimental)] -public sealed partial class CanvasRegistryChangedCanvasAction +/// What initiated a conversation compaction. +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct CompactionTrigger : IEquatable { - /// Action description. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("description")] - public string? Description { get; set; } + private readonly string? _value; - /// JSON Schema for action input. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("inputSchema")] - public JsonElement? InputSchema { get; set; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public CompactionTrigger(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Action name. - [JsonPropertyName("name")] - public required string Name { get; set; } -} + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; -/// A single canvas declaration in `session.canvas.registry_changed`, including provider IDs, display metadata, input schema, and actions. -/// Nested data type for CanvasRegistryChangedCanvas. -[Experimental(Diagnostics.Experimental)] -public sealed partial class CanvasRegistryChangedCanvas -{ - /// Actions the agent or host may invoke. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("actions")] - public CanvasRegistryChangedCanvasAction[]? Actions { get; set; } + /// Background compaction started automatically because context utilization crossed the background threshold. + public static CompactionTrigger Threshold { get; } = new("threshold"); - /// Provider-local canvas identifier. - [JsonPropertyName("canvasId")] - public required string CanvasId { get; set; } + /// 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"); - /// Short, single-sentence description shown to the agent in canvas catalogs. - [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("description")] - public required string Description { get; set; } + /// User-requested compaction, e.g. the /compact command or the history.compact API. + public static CompactionTrigger Manual { get; } = new("manual"); - /// Human-readable canvas name. - [JsonPropertyName("displayName")] - public required string DisplayName { get; set; } + /// Emergency compaction triggered by high process memory usage. + public static CompactionTrigger MemoryPressure { get; } = new("memory_pressure"); - /// Owning provider identifier. - [JsonPropertyName("extensionId")] - public required string ExtensionId { get; set; } + /// Compaction requested while switching to a model with a smaller context window. + public static CompactionTrigger ModelSwitch { get; } = new("model_switch"); - /// Owning extension display name, when available. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("extensionName")] - public string? ExtensionName { get; set; } + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(CompactionTrigger left, CompactionTrigger right) => left.Equals(right); - /// Host-local PNG path for the canvas icon, when supplied. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("icon")] - public string? Icon { get; set; } + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(CompactionTrigger left, CompactionTrigger right) => !(left == right); - /// JSON Schema for canvas open input. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("inputSchema")] - public JsonElement? InputSchema { get; set; } -} + /// + public override bool Equals(object? obj) => obj is CompactionTrigger other && Equals(other); -/// Set when the underlying tools/call threw an error before returning a CallToolResult. -/// Nested data type for McpAppToolCallCompleteError. -public sealed partial class McpAppToolCallCompleteError -{ - /// Human-readable error message. - [JsonPropertyName("message")] - public required string Message { get; set; } -} + /// + public bool Equals(CompactionTrigger other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); -/// MCP App tool `_meta.ui` resource URI and SEP-1865 visibility captured with an `mcp_app.tool_call_complete` result. -/// Nested data type for McpAppToolCallCompleteToolMetaUI. -public sealed partial class McpAppToolCallCompleteToolMetaUI -{ - /// `ui://` URI declared by the tool's `_meta.ui.resourceUri`. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("resourceUri")] - public string? ResourceUri { get; set; } + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - /// Tool visibility per SEP-1865 (typically a subset of `["model","app"]`). - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("visibility")] - public string[]? Visibility { get; set; } -} + /// + public override string ToString() => Value; -/// The tool's `_meta.ui` block at the time of the call, so consumers can decide whether to forward the result to the model without re-listing tools. -/// Nested data type for McpAppToolCallCompleteToolMeta. -public sealed partial class McpAppToolCallCompleteToolMeta -{ - /// MCP App tool `_meta.ui` resource URI and SEP-1865 visibility captured with an `mcp_app.tool_call_complete` result. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("ui")] - public McpAppToolCallCompleteToolMetaUI? Ui { get; set; } + /// 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)); + } + } } -/// Hosting platform type of the repository (github or ado). +/// Semantic result of evaluating a task completion request. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct WorkingDirectoryContextHostType : IEquatable +public readonly struct TaskCompletionOutcome : 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 WorkingDirectoryContextHostType(string value) + public TaskCompletionOutcome(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; - /// Repository is hosted on GitHub. - public static WorkingDirectoryContextHostType GitHub { get; } = new("github"); + /// The completion request was accepted and the objective is complete. + public static TaskCompletionOutcome Completed { get; } = new("completed"); - /// Repository is hosted on Azure DevOps. - public static WorkingDirectoryContextHostType Ado { get; } = new("ado"); + /// The completion request was rejected because more work or validation remains. + public static TaskCompletionOutcome Continue { get; } = new("continue"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(WorkingDirectoryContextHostType left, WorkingDirectoryContextHostType right) => left.Equals(right); + /// 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 not equivalent. - public static bool operator !=(WorkingDirectoryContextHostType left, WorkingDirectoryContextHostType right) => !(left == right); + /// 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 WorkingDirectoryContextHostType other && Equals(other); + public override bool Equals(object? obj) => obj is TaskCompletionOutcome other && Equals(other); /// - public bool Equals(WorkingDirectoryContextHostType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(TaskCompletionOutcome other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -8828,60 +11661,69 @@ public WorkingDirectoryContextHostType(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 WorkingDirectoryContextHostType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override TaskCompletionOutcome Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, WorkingDirectoryContextHostType value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, TaskCompletionOutcome value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(WorkingDirectoryContextHostType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskCompletionOutcome)); } } } -/// Allowed values for the `ContextTier` enumeration. +/// Structured terminal status from a tool completion event. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ContextTier : IEquatable +public readonly struct CompletionReceiptToolStatus : 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 ContextTier(string value) + public CompletionReceiptToolStatus(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; - /// Default context tier with standard context window size. - public static ContextTier Default { get; } = new("default"); + /// The tool completed successfully. + public static CompletionReceiptToolStatus Success { get; } = new("success"); - /// Extended context tier with a larger context window. - public static ContextTier LongContext { get; } = new("long_context"); + /// The tool failed without a more specific structured status. + public static CompletionReceiptToolStatus Failure { get; } = new("failure"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(ContextTier left, ContextTier right) => left.Equals(right); + /// The tool exceeded its time budget. + public static CompletionReceiptToolStatus Timeout { get; } = new("timeout"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(ContextTier left, ContextTier right) => !(left == right); + /// The user rejected the tool call. + public static CompletionReceiptToolStatus Rejected { get; } = new("rejected"); + + /// The permissions service denied the tool call. + public static CompletionReceiptToolStatus Denied { get; } = new("denied"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(CompletionReceiptToolStatus left, CompletionReceiptToolStatus right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(CompletionReceiptToolStatus left, CompletionReceiptToolStatus right) => !(left == right); /// - public override bool Equals(object? obj) => obj is ContextTier other && Equals(other); + public override bool Equals(object? obj) => obj is CompletionReceiptToolStatus other && Equals(other); /// - public bool Equals(ContextTier other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(CompletionReceiptToolStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -8889,63 +11731,63 @@ public ContextTier(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 ContextTier Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override CompletionReceiptToolStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, ContextTier value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, CompletionReceiptToolStatus value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ContextTier)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(CompletionReceiptToolStatus)); } } } -/// Reasoning summary mode used for model calls, if applicable (e.g. "none", "concise", "detailed"). +/// Runtime reason the completion decision was accepted. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ReasoningSummary : IEquatable +public readonly struct CompletionReceiptStopReason : 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 ReasoningSummary(string value) + public CompletionReceiptStopReason(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 ReasoningSummary None { get; } = new("none"); + /// The model reached a natural terminal response. + public static CompletionReceiptStopReason Natural { get; } = new("natural"); - /// Request a concise summary of the model's reasoning. - public static ReasoningSummary Concise { get; } = new("concise"); + /// A terminal tool ended the interaction. + public static CompletionReceiptStopReason TerminalTool { get; } = new("terminal_tool"); - /// Request a detailed summary of the model's reasoning. - public static ReasoningSummary Detailed { get; } = new("detailed"); + /// The configured agentStop continuation limit was reached. + public static CompletionReceiptStopReason AgentStopBlockLimit { get; } = new("agent_stop_block_limit"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(ReasoningSummary left, ReasoningSummary right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(CompletionReceiptStopReason left, CompletionReceiptStopReason right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(ReasoningSummary left, ReasoningSummary right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(CompletionReceiptStopReason left, CompletionReceiptStopReason right) => !(left == right); /// - public override bool Equals(object? obj) => obj is ReasoningSummary other && Equals(other); + public override bool Equals(object? obj) => obj is CompletionReceiptStopReason other && Equals(other); /// - public bool Equals(ReasoningSummary other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(CompletionReceiptStopReason other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -8953,63 +11795,61 @@ public ReasoningSummary(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 ReasoningSummary Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override CompletionReceiptStopReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, ReasoningSummary value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, CompletionReceiptStopReason value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ReasoningSummary)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(CompletionReceiptStopReason)); } } } -/// Output verbosity level used for supported model calls (e.g. "low", "medium", "high"). +/// Kind of turn for which HydraFusion routing is running. +[Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct Verbosity : IEquatable +public readonly struct FusionTurnKind : 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 Verbosity(string value) + public FusionTurnKind(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 terse response was requested. - public static Verbosity Low { get; } = new("low"); - - /// A medium amount of response detail was requested. - public static Verbosity Medium { get; } = new("medium"); + /// A user-message turn. + public static FusionTurnKind User { get; } = new("user"); - /// A more detailed response was requested. - public static Verbosity High { get; } = new("high"); + /// A conversation-compaction turn. + public static FusionTurnKind Compaction { get; } = new("compaction"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(Verbosity left, Verbosity right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(FusionTurnKind left, FusionTurnKind right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(Verbosity left, Verbosity right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(FusionTurnKind left, FusionTurnKind right) => !(left == right); /// - public override bool Equals(object? obj) => obj is Verbosity other && Equals(other); + public override bool Equals(object? obj) => obj is FusionTurnKind other && Equals(other); /// - public bool Equals(Verbosity other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(FusionTurnKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -9017,60 +11857,61 @@ public Verbosity(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 Verbosity Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override FusionTurnKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, Verbosity value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, FusionTurnKind value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(Verbosity)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FusionTurnKind)); } } } -/// 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. +/// Server-recommended routing behavior for a later HydraFusion turn. +[Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ScheduleOrigin : IEquatable +public readonly struct FusionFollowUpAction : 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 ScheduleOrigin(string value) + public FusionFollowUpAction(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 schedule was created by an explicit user action, such as `/every` or `/after`. - public static ScheduleOrigin User { get; } = new("user"); + /// Reuse the durable primary model without routing. + public static FusionFollowUpAction ReusePrimary { get; } = new("reuse_primary"); - /// The schedule was created by the agent via the `manage_schedule` tool. - public static ScheduleOrigin Model { get; } = new("model"); + /// Request a new routing decision. + public static FusionFollowUpAction Reroute { get; } = new("reroute"); - /// 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 equivalent. + public static bool operator ==(FusionFollowUpAction left, FusionFollowUpAction right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(ScheduleOrigin left, ScheduleOrigin right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(FusionFollowUpAction left, FusionFollowUpAction right) => !(left == right); /// - public override bool Equals(object? obj) => obj is ScheduleOrigin other && Equals(other); + public override bool Equals(object? obj) => obj is FusionFollowUpAction other && Equals(other); /// - public bool Equals(ScheduleOrigin other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(FusionFollowUpAction other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -9078,63 +11919,64 @@ public ScheduleOrigin(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 ScheduleOrigin Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override FusionFollowUpAction 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) + public override void Write(Utf8JsonWriter writer, FusionFollowUpAction value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ScheduleOrigin)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FusionFollowUpAction)); } } } -/// The type of operation performed on the autopilot objective state file. +/// Validated HydraFusion execution pattern. +[Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct AutopilotObjectiveChangedOperation : IEquatable +public readonly struct FusionPattern : 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 AutopilotObjectiveChangedOperation(string value) + public FusionPattern(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; - /// Autopilot objective state file was created for a new objective. - public static AutopilotObjectiveChangedOperation Create { get; } = new("create"); + /// Run one primary solver phase. + public static FusionPattern Single { get; } = new("single"); - /// Autopilot objective state file was updated for an existing objective. - public static AutopilotObjectiveChangedOperation Update { get; } = new("update"); + /// Run a primary phase, a judge, and an optional repair. + public static FusionPattern Cascade { get; } = new("cascade"); - /// Autopilot objective state file was deleted or cleared. - public static AutopilotObjectiveChangedOperation Delete { get; } = new("delete"); + /// Run a primary draft, a read-only critique, and a revision. + public static FusionPattern Critique { get; } = new("critique"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(AutopilotObjectiveChangedOperation left, AutopilotObjectiveChangedOperation right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(FusionPattern left, FusionPattern right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(AutopilotObjectiveChangedOperation left, AutopilotObjectiveChangedOperation right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(FusionPattern left, FusionPattern right) => !(left == right); /// - public override bool Equals(object? obj) => obj is AutopilotObjectiveChangedOperation other && Equals(other); + public override bool Equals(object? obj) => obj is FusionPattern other && Equals(other); /// - public bool Equals(AutopilotObjectiveChangedOperation other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(FusionPattern other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -9142,66 +11984,76 @@ public AutopilotObjectiveChangedOperation(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 AutopilotObjectiveChangedOperation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override FusionPattern Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, AutopilotObjectiveChangedOperation value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, FusionPattern value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AutopilotObjectiveChangedOperation)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FusionPattern)); } } } -/// Current autopilot objective status, if one exists. +/// HydraFusion phase kind. +[Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct AutopilotObjectiveChangedStatus : IEquatable +public readonly struct FusionPhaseKind : 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 AutopilotObjectiveChangedStatus(string value) + public FusionPhaseKind(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; - /// Objective is active and can drive autopilot continuations. - public static AutopilotObjectiveChangedStatus Active { get; } = new("active"); + /// Primary solver phase. + public static FusionPhaseKind Primary { get; } = new("primary"); - /// Objective is paused and will not drive autopilot continuations. - public static AutopilotObjectiveChangedStatus Paused { get; } = new("paused"); + /// Read-only cascade judge phase. + public static FusionPhaseKind Judge { get; } = new("judge"); - /// Legacy objective state indicating the previous continuation cap was reached. - public static AutopilotObjectiveChangedStatus CapReached { get; } = new("cap_reached"); + /// Cascade repair phase. + public static FusionPhaseKind Repair { get; } = new("repair"); - /// Objective was completed by the agent. - public static AutopilotObjectiveChangedStatus Completed { get; } = new("completed"); + /// Initial critique-pattern draft phase. + public static FusionPhaseKind Draft { get; } = new("draft"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(AutopilotObjectiveChangedStatus left, AutopilotObjectiveChangedStatus right) => left.Equals(right); + /// Read-only critique phase. + public static FusionPhaseKind Critic { get; } = new("critic"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(AutopilotObjectiveChangedStatus left, AutopilotObjectiveChangedStatus right) => !(left == right); + /// Critique-pattern revision phase. + public static FusionPhaseKind Revision { get; } = new("revision"); + + /// Follow-up phase continuing from the resolved model. + public static FusionPhaseKind FollowUp { get; } = new("follow_up"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(FusionPhaseKind left, FusionPhaseKind right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(FusionPhaseKind left, FusionPhaseKind right) => !(left == right); /// - public override bool Equals(object? obj) => obj is AutopilotObjectiveChangedStatus other && Equals(other); + public override bool Equals(object? obj) => obj is FusionPhaseKind other && Equals(other); /// - public bool Equals(AutopilotObjectiveChangedStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(FusionPhaseKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -9209,63 +12061,61 @@ public AutopilotObjectiveChangedStatus(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 AutopilotObjectiveChangedStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override FusionPhaseKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, AutopilotObjectiveChangedStatus value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, FusionPhaseKind value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AutopilotObjectiveChangedStatus)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FusionPhaseKind)); } } } -/// The session mode the agent is operating in. +/// Conversation scope in which a HydraFusion phase executes. +[Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct SessionMode : IEquatable +public readonly struct FusionConversationScope : 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 SessionMode(string value) + public FusionConversationScope(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 SessionMode Interactive { get; } = new("interactive"); - - /// The agent is preparing a plan before making changes. - public static SessionMode Plan { get; } = new("plan"); + /// Canonical root conversation history. + public static FusionConversationScope Root { get; } = new("root"); - /// The agent is working autonomously toward task completion. - public static SessionMode Autopilot { get; } = new("autopilot"); + /// Isolated read-only review history that does not enter the root conversation. + public static FusionConversationScope Review { get; } = new("review"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SessionMode left, SessionMode right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(FusionConversationScope left, FusionConversationScope right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SessionMode left, SessionMode right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(FusionConversationScope left, FusionConversationScope right) => !(left == right); /// - public override bool Equals(object? obj) => obj is SessionMode other && Equals(other); + public override bool Equals(object? obj) => obj is FusionConversationScope other && Equals(other); /// - public bool Equals(SessionMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(FusionConversationScope other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -9273,64 +12123,66 @@ public SessionMode(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 SessionMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override FusionConversationScope Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, SessionMode value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, FusionConversationScope value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FusionConversationScope)); } } } -/// Allow-all mode for the session. -[Experimental(Diagnostics.Experimental)] +/// The agent mode that was active when this message was sent. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct PermissionAllowAllMode : IEquatable +public readonly struct UserMessageAgentMode : 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 PermissionAllowAllMode(string value) + public UserMessageAgentMode(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 PermissionAllowAllMode Off { get; } = new("off"); + /// The agent is responding interactively to the user. + public static UserMessageAgentMode Interactive { get; } = new("interactive"); - /// Tool, path, and URL permission requests are automatically approved. - public static PermissionAllowAllMode On { get; } = new("on"); + /// The agent is preparing a plan before making changes. + public static UserMessageAgentMode Plan { get; } = new("plan"); + + /// The agent is working autonomously toward task completion. + public static UserMessageAgentMode Autopilot { get; } = new("autopilot"); - /// 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 PermissionAllowAllMode Auto { get; } = new("auto"); + /// The agent is in shell-focused UI mode. + public static UserMessageAgentMode Shell { get; } = new("shell"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(PermissionAllowAllMode left, PermissionAllowAllMode right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(UserMessageAgentMode left, UserMessageAgentMode right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(PermissionAllowAllMode left, PermissionAllowAllMode right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(UserMessageAgentMode left, UserMessageAgentMode right) => !(left == right); /// - public override bool Equals(object? obj) => obj is PermissionAllowAllMode other && Equals(other); + public override bool Equals(object? obj) => obj is UserMessageAgentMode other && Equals(other); /// - public bool Equals(PermissionAllowAllMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(UserMessageAgentMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -9338,63 +12190,60 @@ public PermissionAllowAllMode(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 PermissionAllowAllMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override UserMessageAgentMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, PermissionAllowAllMode value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, UserMessageAgentMode value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionAllowAllMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(UserMessageAgentMode)); } } } -/// The type of operation performed on the plan file. +/// Why the binary data is absent: it exceeded the inline size limit, or its asset was unavailable. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct PlanChangedOperation : IEquatable +public readonly struct OmittedBinaryOmittedReason : 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 PlanChangedOperation(string value) + public OmittedBinaryOmittedReason(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 plan file was created. - public static PlanChangedOperation Create { get; } = new("create"); - - /// The plan file was updated. - public static PlanChangedOperation Update { get; } = new("update"); + /// Bytes exceeded the session's inline size limit. + public static OmittedBinaryOmittedReason TooLarge { get; } = new("too_large"); - /// The plan file was deleted. - public static PlanChangedOperation Delete { get; } = new("delete"); + /// The referenced binary asset could not be found (e.g. a truncated log). + public static OmittedBinaryOmittedReason AssetUnavailable { get; } = new("asset_unavailable"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(PlanChangedOperation left, PlanChangedOperation right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(OmittedBinaryOmittedReason left, OmittedBinaryOmittedReason right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(PlanChangedOperation left, PlanChangedOperation right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(OmittedBinaryOmittedReason left, OmittedBinaryOmittedReason right) => !(left == right); /// - public override bool Equals(object? obj) => obj is PlanChangedOperation other && Equals(other); + public override bool Equals(object? obj) => obj is OmittedBinaryOmittedReason other && Equals(other); /// - public bool Equals(PlanChangedOperation other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(OmittedBinaryOmittedReason other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -9402,60 +12251,63 @@ public PlanChangedOperation(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 PlanChangedOperation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override OmittedBinaryOmittedReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, PlanChangedOperation value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, OmittedBinaryOmittedReason value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PlanChangedOperation)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(OmittedBinaryOmittedReason)); } } } -/// Whether the file was newly created or updated. +/// Type of GitHub reference. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct WorkspaceFileChangedOperation : IEquatable +public readonly struct AttachmentGitHubReferenceType : 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 WorkspaceFileChangedOperation(string value) + public AttachmentGitHubReferenceType(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 workspace file was created. - public static WorkspaceFileChangedOperation Create { get; } = new("create"); + /// GitHub issue reference. + public static AttachmentGitHubReferenceType Issue { get; } = new("issue"); - /// The workspace file was updated. - public static WorkspaceFileChangedOperation Update { get; } = new("update"); + /// GitHub pull request reference. + public static AttachmentGitHubReferenceType Pr { get; } = new("pr"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(WorkspaceFileChangedOperation left, WorkspaceFileChangedOperation right) => left.Equals(right); + /// GitHub discussion reference. + public static AttachmentGitHubReferenceType Discussion { get; } = new("discussion"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(WorkspaceFileChangedOperation left, WorkspaceFileChangedOperation right) => !(left == right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(AttachmentGitHubReferenceType left, AttachmentGitHubReferenceType right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AttachmentGitHubReferenceType left, AttachmentGitHubReferenceType right) => !(left == right); /// - public override bool Equals(object? obj) => obj is WorkspaceFileChangedOperation other && Equals(other); + public override bool Equals(object? obj) => obj is AttachmentGitHubReferenceType other && Equals(other); /// - public bool Equals(WorkspaceFileChangedOperation other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(AttachmentGitHubReferenceType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -9463,60 +12315,63 @@ public WorkspaceFileChangedOperation(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 WorkspaceFileChangedOperation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override AttachmentGitHubReferenceType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, WorkspaceFileChangedOperation value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, AttachmentGitHubReferenceType value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(WorkspaceFileChangedOperation)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AttachmentGitHubReferenceType)); } } } -/// Origin type of the session being handed off. +/// How this user message was delivered to the agentic loop, relative to whether the loop was already running. This is the timing axis only; the message's origin (human vs. system/command/schedule/skill/etc.) is carried separately by `source`. A system-injected message has a delivery too — e.g. a background-task notification waking an idle agent is `idle`, the same mechanism as a human starting a fresh turn. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct HandoffSourceType : IEquatable +public readonly struct UserMessageDelivery : 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 HandoffSourceType(string value) + public UserMessageDelivery(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 handoff originated from a remote session. - public static HandoffSourceType Remote { get; } = new("remote"); + /// Delivered while the loop was idle; starts its own run immediately (a human's fresh turn, or a system notification waking an idle agent). + public static UserMessageDelivery Idle { get; } = new("idle"); - /// The handoff originated from a local session. - public static HandoffSourceType Local { get; } = new("local"); + /// Injected into the current in-flight run while the agent was busy (immediate mode). + public static UserMessageDelivery Steering { get; } = new("steering"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(HandoffSourceType left, HandoffSourceType right) => left.Equals(right); + /// Enqueued while the agent was busy; processed as its own run afterward. + public static UserMessageDelivery Queued { get; } = new("queued"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(HandoffSourceType left, HandoffSourceType right) => !(left == right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(UserMessageDelivery left, UserMessageDelivery right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(UserMessageDelivery left, UserMessageDelivery right) => !(left == right); /// - public override bool Equals(object? obj) => obj is HandoffSourceType other && Equals(other); + public override bool Equals(object? obj) => obj is UserMessageDelivery other && Equals(other); /// - public bool Equals(HandoffSourceType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(UserMessageDelivery other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -9524,60 +12379,66 @@ public HandoffSourceType(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 HandoffSourceType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override UserMessageDelivery Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, HandoffSourceType value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, UserMessageDelivery value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(HandoffSourceType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(UserMessageDelivery)); } } } -/// Whether the session ended normally ("routine") or due to a crash/fatal error ("error"). +/// What the agent was doing when the user interrupted it. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ShutdownType : IEquatable +public readonly struct AgentInterruptedActivity : 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 ShutdownType(string value) + public AgentInterruptedActivity(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 session ended normally. - public static ShutdownType Routine { get; } = new("routine"); + /// A request to the model was open. + public static AgentInterruptedActivity ModelCall { get; } = new("model_call"); - /// The session ended because of a crash or fatal error. - public static ShutdownType Error { get; } = new("error"); + /// The turn was sleeping between retry attempts. + public static AgentInterruptedActivity RetryBackoff { get; } = new("retry_backoff"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(ShutdownType left, ShutdownType right) => left.Equals(right); + /// One or more tools were executing. + public static AgentInterruptedActivity ToolCall { get; } = new("tool_call"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(ShutdownType left, ShutdownType right) => !(left == right); + /// Background sub-agents were running while the main loop was idle. + public static AgentInterruptedActivity BackgroundAgent { get; } = new("background_agent"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(AgentInterruptedActivity left, AgentInterruptedActivity right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AgentInterruptedActivity left, AgentInterruptedActivity right) => !(left == right); /// - public override bool Equals(object? obj) => obj is ShutdownType other && Equals(other); + public override bool Equals(object? obj) => obj is AgentInterruptedActivity other && Equals(other); /// - public bool Equals(ShutdownType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(AgentInterruptedActivity other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -9585,69 +12446,60 @@ public ShutdownType(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 ShutdownType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override AgentInterruptedActivity Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, ShutdownType value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, AgentInterruptedActivity value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ShutdownType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentInterruptedActivity)); } } } -/// What initiated a conversation compaction. +/// Where the interruption landed relative to the first streamed token. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct CompactionTrigger : IEquatable +public readonly struct AgentInterruptedCancelPhase : 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 CompactionTrigger(string value) + public AgentInterruptedCancelPhase(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; - /// 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"); + /// No output had been produced when the request was cancelled. + public static AgentInterruptedCancelPhase PreFirstToken { get; } = new("pre_first_token"); - /// Compaction requested while switching to a model with a smaller context window. - public static CompactionTrigger ModelSwitch { get; } = new("model_switch"); + /// The response was already streaming when the request was cancelled. + public static AgentInterruptedCancelPhase MidStream { get; } = new("mid_stream"); - /// 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 equivalent. + public static bool operator ==(AgentInterruptedCancelPhase left, AgentInterruptedCancelPhase right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(CompactionTrigger left, CompactionTrigger right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AgentInterruptedCancelPhase left, AgentInterruptedCancelPhase right) => !(left == right); /// - public override bool Equals(object? obj) => obj is CompactionTrigger other && Equals(other); + public override bool Equals(object? obj) => obj is AgentInterruptedCancelPhase other && Equals(other); /// - public bool Equals(CompactionTrigger other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(AgentInterruptedCancelPhase other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -9655,63 +12507,60 @@ public CompactionTrigger(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 CompactionTrigger Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override AgentInterruptedCancelPhase 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) + public override void Write(Utf8JsonWriter writer, AgentInterruptedCancelPhase value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(CompactionTrigger)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentInterruptedCancelPhase)); } } } -/// Semantic result of evaluating a task completion request. +/// Transport used for a failed model call. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct TaskCompletionOutcome : IEquatable +public readonly struct ModelCallFailureTransport : 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 TaskCompletionOutcome(string value) + public ModelCallFailureTransport(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 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"); + /// HTTP transport, including SSE streams. + public static ModelCallFailureTransport Http { get; } = new("http"); - /// Completion cannot proceed without intervention; the active objective is paused when one is identified. - public static TaskCompletionOutcome Blocked { get; } = new("blocked"); + /// WebSocket transport. + public static ModelCallFailureTransport Websocket { get; } = new("websocket"); - /// 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 equivalent. + public static bool operator ==(ModelCallFailureTransport left, ModelCallFailureTransport right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(TaskCompletionOutcome left, TaskCompletionOutcome right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ModelCallFailureTransport left, ModelCallFailureTransport right) => !(left == right); /// - public override bool Equals(object? obj) => obj is TaskCompletionOutcome other && Equals(other); + public override bool Equals(object? obj) => obj is ModelCallFailureTransport other && Equals(other); /// - public bool Equals(TaskCompletionOutcome other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(ModelCallFailureTransport other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -9719,66 +12568,64 @@ public TaskCompletionOutcome(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 TaskCompletionOutcome Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override ModelCallFailureTransport 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) + public override void Write(Utf8JsonWriter writer, ModelCallFailureTransport value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskCompletionOutcome)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ModelCallFailureTransport)); } } } -/// The agent mode that was active when this message was sent. +/// Content-safe activity observed while a HydraFusion phase is running. +[Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct UserMessageAgentMode : IEquatable +public readonly struct FusionPhaseActivityKind : 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 UserMessageAgentMode(string value) + public FusionPhaseActivityKind(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 UserMessageAgentMode Interactive { get; } = new("interactive"); - - /// The agent is preparing a plan before making changes. - public static UserMessageAgentMode Plan { get; } = new("plan"); + /// The provider produced additional private output bytes. + public static FusionPhaseActivityKind ModelOutput { get; } = new("model_output"); - /// The agent is working autonomously toward task completion. - public static UserMessageAgentMode Autopilot { get; } = new("autopilot"); + /// A tool began executing inside the phase. + public static FusionPhaseActivityKind ToolStarted { get; } = new("tool_started"); - /// The agent is in shell-focused UI mode. - public static UserMessageAgentMode Shell { get; } = new("shell"); + /// A tool finished executing inside the phase. + public static FusionPhaseActivityKind ToolCompleted { get; } = new("tool_completed"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(UserMessageAgentMode left, UserMessageAgentMode right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(FusionPhaseActivityKind left, FusionPhaseActivityKind right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(UserMessageAgentMode left, UserMessageAgentMode right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(FusionPhaseActivityKind left, FusionPhaseActivityKind right) => !(left == right); /// - public override bool Equals(object? obj) => obj is UserMessageAgentMode other && Equals(other); + public override bool Equals(object? obj) => obj is FusionPhaseActivityKind other && Equals(other); /// - public bool Equals(UserMessageAgentMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(FusionPhaseActivityKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -9786,60 +12633,64 @@ public UserMessageAgentMode(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 UserMessageAgentMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override FusionPhaseActivityKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, UserMessageAgentMode value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, FusionPhaseActivityKind value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(UserMessageAgentMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FusionPhaseActivityKind)); } } } -/// Why the binary data is absent: it exceeded the inline size limit, or its asset was unavailable. +/// How a durable phase checkpoint contributes its exact message to canonical root history. +[Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct OmittedBinaryOmittedReason : IEquatable +public readonly struct FusionProjectionMode : 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 OmittedBinaryOmittedReason(string value) + public FusionProjectionMode(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; - /// Bytes exceeded the session's inline size limit. - public static OmittedBinaryOmittedReason TooLarge { get; } = new("too_large"); + /// Append the exact root message immediately. + public static FusionProjectionMode Append { get; } = new("append"); - /// The referenced binary asset could not be found (e.g. a truncated log). - public static OmittedBinaryOmittedReason AssetUnavailable { get; } = new("asset_unavailable"); + /// Hold a terminal message outside canonical history until the final commit selects it. + public static FusionProjectionMode Staged { get; } = new("staged"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(OmittedBinaryOmittedReason left, OmittedBinaryOmittedReason right) => left.Equals(right); + /// Do not project the checkpoint into root history. + public static FusionProjectionMode None { get; } = new("none"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(OmittedBinaryOmittedReason left, OmittedBinaryOmittedReason right) => !(left == right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(FusionProjectionMode left, FusionProjectionMode right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(FusionProjectionMode left, FusionProjectionMode right) => !(left == right); /// - public override bool Equals(object? obj) => obj is OmittedBinaryOmittedReason other && Equals(other); + public override bool Equals(object? obj) => obj is FusionProjectionMode other && Equals(other); /// - public bool Equals(OmittedBinaryOmittedReason other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(FusionProjectionMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -9847,63 +12698,64 @@ public OmittedBinaryOmittedReason(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 OmittedBinaryOmittedReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override FusionProjectionMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, OmittedBinaryOmittedReason value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, FusionProjectionMode value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(OmittedBinaryOmittedReason)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FusionProjectionMode)); } } } -/// Type of GitHub reference. +/// Durable outcome status of a HydraFusion phase. +[Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct AttachmentGitHubReferenceType : IEquatable +public readonly struct FusionPhaseStatus : 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 AttachmentGitHubReferenceType(string value) + public FusionPhaseStatus(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 AttachmentGitHubReferenceType Issue { get; } = new("issue"); + /// The phase completed successfully. + public static FusionPhaseStatus Succeeded { get; } = new("succeeded"); - /// GitHub pull request reference. - public static AttachmentGitHubReferenceType Pr { get; } = new("pr"); + /// The phase failed. + public static FusionPhaseStatus Failed { get; } = new("failed"); - /// GitHub discussion reference. - public static AttachmentGitHubReferenceType Discussion { get; } = new("discussion"); + /// The phase was cancelled. + public static FusionPhaseStatus Cancelled { get; } = new("cancelled"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(AttachmentGitHubReferenceType left, AttachmentGitHubReferenceType right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(FusionPhaseStatus left, FusionPhaseStatus right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(AttachmentGitHubReferenceType left, AttachmentGitHubReferenceType right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(FusionPhaseStatus left, FusionPhaseStatus right) => !(left == right); /// - public override bool Equals(object? obj) => obj is AttachmentGitHubReferenceType other && Equals(other); + public override bool Equals(object? obj) => obj is FusionPhaseStatus other && Equals(other); /// - public bool Equals(AttachmentGitHubReferenceType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(FusionPhaseStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -9911,63 +12763,60 @@ public AttachmentGitHubReferenceType(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 AttachmentGitHubReferenceType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override FusionPhaseStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, AttachmentGitHubReferenceType value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, FusionPhaseStatus value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AttachmentGitHubReferenceType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FusionPhaseStatus)); } } } -/// How this user message was delivered to the agentic loop, relative to whether the loop was already running. This is the timing axis only; the message's origin (human vs. system/command/schedule/skill/etc.) is carried separately by `source`. A system-injected message has a delivery too — e.g. a background-task notification waking an idle agent is `idle`, the same mechanism as a human starting a fresh turn. +/// Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct UserMessageDelivery : IEquatable +public readonly struct AssistantMessageToolRequestType : 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 UserMessageDelivery(string value) + public AssistantMessageToolRequestType(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; - /// Delivered while the loop was idle; starts its own run immediately (a human's fresh turn, or a system notification waking an idle agent). - public static UserMessageDelivery Idle { get; } = new("idle"); - - /// Injected into the current in-flight run while the agent was busy (immediate mode). - public static UserMessageDelivery Steering { get; } = new("steering"); + /// Standard function-style tool call. + public static AssistantMessageToolRequestType Function { get; } = new("function"); - /// Enqueued while the agent was busy; processed as its own run afterward. - public static UserMessageDelivery Queued { get; } = new("queued"); + /// Custom grammar-based tool call. + public static AssistantMessageToolRequestType Custom { get; } = new("custom"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(UserMessageDelivery left, UserMessageDelivery right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(AssistantMessageToolRequestType left, AssistantMessageToolRequestType right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(UserMessageDelivery left, UserMessageDelivery right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AssistantMessageToolRequestType left, AssistantMessageToolRequestType right) => !(left == right); /// - public override bool Equals(object? obj) => obj is UserMessageDelivery other && Equals(other); + public override bool Equals(object? obj) => obj is AssistantMessageToolRequestType other && Equals(other); /// - public bool Equals(UserMessageDelivery other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(AssistantMessageToolRequestType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -9975,60 +12824,64 @@ public UserMessageDelivery(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 UserMessageDelivery Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override AssistantMessageToolRequestType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, UserMessageDelivery value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, AssistantMessageToolRequestType value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(UserMessageDelivery)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AssistantMessageToolRequestType)); } } } -/// Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. +/// The system that produced a citation. +[Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct AssistantMessageToolRequestType : IEquatable +public readonly struct CitationProvider : 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 AssistantMessageToolRequestType(string value) + public CitationProvider(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 function-style tool call. - public static AssistantMessageToolRequestType Function { get; } = new("function"); + /// Citation produced by an Anthropic (Claude) model response. + public static CitationProvider Anthropic { get; } = new("anthropic"); - /// Custom grammar-based tool call. - public static AssistantMessageToolRequestType Custom { get; } = new("custom"); + /// Citation produced by an OpenAI model response. + public static CitationProvider Openai { get; } = new("openai"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(AssistantMessageToolRequestType left, AssistantMessageToolRequestType right) => left.Equals(right); + /// Citation synthesized client-side by the runtime from tool output. + public static CitationProvider Client { get; } = new("client"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(AssistantMessageToolRequestType left, AssistantMessageToolRequestType right) => !(left == right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(CitationProvider left, CitationProvider right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(CitationProvider left, CitationProvider right) => !(left == right); /// - public override bool Equals(object? obj) => obj is AssistantMessageToolRequestType other && Equals(other); + public override bool Equals(object? obj) => obj is CitationProvider other && Equals(other); /// - public bool Equals(AssistantMessageToolRequestType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(CitationProvider other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -10036,64 +12889,57 @@ public AssistantMessageToolRequestType(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 AssistantMessageToolRequestType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override CitationProvider Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, AssistantMessageToolRequestType value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, CitationProvider value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AssistantMessageToolRequestType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(CitationProvider)); } } } -/// The system that produced a citation. -[Experimental(Diagnostics.Experimental)] +/// Hosted program caller type. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct CitationProvider : IEquatable +public readonly struct AssistantMessageToolRequestCallerType : 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 CitationProvider(string value) + public AssistantMessageToolRequestCallerType(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; - /// Citation produced by an Anthropic (Claude) model response. - public static CitationProvider Anthropic { get; } = new("anthropic"); - - /// Citation produced by an OpenAI model response. - public static CitationProvider Openai { get; } = new("openai"); - - /// Citation synthesized client-side by the runtime from tool output. - public static CitationProvider Client { get; } = new("client"); + /// Gets the program value. + public static AssistantMessageToolRequestCallerType Program { get; } = new("program"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(CitationProvider left, CitationProvider right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(AssistantMessageToolRequestCallerType left, AssistantMessageToolRequestCallerType right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(CitationProvider left, CitationProvider right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AssistantMessageToolRequestCallerType left, AssistantMessageToolRequestCallerType right) => !(left == right); /// - public override bool Equals(object? obj) => obj is CitationProvider other && Equals(other); + public override bool Equals(object? obj) => obj is AssistantMessageToolRequestCallerType other && Equals(other); /// - public bool Equals(CitationProvider other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(AssistantMessageToolRequestCallerType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -10101,20 +12947,20 @@ public CitationProvider(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 CitationProvider Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override AssistantMessageToolRequestCallerType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, CitationProvider value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, AssistantMessageToolRequestCallerType value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(CitationProvider)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AssistantMessageToolRequestCallerType)); } } } @@ -10186,6 +13032,67 @@ public override void Write(Utf8JsonWriter writer, AssistantUsageApiEndpoint valu } } +/// Transport used for a successful model call. +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct AssistantUsageTransport : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public AssistantUsageTransport(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// HTTP transport, including SSE streams. + public static AssistantUsageTransport Http { get; } = new("http"); + + /// WebSocket transport. + public static AssistantUsageTransport Websocket { get; } = new("websocket"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(AssistantUsageTransport left, AssistantUsageTransport right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AssistantUsageTransport left, AssistantUsageTransport right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is AssistantUsageTransport other && Equals(other); + + /// + public bool Equals(AssistantUsageTransport 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 AssistantUsageTransport Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, AssistantUsageTransport value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AssistantUsageTransport)); + } + } +} + /// For HTTP 400 failures only: whether the response carried a structured CAPI error envelope (structured_error, a deterministic validation failure) or no error body (bodyless, the transient gateway/proxy signature). Absent for non-400 failures. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] @@ -10372,42 +13279,48 @@ public override void Write(Utf8JsonWriter writer, ModelCallFailureSource value, } } -/// Transport used for a failed model call. +/// Final outcome of one logical model dispatch after response acceptance processing. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ModelCallFailureTransport : IEquatable +public readonly struct ModelCallFinishedOutcome : 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 ModelCallFailureTransport(string value) + public ModelCallFinishedOutcome(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 transport, including SSE streams. - public static ModelCallFailureTransport Http { get; } = new("http"); + /// The provider response was accepted for continued agent processing. + public static ModelCallFinishedOutcome Success { get; } = new("success"); - /// WebSocket transport. - public static ModelCallFailureTransport Websocket { get; } = new("websocket"); + /// The dispatch ended with a provider or transport error. + public static ModelCallFinishedOutcome Error { get; } = new("error"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(ModelCallFailureTransport left, ModelCallFailureTransport right) => left.Equals(right); + /// The dispatch was cancelled before an accepted response was produced. + public static ModelCallFinishedOutcome Cancelled { get; } = new("cancelled"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(ModelCallFailureTransport left, ModelCallFailureTransport right) => !(left == right); + /// The provider response was rejected during post-response acceptance processing. + public static ModelCallFinishedOutcome Rejected { get; } = new("rejected"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ModelCallFinishedOutcome left, ModelCallFinishedOutcome right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ModelCallFinishedOutcome left, ModelCallFinishedOutcome right) => !(left == right); /// - public override bool Equals(object? obj) => obj is ModelCallFailureTransport other && Equals(other); + public override bool Equals(object? obj) => obj is ModelCallFinishedOutcome other && Equals(other); /// - public bool Equals(ModelCallFailureTransport other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(ModelCallFinishedOutcome other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -10415,20 +13328,20 @@ public ModelCallFailureTransport(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 ModelCallFailureTransport Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override ModelCallFinishedOutcome Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, ModelCallFailureTransport value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, ModelCallFinishedOutcome value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ModelCallFailureTransport)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ModelCallFinishedOutcome)); } } } @@ -11167,55 +14080,253 @@ public SystemNotificationFactoryCompletedStatus(string value) public sealed class Converter : JsonConverter { /// - public override SystemNotificationFactoryCompletedStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + 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)); + } + } +} + +/// Advisory recommendation the runtime attaches to a permission request whose origin it can vouch for by construction. Unlike the auto-approval judge this does not depend on auto mode and does not evaluate what the tool call does; its absence simply means the runtime has no opinion and the request follows the host's normal approval flow. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct PermissionRecommendation : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public PermissionRecommendation(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The runtime vouches for the request's origin and recommends approving it without prompting. The host still owns the decision and may deny it; deny rules, managed policy, and the auto-approval safety judge all outrank this recommendation. + public static PermissionRecommendation Approve { get; } = new("approve"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(PermissionRecommendation left, PermissionRecommendation right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(PermissionRecommendation left, PermissionRecommendation right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is PermissionRecommendation other && Equals(other); + + /// + public bool Equals(PermissionRecommendation 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 PermissionRecommendation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, PermissionRecommendation value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionRecommendation)); + } + } +} + +/// Whether this is a store or vote memory operation. +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct PermissionRequestMemoryAction : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public PermissionRequestMemoryAction(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Store a new memory. + public static PermissionRequestMemoryAction Store { get; } = new("store"); + + /// Vote on an existing memory. + public static PermissionRequestMemoryAction Vote { get; } = new("vote"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(PermissionRequestMemoryAction left, PermissionRequestMemoryAction right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(PermissionRequestMemoryAction left, PermissionRequestMemoryAction right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is PermissionRequestMemoryAction other && Equals(other); + + /// + public bool Equals(PermissionRequestMemoryAction 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 PermissionRequestMemoryAction Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, PermissionRequestMemoryAction value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionRequestMemoryAction)); + } + } +} + +/// Why the assisted-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 AssistedApprovalJudgeFailureReason : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public AssistedApprovalJudgeFailureReason(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 AssistedApprovalJudgeFailureReason Timeout { get; } = new("timeout"); + + /// The judge model call was cancelled before it returned. + public static AssistedApprovalJudgeFailureReason Abort { get; } = new("abort"); + + /// The judge model call completed but returned no content. + public static AssistedApprovalJudgeFailureReason EmptyResponse { get; } = new("empty_response"); + + /// The judge model call failed (for example a transport, authentication, or rate-limit error). + public static AssistedApprovalJudgeFailureReason ModelError { get; } = new("model_error"); + + /// The judge model replied, but the reply carried no ALLOW/DENY verdict. + public static AssistedApprovalJudgeFailureReason ParseError { get; } = new("parse_error"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(AssistedApprovalJudgeFailureReason left, AssistedApprovalJudgeFailureReason right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AssistedApprovalJudgeFailureReason left, AssistedApprovalJudgeFailureReason right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is AssistedApprovalJudgeFailureReason other && Equals(other); + + /// + public bool Equals(AssistedApprovalJudgeFailureReason 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 AssistedApprovalJudgeFailureReason 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) + public override void Write(Utf8JsonWriter writer, AssistedApprovalJudgeFailureReason value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SystemNotificationFactoryCompletedStatus)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AssistedApprovalJudgeFailureReason)); } } } -/// Whether this is a store or vote memory operation. +/// Outcome of the assisted-approval safety judge for a permission request. Present only in assisted mode; its absence means the judge did not evaluate the request. +[Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct PermissionRequestMemoryAction : IEquatable +public readonly struct AssistedApprovalRecommendation : 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 PermissionRequestMemoryAction(string value) + public AssistedApprovalRecommendation(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; - /// Store a new memory. - public static PermissionRequestMemoryAction Store { get; } = new("store"); + /// The judge evaluated the request and recommends automatically approving it. + public static AssistedApprovalRecommendation Approve { get; } = new("approve"); - /// Vote on an existing memory. - public static PermissionRequestMemoryAction Vote { get; } = new("vote"); + /// The judge evaluated the request and does not recommend automatically approving it; explicit approval is required. Whether that means prompting, denying, or something else is the consumer's decision. + public static AssistedApprovalRecommendation RequireApproval { get; } = new("requireApproval"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(PermissionRequestMemoryAction left, PermissionRequestMemoryAction right) => left.Equals(right); + /// Assisted mode is enabled, but this request category is never automatically approvable (for example, sandbox-bypass requests), so the judge was not consulted. + public static AssistedApprovalRecommendation Excluded { get; } = new("excluded"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(PermissionRequestMemoryAction left, PermissionRequestMemoryAction right) => !(left == right); + /// The judge was consulted but did not return a usable recommendation, so the request requires explicit approval. + public static AssistedApprovalRecommendation Error { get; } = new("error"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(AssistedApprovalRecommendation left, AssistedApprovalRecommendation right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AssistedApprovalRecommendation left, AssistedApprovalRecommendation right) => !(left == right); /// - public override bool Equals(object? obj) => obj is PermissionRequestMemoryAction other && Equals(other); + public override bool Equals(object? obj) => obj is AssistedApprovalRecommendation other && Equals(other); /// - public bool Equals(PermissionRequestMemoryAction other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(AssistedApprovalRecommendation other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -11223,20 +14334,20 @@ public PermissionRequestMemoryAction(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 PermissionRequestMemoryAction Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override AssistedApprovalRecommendation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, PermissionRequestMemoryAction value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, AssistedApprovalRecommendation value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionRequestMemoryAction)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AssistedApprovalRecommendation)); } } } @@ -11302,113 +14413,42 @@ 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)] +/// Scope of a stored memory. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct AutoApprovalJudgeFailureReason : IEquatable +public readonly struct PermissionRequestMemoryScope : 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 AutoApprovalJudgeFailureReason(string value) + public PermissionRequestMemoryScope(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 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"); + /// Store the memory for the current repository. + public static PermissionRequestMemoryScope Repository { get; } = new("repository"); - /// The judge model replied, but the reply carried no ALLOW/DENY verdict. - public static AutoApprovalJudgeFailureReason ParseError { get; } = new("parse_error"); + /// Store the memory for the current user. + public static PermissionRequestMemoryScope User { get; } = new("user"); - /// 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 equivalent. + public static bool operator ==(PermissionRequestMemoryScope left, PermissionRequestMemoryScope right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(AutoApprovalJudgeFailureReason left, AutoApprovalJudgeFailureReason right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(PermissionRequestMemoryScope left, PermissionRequestMemoryScope right) => !(left == right); /// - public override bool Equals(object? obj) => obj is AutoApprovalJudgeFailureReason other && Equals(other); + public override bool Equals(object? obj) => obj is PermissionRequestMemoryScope other && Equals(other); /// - public bool Equals(AutoApprovalJudgeFailureReason other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(PermissionRequestMemoryScope other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -11416,67 +14456,60 @@ public AutoApprovalJudgeFailureReason(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 AutoApprovalJudgeFailureReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override PermissionRequestMemoryScope 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) + public override void Write(Utf8JsonWriter writer, PermissionRequestMemoryScope value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AutoApprovalJudgeFailureReason)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionRequestMemoryScope)); } } } -/// 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)] +/// Operation gated by a factory permission request. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct AutoApprovalRecommendation : IEquatable +public readonly struct FactoryPermissionOperation : 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 AutoApprovalRecommendation(string value) + public FactoryPermissionOperation(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 judge evaluated the request and recommends automatically approving it. - public static AutoApprovalRecommendation Approve { get; } = new("approve"); - - /// The judge evaluated the request and does not recommend auto-approving it; explicit approval is required. Whether that means prompting, denying, or something else is the consumer's decision. - public static AutoApprovalRecommendation RequireApproval { get; } = new("requireApproval"); - - /// Auto mode is enabled, but this request category is never auto-approvable (for example, sandbox-bypass requests), so the judge was not consulted. - public static AutoApprovalRecommendation Excluded { get; } = new("excluded"); + /// Running a registered factory, which spends subagents, active time, and AI credits under the approved limits. + public static FactoryPermissionOperation Run { get; } = new("run"); - /// The judge was consulted but did not return a usable recommendation, so the request requires explicit approval. - public static AutoApprovalRecommendation Error { get; } = new("error"); + /// 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 ==(AutoApprovalRecommendation left, AutoApprovalRecommendation right) => left.Equals(right); + /// 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 !=(AutoApprovalRecommendation left, AutoApprovalRecommendation right) => !(left == 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 AutoApprovalRecommendation other && Equals(other); + public override bool Equals(object? obj) => obj is FactoryPermissionOperation other && Equals(other); /// - public bool Equals(AutoApprovalRecommendation other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(FactoryPermissionOperation other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -11484,20 +14517,20 @@ public AutoApprovalRecommendation(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 AutoApprovalRecommendation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override FactoryPermissionOperation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, AutoApprovalRecommendation value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, FactoryPermissionOperation value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AutoApprovalRecommendation)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FactoryPermissionOperation)); } } } @@ -11947,6 +14980,77 @@ public override void Write(Utf8JsonWriter writer, McpHeadersRefreshCompletedOutc } } +/// Lifecycle phase for a Rust-owned ephemeral query stream. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct UIEphemeralQueryPhase : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public UIEphemeralQueryPhase(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The ephemeral query stream has begun. + public static UIEphemeralQueryPhase Started { get; } = new("started"); + + /// A partial result chunk was produced by the stream. + public static UIEphemeralQueryPhase Chunk { get; } = new("chunk"); + + /// The ephemeral query stream finished successfully. + public static UIEphemeralQueryPhase Completed { get; } = new("completed"); + + /// The ephemeral query stream ended with an error. + public static UIEphemeralQueryPhase Failed { get; } = new("failed"); + + /// The ephemeral query stream was cancelled before completing. + public static UIEphemeralQueryPhase Aborted { get; } = new("aborted"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(UIEphemeralQueryPhase left, UIEphemeralQueryPhase right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(UIEphemeralQueryPhase left, UIEphemeralQueryPhase right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is UIEphemeralQueryPhase other && Equals(other); + + /// + public bool Equals(UIEphemeralQueryPhase 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 UIEphemeralQueryPhase Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, UIEphemeralQueryPhase value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(UIEphemeralQueryPhase)); + } + } +} + /// The user's auto-mode-switch choice. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] @@ -12142,7 +15246,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 @@ -12161,13 +15265,22 @@ 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"); + + /// A policy helper registered by device or server policy contributed. Device registration takes priority when present. + public static ManagedSettingsResolvedSource PolicyHelper { get; } = new("policyHelper"); + + /// More than one channel contributed. Ordinary keys resolve device over server over policy helper 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. @@ -12283,14 +15396,14 @@ public ManagedSettingsEnforcedEscalation(string value) /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Full allow-all ("/allow-all on") permissions — auto-approving tools, paths, and URLs. + /// Full allow-all permissions — automatically approving tools, paths, and URLs. public static ManagedSettingsEnforcedEscalation AllowAll { get; } = new("allow_all"); - /// Auto-approval of all tool permission requests. + /// Automatic approval of all tool permission requests. public static ManagedSettingsEnforcedEscalation ApproveAll { get; } = new("approve_all"); - /// Advisory auto-approval ("/allow-all auto") mode — keeps normal prompt paths and adds LLM-advised approval, distinct from full allow-all. - public static ManagedSettingsEnforcedEscalation AutoApproval { get; } = new("auto_approval"); + /// Assisted mode — keeps normal prompt paths and adds an LLM recommendation, distinct from allow-all. + public static ManagedSettingsEnforcedEscalation AssistedApproval { get; } = new("assisted_approval"); /// Unrestricted filesystem access outside the session's allowed directories. public static ManagedSettingsEnforcedEscalation UnrestrictedPaths { get; } = new("unrestricted_paths"); @@ -12298,6 +15411,9 @@ public ManagedSettingsEnforcedEscalation(string value) /// Unrestricted URL fetch access. public static ManagedSettingsEnforcedEscalation UnrestrictedUrls { get; } = new("unrestricted_urls"); + /// A server-wide MCP "Always Allow" (or `--allow-tool <server>`) blanket that would auto-approve every tool from an MCP server. Capped to per-tool approval; each tool still prompts. + public static ManagedSettingsEnforcedEscalation ServerWideMcpApproval { get; } = new("server_wide_mcp_approval"); + /// Returns a value indicating whether two instances are equivalent. public static bool operator ==(ManagedSettingsEnforcedEscalation left, ManagedSettingsEnforcedEscalation right) => left.Equals(right); @@ -12401,7 +15517,74 @@ public override void Write(Utf8JsonWriter writer, ExitPlanModeAction value, Json } } -/// Source location type (e.g., project, personal-copilot, plugin, builtin). +/// Terminal status a factory run committed. A settled run is never `pending` or `running`, so those two members of the run-status domain are deliberately absent. +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct FactoryRunSettledStatus : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public FactoryRunSettledStatus(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The factory body resolved and its result was committed. + public static FactoryRunSettledStatus Completed { get; } = new("completed"); + + /// The run was stopped by a limit, an approval refusal or another policy decision. + public static FactoryRunSettledStatus Halted { get; } = new("halted"); + + /// The run was cancelled by its caller or by session disposal. + public static FactoryRunSettledStatus Cancelled { get; } = new("cancelled"); + + /// The run failed, with `failureType` carrying the class when it has one. + public static FactoryRunSettledStatus Error { get; } = new("error"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(FactoryRunSettledStatus left, FactoryRunSettledStatus right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(FactoryRunSettledStatus left, FactoryRunSettledStatus right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is FactoryRunSettledStatus other && Equals(other); + + /// + public bool Equals(FactoryRunSettledStatus 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 FactoryRunSettledStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, FactoryRunSettledStatus value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FactoryRunSettledStatus)); + } + } +} + +/// Source location type (e.g., project, personal-copilot, plugin, builtin, sdk). [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] public readonly struct SkillSource : IEquatable @@ -12441,6 +15624,9 @@ public SkillSource(string value) /// Skill bundled with the runtime. public static SkillSource Builtin { get; } = new("builtin"); + /// Pathless skill supplied lazily by an SDK skill provider. + public static SkillSource Sdk { get; } = new("sdk"); + /// Returns a value indicating whether two instances are equivalent. public static bool operator ==(SkillSource left, SkillSource right) => left.Equals(right); @@ -12477,6 +15663,67 @@ public override void Write(Utf8JsonWriter writer, SkillSource value, JsonSeriali } } +/// Whether configured models are advisory preferences or required constraints. +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct AgentModelPolicy : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public AgentModelPolicy(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Treat the authored models as advisory preferences that callers may override. + public static AgentModelPolicy Preferred { get; } = new("preferred"); + + /// Require subagent execution to use one of the authored models. + public static AgentModelPolicy Required { get; } = new("required"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(AgentModelPolicy left, AgentModelPolicy right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AgentModelPolicy left, AgentModelPolicy right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is AgentModelPolicy other && Equals(other); + + /// + public bool Equals(AgentModelPolicy 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 AgentModelPolicy Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, AgentModelPolicy value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentModelPolicy)); + } + } +} + /// Configuration source: user, workspace, plugin, or builtin. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] @@ -12828,6 +16075,16 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] [JsonSerializable(typeof(AbortData))] [JsonSerializable(typeof(AbortEvent))] +[JsonSerializable(typeof(AgentInterruptedData))] +[JsonSerializable(typeof(AgentInterruptedEvent))] +[JsonSerializable(typeof(AssistantFusionPhaseActivityData))] +[JsonSerializable(typeof(AssistantFusionPhaseActivityEvent))] +[JsonSerializable(typeof(AssistantFusionPhaseCompletedData))] +[JsonSerializable(typeof(AssistantFusionPhaseCompletedEvent))] +[JsonSerializable(typeof(AssistantFusionPhaseFailedData))] +[JsonSerializable(typeof(AssistantFusionPhaseFailedEvent))] +[JsonSerializable(typeof(AssistantFusionPhaseStartedData))] +[JsonSerializable(typeof(AssistantFusionPhaseStartedEvent))] [JsonSerializable(typeof(AssistantIdleData))] [JsonSerializable(typeof(AssistantIdleEvent))] [JsonSerializable(typeof(AssistantIntentData))] @@ -12836,10 +16093,12 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(AssistantMessageDeltaData))] [JsonSerializable(typeof(AssistantMessageDeltaEvent))] [JsonSerializable(typeof(AssistantMessageEvent))] +[JsonSerializable(typeof(AssistantMessageReasoningBlocks))] [JsonSerializable(typeof(AssistantMessageServerTools))] [JsonSerializable(typeof(AssistantMessageStartData))] [JsonSerializable(typeof(AssistantMessageStartEvent))] [JsonSerializable(typeof(AssistantMessageToolRequest))] +[JsonSerializable(typeof(AssistantMessageToolRequestCaller))] [JsonSerializable(typeof(AssistantReasoningData))] [JsonSerializable(typeof(AssistantReasoningDeltaData))] [JsonSerializable(typeof(AssistantReasoningDeltaEvent))] @@ -12914,6 +16173,8 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(CompactionCompleteCompactionTokensUsed))] [JsonSerializable(typeof(CompactionCompleteCompactionTokensUsedCopilotUsage))] [JsonSerializable(typeof(CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail))] +[JsonSerializable(typeof(CompletionReceiptEventRange))] +[JsonSerializable(typeof(CompletionReceiptFinalTool))] [JsonSerializable(typeof(CustomAgentsUpdatedAgent))] [JsonSerializable(typeof(ElicitationCompletedData))] [JsonSerializable(typeof(ElicitationCompletedEvent))] @@ -12932,8 +16193,18 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(ExternalToolRequestedData))] [JsonSerializable(typeof(ExternalToolRequestedEvent))] [JsonSerializable(typeof(FactoryPermissionPhase))] +[JsonSerializable(typeof(FactoryRunSettledData))] +[JsonSerializable(typeof(FactoryRunSettledEvent))] +[JsonSerializable(typeof(FactoryRunStartedData))] +[JsonSerializable(typeof(FactoryRunStartedEvent))] [JsonSerializable(typeof(FactoryRunUpdatedData))] [JsonSerializable(typeof(FactoryRunUpdatedEvent))] +[JsonSerializable(typeof(FusionAttribution))] +[JsonSerializable(typeof(FusionFollowUpRecommendation))] +[JsonSerializable(typeof(FusionPhasePlanStep))] +[JsonSerializable(typeof(FusionPhaseUsage))] +[JsonSerializable(typeof(FusionScores))] +[JsonSerializable(typeof(FusionStagedTerminal))] [JsonSerializable(typeof(GitHubMcpToolConfig))] [JsonSerializable(typeof(GitHubRepoRef))] [JsonSerializable(typeof(HandoffRepository))] @@ -12971,17 +16242,20 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(ModelCallFailureData))] [JsonSerializable(typeof(ModelCallFailureEvent))] [JsonSerializable(typeof(ModelCallFailureRequestFingerprint))] +[JsonSerializable(typeof(ModelCallFinishedData))] +[JsonSerializable(typeof(ModelCallFinishedEvent))] [JsonSerializable(typeof(ModelCallStartData))] [JsonSerializable(typeof(ModelCallStartEvent))] [JsonSerializable(typeof(OmittedBinaryResult))] [JsonSerializable(typeof(PendingMessagesModifiedData))] [JsonSerializable(typeof(PendingMessagesModifiedEvent))] -[JsonSerializable(typeof(PermissionAutoApproval))] +[JsonSerializable(typeof(PermissionAssistedApproval))] [JsonSerializable(typeof(PermissionCompletedData))] [JsonSerializable(typeof(PermissionCompletedEvent))] [JsonSerializable(typeof(PermissionPromptRequest))] [JsonSerializable(typeof(PermissionPromptRequestCommands))] [JsonSerializable(typeof(PermissionPromptRequestCustomTool))] +[JsonSerializable(typeof(PermissionPromptRequestExtensionEnvAccess))] [JsonSerializable(typeof(PermissionPromptRequestExtensionManagement))] [JsonSerializable(typeof(PermissionPromptRequestExtensionPermissionAccess))] [JsonSerializable(typeof(PermissionPromptRequestFactory))] @@ -12994,6 +16268,7 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(PermissionPromptRequestWrite))] [JsonSerializable(typeof(PermissionRequest))] [JsonSerializable(typeof(PermissionRequestCustomTool))] +[JsonSerializable(typeof(PermissionRequestExtensionEnvAccess))] [JsonSerializable(typeof(PermissionRequestExtensionManagement))] [JsonSerializable(typeof(PermissionRequestExtensionPermissionAccess))] [JsonSerializable(typeof(PermissionRequestFactory))] @@ -13022,10 +16297,14 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(PermissionRule))] [JsonSerializable(typeof(PersistedBinaryImage))] [JsonSerializable(typeof(PersistedBinaryResult))] +[JsonSerializable(typeof(PromptCacheBreakData))] +[JsonSerializable(typeof(PromptCacheBreakEvent))] [JsonSerializable(typeof(SamplingCompletedData))] [JsonSerializable(typeof(SamplingCompletedEvent))] [JsonSerializable(typeof(SamplingRequestedData))] [JsonSerializable(typeof(SamplingRequestedEvent))] +[JsonSerializable(typeof(SandboxDecisionData))] +[JsonSerializable(typeof(SandboxDecisionEvent))] [JsonSerializable(typeof(SessionAutoModeResolvedData))] [JsonSerializable(typeof(SessionAutoModeResolvedEvent))] [JsonSerializable(typeof(SessionAutopilotObjectiveChangedData))] @@ -13050,6 +16329,8 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(SessionCompactionCompleteEvent))] [JsonSerializable(typeof(SessionCompactionStartData))] [JsonSerializable(typeof(SessionCompactionStartEvent))] +[JsonSerializable(typeof(SessionCompletionReceiptData))] +[JsonSerializable(typeof(SessionCompletionReceiptEvent))] [JsonSerializable(typeof(SessionContextChangedData))] [JsonSerializable(typeof(SessionContextChangedEvent))] [JsonSerializable(typeof(SessionContextClearedData))] @@ -13065,6 +16346,14 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(SessionExtensionsAttachmentsPushedEvent))] [JsonSerializable(typeof(SessionExtensionsLoadedData))] [JsonSerializable(typeof(SessionExtensionsLoadedEvent))] +[JsonSerializable(typeof(SessionFusionCompletedData))] +[JsonSerializable(typeof(SessionFusionCompletedEvent))] +[JsonSerializable(typeof(SessionFusionResolvedData))] +[JsonSerializable(typeof(SessionFusionResolvedEvent))] +[JsonSerializable(typeof(SessionFusionRouteFailedData))] +[JsonSerializable(typeof(SessionFusionRouteFailedEvent))] +[JsonSerializable(typeof(SessionFusionRouteStartedData))] +[JsonSerializable(typeof(SessionFusionRouteStartedEvent))] [JsonSerializable(typeof(SessionHandoffData))] [JsonSerializable(typeof(SessionHandoffEvent))] [JsonSerializable(typeof(SessionIdleData))] @@ -13087,6 +16376,8 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(SessionMcpServersLoadedEvent))] [JsonSerializable(typeof(SessionModeChangedData))] [JsonSerializable(typeof(SessionModeChangedEvent))] +[JsonSerializable(typeof(SessionModeNoticeDeliveredData))] +[JsonSerializable(typeof(SessionModeNoticeDeliveredEvent))] [JsonSerializable(typeof(SessionModelChangeData))] [JsonSerializable(typeof(SessionModelChangeEvent))] [JsonSerializable(typeof(SessionPermissionsChangedData))] @@ -13131,6 +16422,7 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(SessionWarningEvent))] [JsonSerializable(typeof(SessionWorkspaceFileChangedData))] [JsonSerializable(typeof(SessionWorkspaceFileChangedEvent))] +[JsonSerializable(typeof(ShutdownAgentMetric))] [JsonSerializable(typeof(ShutdownCodeChanges))] [JsonSerializable(typeof(ShutdownModelMetric))] [JsonSerializable(typeof(ShutdownModelMetricRequests))] @@ -13142,6 +16434,8 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(SkillsLoadedSkill))] [JsonSerializable(typeof(SubagentCompletedData))] [JsonSerializable(typeof(SubagentCompletedEvent))] +[JsonSerializable(typeof(SubagentConfiguredData))] +[JsonSerializable(typeof(SubagentConfiguredEvent))] [JsonSerializable(typeof(SubagentDeselectedData))] [JsonSerializable(typeof(SubagentDeselectedEvent))] [JsonSerializable(typeof(SubagentFailedData))] @@ -13204,6 +16498,8 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(ToolSearchActivatedEvent))] [JsonSerializable(typeof(ToolUserRequestedData))] [JsonSerializable(typeof(ToolUserRequestedEvent))] +[JsonSerializable(typeof(UiEphemeralQueryData))] +[JsonSerializable(typeof(UiEphemeralQueryEvent))] [JsonSerializable(typeof(UsageCheckpointModelCacheState))] [JsonSerializable(typeof(UserInputCompletedData))] [JsonSerializable(typeof(UserInputCompletedEvent))] @@ -13214,6 +16510,7 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(UserToolSessionApproval))] [JsonSerializable(typeof(UserToolSessionApprovalCommands))] [JsonSerializable(typeof(UserToolSessionApprovalCustomTool))] +[JsonSerializable(typeof(UserToolSessionApprovalExtensionEnvAccess))] [JsonSerializable(typeof(UserToolSessionApprovalExtensionManagement))] [JsonSerializable(typeof(UserToolSessionApprovalExtensionPermissionAccess))] [JsonSerializable(typeof(UserToolSessionApprovalFactory))] diff --git a/dotnet/src/GitHub.Copilot.SDK.csproj b/dotnet/src/GitHub.Copilot.SDK.csproj index f48fb802d7..e5a2d9fb98 100644 --- a/dotnet/src/GitHub.Copilot.SDK.csproj +++ b/dotnet/src/GitHub.Copilot.SDK.csproj @@ -63,10 +63,10 @@ - + - + diff --git a/dotnet/src/GitHubTokenProvider.cs b/dotnet/src/GitHubTokenProvider.cs new file mode 100644 index 0000000000..3a79d09662 --- /dev/null +++ b/dotnet/src/GitHubTokenProvider.cs @@ -0,0 +1,77 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +using System.Diagnostics.CodeAnalysis; + +namespace GitHub.Copilot; + +/// Why the runtime is requesting a GitHub token. +[Experimental(Diagnostics.Experimental)] +public enum GitHubTokenRequestReason +{ + /// The session needs its initial token. + Initial, + + /// The session needs a refreshed token. + Refresh, +} + +/// Arguments passed to a session-scoped GitHub token provider. +[Experimental(Diagnostics.Experimental)] +public sealed class GitHubTokenProviderArgs +{ + /// Gets the effective GitHub host for which a token is needed. + public required string Host { get; init; } + + /// + /// Gets the session receiving the token, or before a + /// cloud session has been assigned an identifier. + /// + public string? SessionId { get; init; } + + /// Gets whether the runtime needs an initial or refreshed token. + public required GitHubTokenRequestReason Reason { get; init; } +} + +/// A GitHub access token returned by a session-scoped provider. +[Experimental(Diagnostics.Experimental)] +public sealed class GitHubToken +{ + /// Gets or sets the GitHub access token. + public required string AccessToken { get; set; } + + /// Gets or sets the OAuth token type. The runtime defaults it to bearer. + public string? TokenType { get; set; } + + /// + /// Gets or sets the required positive number of seconds remaining when the + /// callback completes. Production GitHub tokens typically last eight hours. + /// + public required long ExpiresIn { get; set; } + + /// + public override string ToString() + => $"{nameof(GitHubToken)} {{ {nameof(TokenType)} = {TokenType}, {nameof(ExpiresIn)} = {ExpiresIn}, {nameof(AccessToken)} = }}"; +} + +/// The result returned by a session-scoped GitHub token provider. +[Experimental(Diagnostics.Experimental)] +public sealed class GitHubTokenProviderResult +{ + /// Gets whether token acquisition was cancelled. + public bool Cancelled { get; private init; } + + /// Gets the acquired token, if acquisition was not cancelled. + public GitHubToken? Token { get; private init; } + + /// Creates a successful token result. + public static GitHubTokenProviderResult FromToken(GitHubToken token) + { + ArgumentNullException.ThrowIfNull(token); + return new() { Token = token }; + } + + /// Creates a cancelled result. + public static GitHubTokenProviderResult Cancel() => new() { Cancelled = true }; +} diff --git a/dotnet/src/JsonRpc.cs b/dotnet/src/JsonRpc.cs index 36289d2e68..c5c444ea70 100644 --- a/dotnet/src/JsonRpc.cs +++ b/dotnet/src/JsonRpc.cs @@ -548,7 +548,11 @@ private async Task HandleIncomingMethodAsync(string methodName, JsonElement mess if (requestId.HasValue) { - await SendResultResponseAsync(requestId.Value, result, cancellationToken).ConfigureAwait(false); + await SendResultResponseAsync( + requestId.Value, + result, + registration.ResultType, + cancellationToken).ConfigureAwait(false); } } catch (Exception ex) when (ex is not OperationCanceledException) @@ -772,18 +776,20 @@ private static bool TryGetPropertyCaseInsensitive(JsonElement obj, string name, return doc.RootElement.Clone(); } - private async Task SendResultResponseAsync(JsonElement id, object? result, CancellationToken cancellationToken) + private async Task SendResultResponseAsync( + JsonElement id, + object? result, + Type? declaredResultType, + CancellationToken cancellationToken) { try { - // Convert the result to a JsonElement using the runtime type, looked up via - // the merged resolver. Source-gen serialization of an `object`-typed property - // would otherwise have no way to find metadata for the actual response type - // (e.g. SystemMessageTransformRpcResponse, SessionFsReadFileResult, ...). + // Prefer the handler's declared result type so polymorphic base types emit + // their discriminator. Fall back to the runtime type for untyped handlers. JsonElement? resultElement = null; if (result is not null) { - var typeInfo = _serializerOptions.GetTypeInfo(result.GetType()); + var typeInfo = _serializerOptions.GetTypeInfo(declaredResultType ?? result.GetType()); resultElement = JsonSerializer.SerializeToElement(result, typeInfo); } @@ -863,6 +869,11 @@ public MethodRegistration(Delegate handler, bool singleObjectParam) { ValueTaskAsTaskMethod = GetMethodFromGenericMethodDefinition(returnType, s_valueTaskAsTask); TaskResultGetter = GetMethodFromGenericMethodDefinition(ValueTaskAsTaskMethod.ReturnType, s_taskGetResult); + ResultType = returnType.GetGenericArguments()[0]; + } + else if (returnType != typeof(void) && returnType != typeof(Task) && returnType != typeof(ValueTask)) + { + ResultType = returnType; } } @@ -871,6 +882,7 @@ public MethodRegistration(Delegate handler, bool singleObjectParam) public ParameterInfo[] Parameters { get; } public MethodInfo? ValueTaskAsTaskMethod { get; } public MethodInfo? TaskResultGetter { get; } + public Type? ResultType { get; } } private static MethodInfo GetMethodFromGenericMethodDefinition(Type specializedType, MethodInfo genericMethodDefinition) diff --git a/dotnet/src/PermissionDecision.cs b/dotnet/src/PermissionDecision.cs index 54e1237917..3eb1d0e08a 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 d990ca6534..6ff3abbda3 100644 --- a/dotnet/src/PermissionHandlers.cs +++ b/dotnet/src/PermissionHandlers.cs @@ -27,6 +27,9 @@ private static bool RequiresManagedApproval(PermissionRequest request) return true; } + // Only reached for a request that did not deserialize into a generated + // variant. Keep this list in sync with the PermissionRequest discriminators + // so a known kind is never treated as requiring managed approval. return request.GetType() == typeof(PermissionRequest) && request.Kind is not ("shell" or "write" @@ -37,6 +40,8 @@ private static bool RequiresManagedApproval(PermissionRequest request) or "custom-tool" or "hook" or "extension-management" - or "extension-permission-access"); + or "factory" + or "extension-permission-access" + or "extension-env-access"); } } diff --git a/dotnet/src/Session.cs b/dotnet/src/Session.cs index 7c34ded166..24450a7c7d 100644 --- a/dotnet/src/Session.cs +++ b/dotnet/src/Session.cs @@ -82,6 +82,7 @@ private sealed record EventSubscription(Type EventType, Action Han private IReadOnlyList _openCanvases = Array.Empty(); private int _isDisposed; + private string? _gitHubTokenProviderRegistrationId; /// /// Channel that serializes event dispatch. enqueues; @@ -203,6 +204,44 @@ internal void RemoveFromClient() ((ICollection>)_parentClient._sessions).Remove(new(SessionId, this)); } + /// + /// Stops the session's event consumer () without + /// making an RPC. and + /// use this on error paths where a + /// locally registered session fails before it can be returned to the caller: + /// starts the consumer eagerly, and no caller + /// ever receives the failed session to dispose it. Safe to call more than once — + /// is idempotent. + /// + internal void CloseEventChannel() + { + _eventChannel.Writer.TryComplete(); + } + + /// + /// Removes the session from its parent client and stops its event consumer. + /// Used on session-creation/resume error paths where the session was registered + /// (and its event loop started) but never returned to the caller. + /// + internal void Unregister() + { + CloseEventChannel(); + RemoveFromClient(); + } + + internal void SetGitHubTokenProviderRegistration(string registrationId) + { + _gitHubTokenProviderRegistrationId = registrationId; + } + + internal void ReleaseGitHubTokenProviderRegistration() + { + if (Interlocked.Exchange(ref _gitHubTokenProviderRegistrationId, null) is { } registrationId) + { + _parentClient.UnregisterGitHubTokenProvider(registrationId); + } + } + internal void StartProcessingEvents() { _ = ProcessEventsAsync(); @@ -357,7 +396,7 @@ void Handler(SessionEvent evt) } break; - case SessionIdleEvent: + case SessionIdleEvent idleEvent when idleEvent.Data.Mode != SessionMode.Autopilot: LoggingHelpers.LogTiming(_logger, LogLevel.Debug, null, "CopilotSession.SendAndWaitAsync idle received. Elapsed={Elapsed}, SessionId={SessionId}", totalTimestamp, @@ -954,7 +993,7 @@ 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, @@ -963,7 +1002,7 @@ private async Task ExecutePermissionAndRespondAsync(string requestId, Permission } catch (Exception ex) { - _logger.LogError(ex, "Permission handler or response delivery failed. SessionId={SessionId}, RequestId={RequestId}", SessionId, requestId); + LogPermissionHandlerOrDeliveryFailed(ex, SessionId, requestId); try { await Rpc.Permissions.HandlePendingPermissionRequestAsync(requestId, PermissionDecision.UserNotAvailable()); @@ -1364,7 +1403,10 @@ public async Task ElicitAsync(ElicitationParams elicitationPa Required = elicitationParams.RequestedSchema.Required }; - var result = await session.Rpc.Ui.ElicitationAsync(elicitationParams.Message, schema, cancellationToken); + var result = await session.Rpc.Ui.ElicitationAsync( + elicitationParams.Message, + schema, + cancellationToken: cancellationToken); return new ElicitationResult { Action = result.Action, @@ -1388,7 +1430,7 @@ public async Task ConfirmAsync(string message, CancellationToken cancellat Required = ["confirmed"] }; - var result = await session.Rpc.Ui.ElicitationAsync(message, schema, cancellationToken); + var result = await session.Rpc.Ui.ElicitationAsync(message, schema, cancellationToken: cancellationToken); if (result.Action == UIElicitationResponseAction.Accept && result.Content != null && result.Content.TryGetValue("confirmed", out var val)) @@ -1422,7 +1464,7 @@ public async Task ConfirmAsync(string message, CancellationToken cancellat Required = ["selection"] }; - var result = await session.Rpc.Ui.ElicitationAsync(message, schema, cancellationToken); + var result = await session.Rpc.Ui.ElicitationAsync(message, schema, cancellationToken: cancellationToken); if (result.Action == UIElicitationResponseAction.Accept && result.Content != null && result.Content.TryGetValue("selection", out var val)) @@ -1457,7 +1499,7 @@ public async Task ConfirmAsync(string message, CancellationToken cancellat Required = ["value"] }; - var result = await session.Rpc.Ui.ElicitationAsync(message, schema, cancellationToken); + var result = await session.Rpc.Ui.ElicitationAsync(message, schema, cancellationToken: cancellationToken); if (result.Action == UIElicitationResponseAction.Accept && result.Content != null && result.Content.TryGetValue("value", out var val)) @@ -1830,14 +1872,14 @@ public async Task SetModelAsync(string model, SetModelOptions options, Cancellat ThrowIfDisposed(); await Rpc.Model.SwitchToAsync( - model, - options.ReasoningEffort, - options.ReasoningSummary, - null, - options.ModelCapabilities, - options.ContextTier, - null, - cancellationToken); + modelId: model, + reasoningEffort: options.ReasoningEffort, + reasoningSummary: options.ReasoningSummary, + verbosity: null, + modelCapabilities: options.ModelCapabilities, + contextTier: options.ContextTier, + source: null, + cancellationToken: cancellationToken); } /// @@ -1916,7 +1958,7 @@ public async ValueTask DisposeAsync() return; } - _eventChannel.Writer.TryComplete(); + CloseEventChannel(); try { @@ -1933,6 +1975,7 @@ await InvokeRpcAsync( } finally { + ReleaseGitHubTokenProviderRegistration(); RemoveFromClient(); GC.SuppressFinalize(this); } @@ -1957,6 +2000,9 @@ await InvokeRpcAsync( [LoggerMessage(Level = LogLevel.Debug, Message = "Failed to fetch tool metadata for {toolName}")] private partial void LogToolMetadataFetchFailed(Exception exception, string toolName); + [LoggerMessage(Level = LogLevel.Error, Message = "Permission handler or response delivery failed. SessionId={SessionId}, RequestId={RequestId}")] + private partial void LogPermissionHandlerOrDeliveryFailed(Exception exception, string sessionId, string requestId); + internal record SendMessageRequest { public string SessionId { get; init; } = string.Empty; diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index a6a1a8c4eb..3af7ea7f12 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; @@ -321,6 +322,7 @@ private CopilotClientOptions(CopilotClientOptions? other) OnGitHubTelemetry = other.OnGitHubTelemetry; SessionIdleTimeoutSeconds = other.SessionIdleTimeoutSeconds; EnableRemoteSessions = other.EnableRemoteSessions; + ClientInfo = other.ClientInfo; Mode = other.Mode; } @@ -358,6 +360,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 /// (, @@ -457,6 +466,16 @@ private CopilotClientOptions(CopilotClientOptions? other) /// public bool EnableRemoteSessions { get; set; } + /// + /// Declares the integrating application's identity, forwarded to the runtime on the + /// server.connect handshake. Declaring it lets the telemetry the + /// runtime emits on this connection be attributed to a consistent surface + /// (the application and its Copilot integration) instead of the runtime's own + /// build. All fields are optional; leave it to keep + /// the runtime's default attribution. + /// + public CopilotClientInfo? ClientInfo { get; set; } + /// /// Creates a shallow clone of this instance. /// @@ -523,6 +542,38 @@ public sealed class TelemetryConfig public bool? CaptureContent { get; set; } } +/// +/// Identifies the integrating application on the server.connect handshake. +/// +/// +/// Declaring it lets the telemetry the runtime emits on the connection be +/// attributed to a single, consistent surface instead of the runtime's own +/// build. All properties are optional; an unset property is omitted from the +/// handshake. +/// +public sealed class CopilotClientInfo +{ + /// + /// Name of the application using the SDK. + /// + public string? ApplicationName { get; set; } + + /// + /// Version of the application using the SDK. + /// + public string? ApplicationVersion { get; set; } + + /// + /// Optionally specifies a named integration within the application, such as an extension or plugin. + /// + public string? IntegrationName { get; set; } + + /// + /// Optionally specifies the version of that integration. + /// + public string? IntegrationVersion { get; set; } +} + /// /// Configuration for a custom session filesystem provider. /// @@ -2390,6 +2441,18 @@ public sealed class CapiSessionOptions /// [JsonPropertyName("enableWebSocketResponses")] public bool? EnableWebSocketResponses { get; set; } + + /// + /// Routing tier for model auto with V2 Auto. + /// + /// + /// Requires a runtime that supports Auto tiers; it has no effect outside V2 Auto. + /// When omitted, the runtime uses its default on create and preserves the persisted or current + /// tier on resume. An explicit tier overrides the persisted tier on a cold resume; a conflicting + /// tier on a resident session resume is rejected by the runtime. + /// + [JsonPropertyName("autoTier")] + public AutoTier? AutoTier { get; set; } } /// @@ -3044,6 +3107,85 @@ public sealed class GitHubMcpToolConfig public bool? DisableFormDeferral { get; set; } } +/// Well-known managed bypass-permissions policies. +public static class DisableBypassPermissionsModes +{ + /// Turns off bypass-permissions mode entirely. + public const string Disable = "disable"; + + /// Permits automatic bypass but blocks full allow-all. + public const string AllowAutoOnly = "allow-auto-only"; +} + +/// +/// 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 policies compose to +/// the most restrictive setting. +/// +public sealed class ManagedSettingsPermissions +{ + /// + /// Restricts bypass-permissions mode for the session regardless of other + /// layers. See for well-known + /// values. Unknown values are forwarded so newer runtime policies fail closed. + /// Serialized as disableBypassPermissionsMode. + /// + [JsonPropertyName("disableBypassPermissionsMode")] + public string? 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; } +} + +/// +/// Selects the model-facing shape of the built-in ask_user tool. +/// +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum AskUserVariant +{ + /// Use the legacy user-input request flow. + [JsonStringEnumMemberName("legacy")] + Legacy, + + /// Use the elicitation request flow. + [JsonStringEnumMemberName("elicitation")] + Elicitation +} + /// /// Shared configuration properties for creating or resuming a Copilot session. /// Use when creating a new session, or @@ -3070,7 +3212,10 @@ protected SessionConfigBase(SessionConfigBase? other) DefaultAgent = other.DefaultAgent; Agent = other.Agent; DisabledSkills = other.DisabledSkills is not null ? [.. other.DisabledSkills] : null; + IncludedBuiltinSkills = other.IncludedBuiltinSkills is not null ? [.. other.IncludedBuiltinSkills] : 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; @@ -3130,11 +3275,17 @@ protected SessionConfigBase(SessionConfigBase? other) ReasoningEffort = other.ReasoningEffort; ReasoningSummary = other.ReasoningSummary; ContextTier = other.ContextTier; + AskUserVariant = other.AskUserVariant; CreateSessionFsProvider = other.CreateSessionFsProvider; GitHubToken = other.GitHubToken; + GitHubTokenProvider = other.GitHubTokenProvider; RemoteSession = other.RemoteSession; + FeatureFlags = other.FeatureFlags is not null + ? new Dictionary(other.FeatureFlags) + : null; 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; @@ -3197,6 +3348,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. @@ -3266,6 +3428,14 @@ protected SessionConfigBase(SessionConfigBase? other) /// public bool? EnableSkills { get; set; } + /// + /// Built-in skill names to include in the session. In + /// , omitting this option excludes all + /// runtime-bundled skills; specifying names opts those built-ins back in. + /// Skills from other sources remain eligible. + /// + public IList? IncludedBuiltinSkills { get; set; } + /// /// Custom tool declarations available to the language model during the session. /// Declarations backed by an are invoked automatically; declarations without one @@ -3276,6 +3446,15 @@ protected SessionConfigBase(SessionConfigBase? other) /// System message configuration for the session. public SystemMessageConfig? SystemMessage { get; set; } + /// + /// Selects the model-facing shape of the built-in ask_user tool. + /// The default is . To use + /// , also provide + /// so the host can answer structured forms. + /// The runtime resolves this option when it creates or cold-resumes the session. + /// + public AskUserVariant? AskUserVariant { get; set; } + /// List of tool names to allow; only these tools will be available when specified. public IList? AvailableTools { get; set; } @@ -3374,7 +3553,11 @@ protected SessionConfigBase(SessionConfigBase? other) /// Handler for permission requests from the server. public Func>? OnPermissionRequest { get; set; } - /// Handler for user input requests from the agent. + /// + /// Handler for user input requests from the agent. When provided with the default + /// variant, enables the + /// question-and-answer form of the ask_user tool. + /// public Func>? OnUserInputRequest { get; set; } /// Slash commands registered for this session. @@ -3500,6 +3683,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. @@ -3556,6 +3746,16 @@ protected SessionConfigBase(SessionConfigBase? other) /// public string? GitHubToken { get; set; } + /// + /// Gets or sets a callback that acquires session-scoped GitHub tokens on + /// demand. Initial cancellation, callback errors, and invalid token responses + /// reject session creation or resume instead of falling back to ambient + /// authentication. This cannot be combined with . + /// + [Experimental(Diagnostics.Experimental)] + [JsonIgnore] + public Func>? GitHubTokenProvider { get; set; } + /// /// Per-session remote behavior control: /// @@ -3583,6 +3783,12 @@ protected SessionConfigBase(SessionConfigBase? other) [EditorBrowsable(EditorBrowsableState.Never)] public CopilotExpAssignmentResponse? ExpAssignments { get; set; } + /// + /// Feature-flag values resolved by the host for this session. + /// Re-supply them when resuming after a runtime restart. + /// + public IDictionary? FeatureFlags { get; set; } + /// /// Opt-in: when true, the runtime self-fetches enterprise managed /// settings (bypass-permissions policy) at session bootstrap using the @@ -3593,6 +3799,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/src/build/GitHub.Copilot.SDK.targets b/dotnet/src/build/GitHub.Copilot.SDK.targets index 5f7944b2c4..aca299dd27 100644 --- a/dotnet/src/build/GitHub.Copilot.SDK.targets +++ b/dotnet/src/build/GitHub.Copilot.SDK.targets @@ -38,6 +38,8 @@ <_CopilotPlatform Condition="'$(_CopilotRid)' == 'osx-arm64'">darwin-arm64 <_CopilotBinary Condition="$(_CopilotRid.StartsWith('win-'))">copilot.exe <_CopilotBinary Condition="'$(_CopilotBinary)' == ''">copilot + <_CopilotRuntimeWrapper Condition="$(_CopilotRid.StartsWith('win-'))">copilot-runtime.exe + <_CopilotRuntimeWrapper Condition="'$(_CopilotRuntimeWrapper)' == ''">copilot-runtime <_CopilotRuntimeNodePath>$(_CopilotCacheDir)\prebuilds\$(_CopilotPlatform)\runtime.node + <_CopilotRuntimeWrapperPath Condition="'$(_CopilotRuntimeWrapperPath)' == ''">$(_CopilotCacheDir)\prebuilds\$(_CopilotPlatform)\$(_CopilotRuntimeWrapper) + <_CopilotRuntimeAssetManifest>$(_CopilotOutputDir)\.copilot-runtime-assets + <_CopilotExplicitCliMarker>$(_CopilotOutputDir)\.copilot-explicit-cli + + + + + + <_CopilotSafePreviousRuntimeAsset Include="@(_CopilotPreviousRuntimeAsset)" + Condition="!$([System.IO.Path]::IsPathRooted('%(Identity)')) And !$([System.String]::Copy('%(Identity)').Contains('..'))" /> + + + + + <_CopilotRuntimeRootAsset Include="$(_CopilotCacheDir)\**\*" + Exclude="$(_CopilotCacheDir)\app.js;$(_CopilotCacheDir)\assets\**\*;$(_CopilotCacheDir)\changelog.json;$(_CopilotCacheDir)\copilot;$(_CopilotCacheDir)\copilot.exe;$(_CopilotCacheDir)\copilot.tgz;$(_CopilotCacheDir)\foundry-local-sdk\**\*;$(_CopilotCacheDir)\index.js;$(_CopilotCacheDir)\LICENSE.md;$(_CopilotCacheDir)\napi-oop-runtime\**\*;$(_CopilotCacheDir)\npm-loader.js;$(_CopilotCacheDir)\package.json;$(_CopilotCacheDir)\prebuilds\**\*;$(_CopilotCacheDir)\pvrecorder\**\*;$(_CopilotCacheDir)\queries\**\*;$(_CopilotCacheDir)\README.md;$(_CopilotCacheDir)\sea-loader.js;$(_CopilotCacheDir)\tree-sitter*.wasm;$(_CopilotCacheDir)\voice-*.js;$(_CopilotCacheDir)\webview\**\*" /> + <_CopilotRuntimePrebuildAsset Include="$(_CopilotCacheDir)\prebuilds\$(_CopilotPlatform)\**\*" + Exclude="$(_CopilotCacheDir)\prebuilds\$(_CopilotPlatform)\cli-native.node;$(_CopilotCacheDir)\prebuilds\$(_CopilotPlatform)\mediaremote-adapter\**\*;$(_CopilotCacheDir)\prebuilds\$(_CopilotPlatform)\copilot-runtime-bin*" /> + + + + + + + + + diff --git a/dotnet/test/E2E/BuiltinToolsE2ETests.cs b/dotnet/test/E2E/BuiltinToolsE2ETests.cs index 5fc0314171..143487dac4 100644 --- a/dotnet/test/E2E/BuiltinToolsE2ETests.cs +++ b/dotnet/test/E2E/BuiltinToolsE2ETests.cs @@ -2,6 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ +using GitHub.Copilot.Test.Harness; using Xunit; using Xunit.Abstractions; diff --git a/dotnet/test/E2E/CanvasE2ETests.cs b/dotnet/test/E2E/CanvasE2ETests.cs index deb94a9b24..249440d993 100644 --- a/dotnet/test/E2E/CanvasE2ETests.cs +++ b/dotnet/test/E2E/CanvasE2ETests.cs @@ -123,7 +123,6 @@ private Task CreateCanvasSessionAsync(TestCanvasHandler handler) { OnPermissionRequest = PermissionHandler.ApproveAll, RequestCanvasRenderer = true, - RequestExtensions = true, ExtensionInfo = new ExtensionInfo { Source = "dotnet-sdk-tests", Name = "canvas-provider" }, Canvases = [ diff --git a/dotnet/test/E2E/ClientE2ETests.cs b/dotnet/test/E2E/ClientE2ETests.cs index d166223d65..282cc9ee67 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] @@ -38,9 +41,8 @@ public async Task Should_Start_And_Connect_To_Server(bool useStdio) [Fact] public async Task Should_Start_And_Connect_Over_InProcess_Ffi() { - // In-process FFI hosting resolves the CLI entrypoint (COPILOT_CLI_PATH or the - // bundled CLI binary) and its sibling native runtime library itself; if neither - // is available, StartAsync throws and the test fails hard. + // In-process FFI hosting loads the bundled runtime library directly; if it is + // unavailable, StartAsync throws and the test fails hard. using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForInProcess(), @@ -177,25 +179,31 @@ 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()); var errorMessage = ex.Message; - // On .NET Framework with stdio transport, the pipe error may not include stderr content. - if (errorMessage.Contains("pipe", StringComparison.OrdinalIgnoreCase)) +#if NETFRAMEWORK + // .NET Framework can surface the stdio pipe failure before redirected stderr is readable. + if (useStdio) { - // .NET Framework pipe behavior — just verify we got an IOException - Assert.Contains("pipe", errorMessage, StringComparison.OrdinalIgnoreCase); + Assert.True( + errorMessage.Contains("CLI", StringComparison.OrdinalIgnoreCase) + || errorMessage.Contains("pipe", StringComparison.OrdinalIgnoreCase), + $"Expected a CLI process or pipe error, got: {errorMessage}"); } else +#endif { - // Verify we get the stderr output in the error message Assert.Contains("stderr", errorMessage, StringComparison.OrdinalIgnoreCase); Assert.Contains("nonexistent", errorMessage, StringComparison.OrdinalIgnoreCase); } diff --git a/dotnet/test/E2E/ClientOptionsE2ETests.cs b/dotnet/test/E2E/ClientOptionsE2ETests.cs index 5391e4bdbc..1ff473bd01 100644 --- a/dotnet/test/E2E/ClientOptionsE2ETests.cs +++ b/dotnet/test/E2E/ClientOptionsE2ETests.cs @@ -296,7 +296,7 @@ public async Task Should_Forward_Advanced_Session_Options_In_Create_Wire_Request var session = await Ctx.CreateSessionAsync(client, new SessionConfig { ClientName = "advanced-create-client", - Model = "claude-sonnet-4.5", + Model = "claude-sonnet-5", ReasoningEffort = "medium", ReasoningSummary = ReasoningSummary.Detailed, ContextTier = ContextTier.LongContext, @@ -379,7 +379,7 @@ public async Task Should_Forward_Advanced_Session_Options_In_Create_Wire_Request Provider = "create-provider", Id = "create-model", Name = "Create Model", - ModelId = "claude-sonnet-4.5", + ModelId = "claude-sonnet-5", WireModel = "create-wire-model", MaxContextWindowTokens = 12_000, MaxPromptTokens = 10_000, @@ -392,7 +392,7 @@ public async Task Should_Forward_Advanced_Session_Options_In_Create_Wire_Request using var capture = JsonDocument.Parse(await File.ReadAllTextAsync(capturePath)); var createRequest = GetCapturedRequestParams(capture.RootElement, "session.create"); Assert.Equal("advanced-create-client", createRequest.GetProperty("clientName").GetString()); - Assert.Equal("claude-sonnet-4.5", createRequest.GetProperty("model").GetString()); + Assert.Equal("claude-sonnet-5", createRequest.GetProperty("model").GetString()); Assert.Equal("medium", createRequest.GetProperty("reasoningEffort").GetString()); Assert.Equal("detailed", createRequest.GetProperty("reasoningSummary").GetString()); Assert.Equal("long_context", createRequest.GetProperty("contextTier").GetString()); @@ -442,7 +442,7 @@ public async Task Should_Forward_Singular_Provider_Options_In_Create_Wire_Reques var session = await Ctx.CreateSessionAsync(client, new SessionConfig { - Model = "claude-sonnet-4.5", + Model = "claude-sonnet-5", Provider = new ProviderConfig { Type = "azure", @@ -453,7 +453,7 @@ public async Task Should_Forward_Singular_Provider_Options_In_Create_Wire_Reques BearerToken = "provider-bearer-token", Azure = new AzureOptions { ApiVersion = "2024-02-15-preview" }, Headers = new Dictionary { ["X-Provider-Wire"] = "yes" }, - ModelId = "claude-sonnet-4.5", + ModelId = "claude-sonnet-5", WireModel = "azure-deployment", MaxPromptTokens = 8192, MaxOutputTokens = 1024, @@ -471,7 +471,7 @@ public async Task Should_Forward_Singular_Provider_Options_In_Create_Wire_Reques Assert.Equal("provider-bearer-token", provider.GetProperty("bearerToken").GetString()); Assert.Equal("2024-02-15-preview", provider.GetProperty("azure").GetProperty("apiVersion").GetString()); Assert.Equal("yes", provider.GetProperty("headers").GetProperty("X-Provider-Wire").GetString()); - Assert.Equal("claude-sonnet-4.5", provider.GetProperty("modelId").GetString()); + Assert.Equal("claude-sonnet-5", provider.GetProperty("modelId").GetString()); Assert.Equal("azure-deployment", provider.GetProperty("wireModel").GetString()); Assert.Equal(8192, provider.GetProperty("maxPromptTokens").GetInt32()); Assert.Equal(1024, provider.GetProperty("maxOutputTokens").GetInt32()); diff --git a/dotnet/test/E2E/CopilotRequestE2EProvider.cs b/dotnet/test/E2E/CopilotRequestE2EProvider.cs index 89826b4f84..f4c250752a 100644 --- a/dotnet/test/E2E/CopilotRequestE2EProvider.cs +++ b/dotnet/test/E2E/CopilotRequestE2EProvider.cs @@ -160,9 +160,9 @@ internal static HttpResponseMessage BuildNonInferenceResponse(string url) private static readonly string[] ChatCompletionStreamEvents = [ - "data: {\"id\":\"chatcmpl-stub-1\",\"object\":\"chat.completion.chunk\",\"created\":1,\"model\":\"claude-sonnet-4.5\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\"},\"finish_reason\":null}]}\n\n", - "data: {\"id\":\"chatcmpl-stub-1\",\"object\":\"chat.completion.chunk\",\"created\":1,\"model\":\"claude-sonnet-4.5\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"" + SyntheticText + "\"},\"finish_reason\":null}]}\n\n", - "data: {\"id\":\"chatcmpl-stub-1\",\"object\":\"chat.completion.chunk\",\"created\":1,\"model\":\"claude-sonnet-4.5\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"stop\"}],\"usage\":{\"prompt_tokens\":5,\"completion_tokens\":7,\"total_tokens\":12}}\n\n", + "data: {\"id\":\"chatcmpl-stub-1\",\"object\":\"chat.completion.chunk\",\"created\":1,\"model\":\"claude-sonnet-5\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\"},\"finish_reason\":null}]}\n\n", + "data: {\"id\":\"chatcmpl-stub-1\",\"object\":\"chat.completion.chunk\",\"created\":1,\"model\":\"claude-sonnet-5\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"" + SyntheticText + "\"},\"finish_reason\":null}]}\n\n", + "data: {\"id\":\"chatcmpl-stub-1\",\"object\":\"chat.completion.chunk\",\"created\":1,\"model\":\"claude-sonnet-5\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"stop\"}],\"usage\":{\"prompt_tokens\":5,\"completion_tokens\":7,\"total_tokens\":12}}\n\n", "data: [DONE]\n\n", ]; @@ -172,7 +172,7 @@ internal static HttpResponseMessage BuildNonInferenceResponse(string url) // runtime's Anthropic client fail with "stream ended without producing a Message". private static readonly string[] AnthropicStreamEvents = [ - "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"id\":\"msg_stub_1\",\"type\":\"message\",\"role\":\"assistant\",\"model\":\"claude-sonnet-4.5\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"usage\":{\"input_tokens\":5,\"output_tokens\":1}}}\n\n", + "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"id\":\"msg_stub_1\",\"type\":\"message\",\"role\":\"assistant\",\"model\":\"claude-sonnet-5\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"usage\":{\"input_tokens\":5,\"output_tokens\":1}}}\n\n", "event: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}\n\n", "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"" + SyntheticText + "\"}}\n\n", "event: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}\n\n", @@ -184,13 +184,13 @@ internal static HttpResponseMessage BuildNonInferenceResponse(string url) "{\"id\":\"resp_stub_1\",\"object\":\"response\",\"status\":\"completed\",\"output\":[{\"id\":\"msg_1\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[{\"type\":\"output_text\",\"text\":\"" + SyntheticText + "\"}]}],\"usage\":{\"input_tokens\":5,\"output_tokens\":7,\"total_tokens\":12}}"; private static readonly string BufferedChatCompletionJson = - "{\"id\":\"chatcmpl-stub-1\",\"object\":\"chat.completion\",\"created\":1,\"model\":\"claude-sonnet-4.5\",\"choices\":[{\"index\":0,\"message\":{\"role\":\"assistant\",\"content\":\"" + SyntheticText + "\"},\"finish_reason\":\"stop\"}],\"usage\":{\"prompt_tokens\":5,\"completion_tokens\":7,\"total_tokens\":12}}"; + "{\"id\":\"chatcmpl-stub-1\",\"object\":\"chat.completion\",\"created\":1,\"model\":\"claude-sonnet-5\",\"choices\":[{\"index\":0,\"message\":{\"role\":\"assistant\",\"content\":\"" + SyntheticText + "\"},\"finish_reason\":\"stop\"}],\"usage\":{\"prompt_tokens\":5,\"completion_tokens\":7,\"total_tokens\":12}}"; private static readonly string BufferedAnthropicMessageJson = - "{\"id\":\"msg_stub_1\",\"type\":\"message\",\"role\":\"assistant\",\"model\":\"claude-sonnet-4.5\",\"content\":[{\"type\":\"text\",\"text\":\"" + SyntheticText + "\"}],\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"usage\":{\"input_tokens\":5,\"output_tokens\":7}}"; + "{\"id\":\"msg_stub_1\",\"type\":\"message\",\"role\":\"assistant\",\"model\":\"claude-sonnet-5\",\"content\":[{\"type\":\"text\",\"text\":\"" + SyntheticText + "\"}],\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"usage\":{\"input_tokens\":5,\"output_tokens\":7}}"; private const string ModelCatalogJson = - "{\"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,\"vision\":true}}}]}"; + "{\"data\":[{\"id\":\"claude-sonnet-5\",\"name\":\"Claude Sonnet 5\",\"object\":\"model\",\"vendor\":\"Anthropic\",\"version\":\"1\",\"preview\":false,\"model_picker_enabled\":true,\"capabilities\":{\"type\":\"chat\",\"family\":\"claude-sonnet-5\",\"tokenizer\":\"o200k_base\",\"limits\":{\"max_context_window_tokens\":200000,\"max_output_tokens\":8192},\"supports\":{\"streaming\":true,\"tool_calls\":true,\"parallel_tool_calls\":true,\"vision\":true}}}]}"; } /// A single request the callback intercepted. diff --git a/dotnet/test/E2E/CopilotRequestSessionIdE2ETests.cs b/dotnet/test/E2E/CopilotRequestSessionIdE2ETests.cs index fd00cc9b99..2fca912592 100644 --- a/dotnet/test/E2E/CopilotRequestSessionIdE2ETests.cs +++ b/dotnet/test/E2E/CopilotRequestSessionIdE2ETests.cs @@ -76,15 +76,15 @@ public async Task Threads_The_Session_Id_Into_A_Byok_Session_Inference_Request() { OnPermissionRequest = PermissionHandler.ApproveAll, // BYOK providers require an explicit model id. - Model = "claude-sonnet-4.5", + Model = "claude-sonnet-5", Provider = new ProviderConfig { Type = "openai", WireApi = "responses", BaseUrl = "https://byok.invalid/v1", ApiKey = "byok-secret", - ModelId = "claude-sonnet-4.5", - WireModel = "claude-sonnet-4.5", + ModelId = "claude-sonnet-5", + WireModel = "claude-sonnet-5", }, }); var byokSessionId = session.SessionId; diff --git a/dotnet/test/E2E/CopilotRequestWebSocketE2ETests.cs b/dotnet/test/E2E/CopilotRequestWebSocketE2ETests.cs index 80ccdb8c90..e6890d6992 100644 --- a/dotnet/test/E2E/CopilotRequestWebSocketE2ETests.cs +++ b/dotnet/test/E2E/CopilotRequestWebSocketE2ETests.cs @@ -349,7 +349,7 @@ private static (string Type, string Json)[] ResponseEvents(string text, string i ]; private const string ModelCatalogJson = - "{\"data\":[{\"id\":\"claude-sonnet-4.5\",\"name\":\"Claude Sonnet 4.5\",\"object\":\"model\",\"vendor\":\"Anthropic\",\"version\":\"1\",\"preview\":false,\"model_picker_enabled\":true,\"supported_endpoints\":[\"/responses\",\"ws:/responses\"],\"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,\"vision\":true}}}]}"; + "{\"data\":[{\"id\":\"claude-sonnet-5\",\"name\":\"Claude Sonnet 5\",\"object\":\"model\",\"vendor\":\"Anthropic\",\"version\":\"1\",\"preview\":false,\"model_picker_enabled\":true,\"supported_endpoints\":[\"/responses\",\"ws:/responses\"],\"capabilities\":{\"type\":\"chat\",\"family\":\"claude-sonnet-5\",\"tokenizer\":\"o200k_base\",\"limits\":{\"max_context_window_tokens\":200000,\"max_output_tokens\":8192},\"supports\":{\"streaming\":true,\"tool_calls\":true,\"parallel_tool_calls\":true,\"vision\":true}}}]}"; private static int GetFreePort() { diff --git a/dotnet/test/E2E/EventFidelityE2ETests.cs b/dotnet/test/E2E/EventFidelityE2ETests.cs index 8882f972d5..da9869757f 100644 --- a/dotnet/test/E2E/EventFidelityE2ETests.cs +++ b/dotnet/test/E2E/EventFidelityE2ETests.cs @@ -12,7 +12,7 @@ namespace GitHub.Copilot.Test.E2E; /// Verifies the shape and ordering of s emitted from the /// runtime: every event has an id and timestamp, user/assistant messages carry /// content, tool execution events carry a toolCallId, and -/// session.idle is the last event of a turn. Mirrors +/// session.idle follows the final assistant message of a turn. Mirrors /// nodejs/test/e2e/event_fidelity.e2e.test.ts. /// public class EventFidelityE2ETests(E2ETestFixture fixture, ITestOutputHelper output) @@ -43,9 +43,12 @@ await session.SendAndWaitAsync(new MessageOptions var assistantIdx = types.LastIndexOf("assistant.message"); Assert.True(userIdx < assistantIdx, $"Expected user.message ({userIdx}) before last assistant.message ({assistantIdx})"); - // session.idle should be the last event we observed + // session.idle should complete the conversational turn. Post-turn + // metadata events may arrive after it on slower target frameworks. var idleIdx = types.LastIndexOf("session.idle"); - Assert.Equal(types.Count - 1, idleIdx); + Assert.True( + assistantIdx < idleIdx, + $"Expected last assistant.message ({assistantIdx}) before session.idle ({idleIdx}): {string.Join(", ", types)}"); await session.DisposeAsync(); } diff --git a/dotnet/test/E2E/MultiClientE2ETests.cs b/dotnet/test/E2E/MultiClientE2ETests.cs index 4dbe7190ad..7597aa580b 100644 --- a/dotnet/test/E2E/MultiClientE2ETests.cs +++ b/dotnet/test/E2E/MultiClientE2ETests.cs @@ -324,6 +324,9 @@ public async Task Disconnecting_Client_Removes_Its_Tools() // Disconnect client 2 await Client2.ForceStopAsync(); + // Give the server time to process the connection close and remove tools. + await Task.Delay(500); + // Recreate client2 for cleanup var port = Client1.RuntimePort!.Value; _client2 = Ctx.CreateClient(options: new CopilotClientOptions diff --git a/dotnet/test/E2E/PendingWorkResumeE2ETests.cs b/dotnet/test/E2E/PendingWorkResumeE2ETests.cs index b3ca218190..e48dbbbcbc 100644 --- a/dotnet/test/E2E/PendingWorkResumeE2ETests.cs +++ b/dotnet/test/E2E/PendingWorkResumeE2ETests.cs @@ -140,10 +140,12 @@ await session1.SendAsync(new MessageOptions } [Description("Looks up a value after resumption")] - async Task BlockingExternalTool([Description("Value to look up")] string value) + async Task BlockingExternalTool( + [Description("Value to look up")] string value, + CancellationToken cancellationToken) { originalToolStarted.TrySetResult(value); - return await releaseOriginalTool.Task; + return await releaseOriginalTool.Task.WaitAsync(Timeout.InfiniteTimeSpan, cancellationToken); } } @@ -196,7 +198,30 @@ await session1.SendAsync(new MessageOptions if (disconnectOriginalClient) { + await using var lockObserver = Ctx.CreateClient(options: new CopilotClientOptions + { + Connection = RuntimeConnection.ForStdio(), + }); + await lockObserver.StartAsync(); + await TestHelper.WaitForConditionAsync( + async () => + { + var result = await lockObserver.Rpc.Sessions.CheckInUseAsync([sessionId]); + return result.InUse.Contains(sessionId); + }, + timeout: PendingWorkTimeout, + timeoutMessage: $"Timed out waiting for session '{sessionId}' to acquire its lock."); + await suspendedClient.ForceStopAsync(); + + await TestHelper.WaitForConditionAsync( + async () => + { + var result = await lockObserver.Rpc.Sessions.CheckInUseAsync([sessionId]); + return !result.InUse.Contains(sessionId); + }, + timeout: PendingWorkTimeout, + timeoutMessage: $"Timed out waiting for session '{sessionId}' to release its lock."); } await using var resumedClient = Ctx.CreateClient(options: new CopilotClientOptions { Connection = RuntimeConnection.ForUri(cliUrl, connectionToken: SharedToken) }); @@ -251,11 +276,13 @@ await session1.SendAsync(new MessageOptions } [Description("Looks up a value after resumption")] - async Task BlockingExternalTool([Description("Value to look up")] string value) + async Task BlockingExternalTool( + [Description("Value to look up")] string value, + CancellationToken cancellationToken) { Interlocked.Increment(ref invocationCount); originalToolStarted.TrySetResult(value); - return await releaseOriginalTool.Task; + return await releaseOriginalTool.Task.WaitAsync(Timeout.InfiniteTimeSpan, cancellationToken); } [Description("Looks up a value after resumption")] @@ -333,17 +360,21 @@ await Task.WhenAll( } [Description("Looks up the first value after resumption")] - async Task BlockingToolA([Description("Value to look up")] string value) + async Task BlockingToolA( + [Description("Value to look up")] string value, + CancellationToken cancellationToken) { originalToolAStarted.TrySetResult(value); - return await releaseOriginalToolA.Task; + return await releaseOriginalToolA.Task.WaitAsync(Timeout.InfiniteTimeSpan, cancellationToken); } [Description("Looks up the second value after resumption")] - async Task BlockingToolB([Description("Value to look up")] string value) + async Task BlockingToolB( + [Description("Value to look up")] string value, + CancellationToken cancellationToken) { originalToolBStarted.TrySetResult(value); - return await releaseOriginalToolB.Task; + return await releaseOriginalToolB.Task.WaitAsync(Timeout.InfiniteTimeSpan, cancellationToken); } } diff --git a/dotnet/test/E2E/RewindE2ETests.cs b/dotnet/test/E2E/RewindE2ETests.cs new file mode 100644 index 0000000000..e8379f988d --- /dev/null +++ b/dotnet/test/E2E/RewindE2ETests.cs @@ -0,0 +1,102 @@ +// 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 OriginalFileContent = "Original rewind content"; + private const string PreparedFileContent = "Prepared rewind content"; + 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 File.WriteAllTextAsync(filePath, OriginalFileContent); + await using var session = await CreateSessionAsync(new SessionConfig + { + Model = "claude-sonnet-5", + EnableFileChangeTracking = true, + }); + + var ready = await session.SendAndWaitAsync( + new MessageOptions + { + Prompt = $"Use the edit tool to replace the exact contents of {FileName} " + + $"from {OriginalFileContent} to {PreparedFileContent}. " + + "After the tool succeeds, reply with exactly SDK_REWIND_READY.", + }, + TimeSpan.FromSeconds(30)); + Assert.Equal("SDK_REWIND_READY", ready?.Data.Content); + Assert.Equal(PreparedFileContent, await File.ReadAllTextAsync(filePath)); + + var response = await session.SendAndWaitAsync( + new MessageOptions + { + Prompt = $"Use the edit tool to replace the exact contents of {FileName} " + + $"from {PreparedFileContent} to {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 + && rewindPoints.Points.Count == 2 + && rewindPoints.Points[1].TurnChangedFiles + && rewindPoints.Points[1].CanRestoreFiles + && rewindPoints.Points[1].FileCount == 1; + }, + timeout: TimeSpan.FromSeconds(30), + timeoutMessage: "Timed out waiting for a restorable file rewind point.", + pollInterval: TimeSpan.FromMilliseconds(100)); + + Assert.NotNull(rewindPoints); + Assert.True(rewindPoints.FileChangeTrackingEnabled); + Assert.Equal(2, rewindPoints.Points.Count); + var rewindPoint = rewindPoints.Points[1]; + Assert.True(rewindPoint.TurnChangedFiles); + 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.Equal(PreparedFileContent, await File.ReadAllTextAsync(filePath)); + + var events = await session.GetEventsAsync(); + Assert.DoesNotContain(events, sessionEvent => sessionEvent.Id.ToString() == rewindPoint.EventId); + } +} diff --git a/dotnet/test/E2E/RpcExtensionsLoadedE2ETests.cs b/dotnet/test/E2E/RpcExtensionsLoadedE2ETests.cs index 0a3513e4b5..3d017d8165 100644 --- a/dotnet/test/E2E/RpcExtensionsLoadedE2ETests.cs +++ b/dotnet/test/E2E/RpcExtensionsLoadedE2ETests.cs @@ -57,10 +57,22 @@ private CopilotClient CreateExtensionsClient() { return Ctx.CreateClient(options: new CopilotClientOptions { - Connection = RuntimeConnection.ForStdio(args: ["--yolo"]), + Connection = RuntimeConnection.ForStdio( + path: Ctx.GetLegacyCliPath(), + args: ["--yolo"]), }, environment: ExtensionsEnabledEnvironment()); } + private static SessionConfig CreateExtensionsSessionConfig(string? workingDirectory = null) + { + return new SessionConfig + { + EnableConfigDiscovery = true, + WorkingDirectory = workingDirectory, + OnPermissionRequest = PermissionHandler.ApproveAll, + }; + } + /// /// Writes a minimal user extension into {HomeDir}/extensions/{name}/extension.mjs. /// The body imports @github/copilot-sdk/extension, calls joinSession @@ -189,12 +201,9 @@ public async Task Discovers_Loads_And_Reports_Running_Extension(string sourceVal await using var client = CreateExtensionsClient(); - await using var session = await Ctx.CreateSessionAsync(client, new SessionConfig - { - EnableConfigDiscovery = true, - WorkingDirectory = workingDirectory, - OnPermissionRequest = PermissionHandler.ApproveAll, - }); + await using var session = await Ctx.CreateSessionAsync( + client, + CreateExtensionsSessionConfig(workingDirectory)); var ext = await WaitForExtensionAsync(session, extId, ExtensionStatus.Running); @@ -214,11 +223,9 @@ public async Task Disable_Then_Enable_Cycles_Extension_Status() await using var client = CreateExtensionsClient(); - await using var session = await Ctx.CreateSessionAsync(client, new SessionConfig - { - EnableConfigDiscovery = true, - OnPermissionRequest = PermissionHandler.ApproveAll, - }); + await using var session = await Ctx.CreateSessionAsync( + client, + CreateExtensionsSessionConfig()); // Wait until the initial running state is observed before mutating. await WaitForExtensionAsync(session, extId, ExtensionStatus.Running); @@ -240,11 +247,9 @@ public async Task Reload_Picks_Up_Extension_Added_After_Session_Create() // Start the session BEFORE writing the extension so the initial discovery sees nothing. await using var client = CreateExtensionsClient(); - await using var session = await Ctx.CreateSessionAsync(client, new SessionConfig - { - EnableConfigDiscovery = true, - OnPermissionRequest = PermissionHandler.ApproveAll, - }); + await using var session = await Ctx.CreateSessionAsync( + client, + CreateExtensionsSessionConfig()); // setupExtensionsForSession runs asynchronously; until it completes the // controller isn't installed and ReloadAsync throws "Extensions not @@ -285,11 +290,9 @@ public async Task Failed_Extension_Reports_Failed_Status() await using var client = CreateExtensionsClient(); - await using var session = await Ctx.CreateSessionAsync(client, new SessionConfig - { - EnableConfigDiscovery = true, - OnPermissionRequest = PermissionHandler.ApproveAll, - }); + await using var session = await Ctx.CreateSessionAsync( + client, + CreateExtensionsSessionConfig()); var ext = await WaitForExtensionAsync(session, extId, ExtensionStatus.Failed); Assert.Equal(extId, ext.Id); @@ -306,11 +309,9 @@ public async Task Multiple_Extensions_Are_Discovered_Independently() await using var client = CreateExtensionsClient(); - await using var session = await Ctx.CreateSessionAsync(client, new SessionConfig - { - EnableConfigDiscovery = true, - OnPermissionRequest = PermissionHandler.ApproveAll, - }); + await using var session = await Ctx.CreateSessionAsync( + client, + CreateExtensionsSessionConfig()); await WaitForExtensionAsync(session, ext1Id, ExtensionStatus.Running); await WaitForExtensionAsync(session, ext2Id, ExtensionStatus.Running); @@ -328,11 +329,9 @@ public async Task Reload_Preserves_Disabled_State_Across_Calls() await using var client = CreateExtensionsClient(); - await using var session = await Ctx.CreateSessionAsync(client, new SessionConfig - { - EnableConfigDiscovery = true, - OnPermissionRequest = PermissionHandler.ApproveAll, - }); + await using var session = await Ctx.CreateSessionAsync( + client, + CreateExtensionsSessionConfig()); await WaitForExtensionAsync(session, extId, ExtensionStatus.Running); diff --git a/dotnet/test/E2E/RpcMcpAndSkillsE2ETests.cs b/dotnet/test/E2E/RpcMcpAndSkillsE2ETests.cs index 0d2942d4bf..ec923b31a1 100644 --- a/dotnet/test/E2E/RpcMcpAndSkillsE2ETests.cs +++ b/dotnet/test/E2E/RpcMcpAndSkillsE2ETests.cs @@ -33,7 +33,7 @@ public async Task Should_List_And_Toggle_Session_Skills() { var skillName = $"session-rpc-skill-{Guid.NewGuid():N}"; var skillsDir = CreateSkillDirectory(skillName, "Session skill controlled by RPC."); - var session = await CreateSessionAsync(new SessionConfig + await using var session = await CreateSessionAsync(new SessionConfig { SkillDirectories = [skillsDir], DisabledSkills = [skillName], @@ -56,7 +56,7 @@ public async Task Should_Ensure_Skills_Are_Loaded_And_List_Invoked_Skills() { var skillName = $"ensure-rpc-skill-{Guid.NewGuid():N}"; var skillsDir = CreateSkillDirectory(skillName, "Skill loaded explicitly by RPC."); - var session = await CreateSessionAsync(new SessionConfig + await using var session = await CreateSessionAsync(new SessionConfig { SkillDirectories = [skillsDir], }); @@ -79,7 +79,7 @@ public async Task Should_Reload_Session_Skills() Directory.CreateDirectory(skillsDir); var skillName = $"reload-rpc-skill-{Guid.NewGuid():N}"; - var session = await CreateSessionAsync(new SessionConfig { SkillDirectories = [skillsDir] }); + await using var session = await CreateSessionAsync(new SessionConfig { SkillDirectories = [skillsDir] }); var before = await session.Rpc.Skills.ListAsync(); Assert.DoesNotContain(before.Skills, skill => string.Equals(skill.Name, skillName, StringComparison.Ordinal)); @@ -95,7 +95,7 @@ public async Task Should_Reload_Session_Skills() public async Task Should_List_Mcp_Servers_With_Configured_Server() { const string serverName = "rpc-list-mcp-server"; - var session = await CreateSessionAsync(new SessionConfig + await using var session = await CreateSessionAsync(new SessionConfig { McpServers = CreateTestMcpServers(serverName), }); @@ -111,7 +111,7 @@ public async Task Should_List_Mcp_Servers_With_Configured_Server() public async Task Should_Set_Mcp_Env_Value_Mode_And_Remove_GitHub_Server() { const string serverName = "github"; - var session = await CreateSessionAsync(new SessionConfig + await using var session = await CreateSessionAsync(new SessionConfig { McpServers = CreateTestMcpServers(serverName), }); @@ -137,7 +137,7 @@ public async Task Should_Set_Mcp_Env_Value_Mode_And_Remove_GitHub_Server() public async Task Should_Report_Mcp_Sampling_Failure_And_Cancel_Missing_Sampling() { const string serverName = "rpc-sampling-server"; - var session = await CreateSessionAsync(new SessionConfig + await using var session = await CreateSessionAsync(new SessionConfig { McpServers = CreateTestMcpServers(serverName), }); @@ -172,7 +172,7 @@ public async Task Should_Report_Mcp_Sampling_Failure_And_Cancel_Missing_Sampling [Fact] public async Task Should_List_Plugins() { - var session = await CreateSessionAsync(); + await using var session = await CreateSessionAsync(); var result = await session.Rpc.Plugins.ListAsync(); @@ -314,7 +314,7 @@ public async Task Should_Report_Error_When_Mcp_App_Resource_Is_Not_Available() [Fact] public async Task Should_Report_Error_When_Mcp_Host_Is_Not_Initialized() { - var session = await CreateSessionAsync(); + await using var session = await CreateSessionAsync(); await AssertFailureAsync( () => session.Rpc.Mcp.EnableAsync("missing-server"), @@ -333,7 +333,7 @@ await AssertFailureAsync( [Fact] public async Task Should_Report_Error_When_Mcp_Oauth_Server_Is_Not_Configured() { - var session = await CreateSessionAsync(new SessionConfig + await using var session = await CreateSessionAsync(new SessionConfig { McpServers = CreateTestMcpServers("configured-stdio-server"), }); @@ -348,7 +348,7 @@ await AssertFailureAsync( public async Task Should_Report_Error_When_Mcp_Oauth_Server_Is_Not_Remote() { const string serverName = "configured-stdio-server"; - var session = await CreateSessionAsync(new SessionConfig + await using var session = await CreateSessionAsync(new SessionConfig { McpServers = CreateTestMcpServers(serverName), }); diff --git a/dotnet/test/E2E/RpcServerE2ETests.cs b/dotnet/test/E2E/RpcServerE2ETests.cs index 2df8593cc4..5e39c5ca5a 100644 --- a/dotnet/test/E2E/RpcServerE2ETests.cs +++ b/dotnet/test/E2E/RpcServerE2ETests.cs @@ -171,7 +171,7 @@ public async Task Should_Call_Rpc_Models_List_With_Typed_Result() var result = await client.Rpc.Models.ListAsync(); Assert.NotNull(result.Models); - Assert.Contains(result.Models, model => model.Id == "claude-sonnet-4.5"); + Assert.Contains(result.Models, model => model.Id == "claude-sonnet-5"); Assert.All(result.Models, model => Assert.False(string.IsNullOrWhiteSpace(model.Name))); } diff --git a/dotnet/test/E2E/RpcServerMiscE2ETests.cs b/dotnet/test/E2E/RpcServerMiscE2ETests.cs index 29e560100e..20993e2ec0 100644 --- a/dotnet/test/E2E/RpcServerMiscE2ETests.cs +++ b/dotnet/test/E2E/RpcServerMiscE2ETests.cs @@ -92,7 +92,7 @@ public async Task Should_Login_List_GetCurrentAuth_And_Logout_Account() var initial = await client.Rpc.Account.GetCurrentAuthAsync(); Assert.Null(initial.AuthInfo); - var loginResult = await client.Rpc.Account.LoginAsync("https://github.com", login, token); + var loginResult = await client.Rpc.Account.LoginAsync("https://github.com", token, login); Assert.NotNull(loginResult); var current = await client.Rpc.Account.GetCurrentAuthAsync(); @@ -111,7 +111,7 @@ user.AuthInfo is AuthInfoUser userAuth Assert.Equal(token, account.Token); } - var logout = await client.Rpc.Account.LogoutAsync(authInfo); + var logout = await client.Rpc.Account.LogoutAsync(authInfo: authInfo); Assert.False(logout.HasMoreUsers); var afterLogout = await client.Rpc.Account.GetCurrentAuthAsync(); diff --git a/dotnet/test/E2E/RpcSessionStateE2ETests.cs b/dotnet/test/E2E/RpcSessionStateE2ETests.cs index 6dce3c250f..196861cd9b 100644 --- a/dotnet/test/E2E/RpcSessionStateE2ETests.cs +++ b/dotnet/test/E2E/RpcSessionStateE2ETests.cs @@ -22,14 +22,14 @@ private static async Task AssertImplementedFailureAsync(Func ac [Fact] public async Task Should_Call_Session_Rpc_Model_GetCurrent() { - await using var session = await CreateSessionAsync(new SessionConfig { Model = "claude-sonnet-4.5" }); + await using var session = await CreateSessionAsync(new SessionConfig { Model = "claude-sonnet-5" }); var result = await session.Rpc.Model.GetCurrentAsync(); Assert.NotNull(result.ModelId); Assert.NotEmpty(result.ModelId); // Strengthen: verify the configured model is actually in effect, not just any model - Assert.Equal("claude-sonnet-4.5", result.ModelId); + Assert.Equal("claude-sonnet-5", result.ModelId); } [Fact] @@ -48,12 +48,12 @@ public async Task Should_Call_Session_Rpc_Model_SwitchTo() await using var session = await isolatedCtx.CreateSessionAsync(isolatedClient, new SessionConfig { - Model = "claude-sonnet-4.5", + Model = "claude-sonnet-5", OnPermissionRequest = PermissionHandler.ApproveAll, }); var before = await session.Rpc.Model.GetCurrentAsync(); - Assert.Equal("claude-sonnet-4.5", before.ModelId); + Assert.Equal("claude-sonnet-5", before.ModelId); var result = await session.Rpc.Model.SwitchToAsync(modelId: "gpt-5.4", reasoningEffort: "high"); Assert.Equal("gpt-5.4", result.ModelId); @@ -281,14 +281,14 @@ public async Task Should_Call_Metadata_Snapshot_SetWorkingDirectory_And_RecordCo var branch = $"rpc-context-{Guid.NewGuid():N}"; await using var session = await CreateSessionAsync(new SessionConfig { - Model = "claude-sonnet-4.5", + Model = "claude-sonnet-5", WorkingDirectory = firstDirectory, }); var initialSnapshot = await session.Rpc.Metadata.SnapshotAsync(); Assert.Equal(session.SessionId, initialSnapshot.SessionId); Assert.Equal(MetadataSnapshotCurrentMode.Interactive, initialSnapshot.CurrentMode); - Assert.Equal("claude-sonnet-4.5", initialSnapshot.SelectedModel); + Assert.Equal("claude-sonnet-5", initialSnapshot.SelectedModel); Assert.False(initialSnapshot.IsRemote); Assert.False(initialSnapshot.AlreadyInUse); Assert.NotEqual(default, initialSnapshot.StartTime); @@ -405,14 +405,14 @@ public async Task Should_Set_ReasoningEffort_And_Auto_Name() { await using var session = await CreateSessionAsync(new SessionConfig { - Model = "claude-sonnet-4.5", + Model = "claude-sonnet-5", }); var reasoning = await session.Rpc.Model.SetReasoningEffortAsync("high"); Assert.Equal("high", reasoning.ReasoningEffort); var currentModel = await session.Rpc.Model.GetCurrentAsync(); - Assert.Equal("claude-sonnet-4.5", currentModel.ModelId); + Assert.Equal("claude-sonnet-5", currentModel.ModelId); Assert.Equal("high", currentModel.ReasoningEffort); var autoName = $"Auto Session {Guid.NewGuid():N}"; @@ -451,7 +451,7 @@ public async Task Should_Set_Auth_Credentials() }); var login = $"sdk-rpc-{Guid.NewGuid():N}"; - var setCredentials = await session.Rpc.GitHubAuth.SetCredentialsAsync(new AuthInfoUser + var setCredentials = await session.Rpc.GitHubAuth.SetCredentialsAsync(new SettableAuthInfoUser { CopilotUser = new CopilotUserResponse { @@ -653,9 +653,9 @@ public async Task Should_Compact_Session_History_After_Messages() var contextInfo = await session.Rpc.Metadata.ContextInfoAsync( promptTokenLimit: 128_000, outputTokenLimit: 4_096, - selectedModel: "claude-sonnet-4.5"); + selectedModel: "claude-sonnet-5"); var context = Assert.IsType(contextInfo.ContextInfo); - Assert.Equal("claude-sonnet-4.5", context.ModelName); + Assert.Equal("claude-sonnet-5", context.ModelName); Assert.Equal(128_000, context.PromptTokenLimit); Assert.True(context.Limit >= context.PromptTokenLimit); Assert.True(context.TotalTokens > 0); @@ -666,7 +666,7 @@ public async Task Should_Compact_Session_History_After_Messages() context.SystemTokens + context.ConversationTokens + context.ToolDefinitionsTokens, context.TotalTokens); - var recomputed = await session.Rpc.Metadata.RecomputeContextTokensAsync("claude-sonnet-4.5"); + var recomputed = await session.Rpc.Metadata.RecomputeContextTokensAsync("claude-sonnet-5"); Assert.True(recomputed.SystemTokenCount > 0); Assert.True(recomputed.MessagesTokenCount > 0); Assert.Equal(recomputed.SystemTokenCount + recomputed.MessagesTokenCount, recomputed.TotalTokens); diff --git a/dotnet/test/E2E/RpcSessionStateExtrasE2ETests.cs b/dotnet/test/E2E/RpcSessionStateExtrasE2ETests.cs index 28ec9b7cfe..234a5d8694 100644 --- a/dotnet/test/E2E/RpcSessionStateExtrasE2ETests.cs +++ b/dotnet/test/E2E/RpcSessionStateExtrasE2ETests.cs @@ -12,7 +12,7 @@ namespace GitHub.Copilot.Test.E2E; /// /// E2E coverage for session-scoped RPC methods that were previously untested: /// completions, model.list, metadata.activity/context attribution/heaviest messages, -/// permissions.getAllowAll/setAllowAll, plan.readSqlTodos, provider.add, +/// permissions.getMode/setMode, plan.readSqlTodos, provider.add, /// telemetry.getEngagementId, tools.getCurrentMetadata/updateSubagentSettings, /// session visibility, and the session-scoped plugins.reload. /// @@ -32,7 +32,7 @@ public async Task Should_List_Models_For_Session() await using var client = CreateAuthenticatedClient(token); await using var session = await Ctx.CreateSessionAsync(client, new SessionConfig { - Model = "claude-sonnet-4.5", + Model = "claude-sonnet-5", OnPermissionRequest = PermissionHandler.ApproveAll, }); @@ -41,7 +41,7 @@ public async Task Should_List_Models_For_Session() Assert.NotNull(result.List); Assert.NotEmpty(result.List); // The configured model must be present in the returned catalog. - Assert.Contains(result.List, model => model.GetRawText().Contains("claude-sonnet-4.5", StringComparison.Ordinal)); + Assert.Contains(result.List, model => model.GetRawText().Contains("claude-sonnet-5", StringComparison.Ordinal)); } [Fact] @@ -73,7 +73,7 @@ public async Task Should_Add_Byok_Provider_And_Model_At_Runtime() Provider = providerName, Id = modelId, Name = "SDK Runtime Model", - ModelId = "claude-sonnet-4.5", + ModelId = "claude-sonnet-5", WireModel = "wire-sdk-runtime-model", MaxContextWindowTokens = 4_096, MaxPromptTokens = 3_072, @@ -158,22 +158,22 @@ public async Task Should_Get_And_Set_AllowAll_Permissions() try { - var initial = await session.Rpc.Permissions.GetAllowAllAsync(); - Assert.False(initial.Enabled, "Allow-all should be disabled on a fresh session."); + var initial = await session.Rpc.Permissions.GetModeAsync(); + Assert.Equal(PermissionMode.Manual, initial.Mode); - var enable = await session.Rpc.Permissions.SetAllowAllAsync(enabled: true); + var enable = await session.Rpc.Permissions.SetModeAsync(PermissionMode.AllowAll); Assert.True(enable.Success); - Assert.True(enable.Enabled); - Assert.True((await session.Rpc.Permissions.GetAllowAllAsync()).Enabled); + Assert.Equal(PermissionMode.AllowAll, enable.Mode); + Assert.Equal(PermissionMode.AllowAll, (await session.Rpc.Permissions.GetModeAsync()).Mode); - var disable = await session.Rpc.Permissions.SetAllowAllAsync(enabled: false); + var disable = await session.Rpc.Permissions.SetModeAsync(PermissionMode.Manual); Assert.True(disable.Success); - Assert.False(disable.Enabled); - Assert.False((await session.Rpc.Permissions.GetAllowAllAsync()).Enabled); + Assert.Equal(PermissionMode.Manual, disable.Mode); + Assert.Equal(PermissionMode.Manual, (await session.Rpc.Permissions.GetModeAsync()).Mode); } finally { - await session.Rpc.Permissions.SetAllowAllAsync(enabled: false); + await session.Rpc.Permissions.SetModeAsync(PermissionMode.Manual); } } @@ -277,7 +277,7 @@ public async Task Should_Update_And_Clear_Live_Subagent_Settings() { ["general-purpose"] = new() { - Model = "claude-sonnet-4.5", + Model = "claude-sonnet-5", EffortLevel = "high", ContextTier = SubagentSettingsEntryContextTier.Default, }, diff --git a/dotnet/test/E2E/RpcUiEphemeralQueryE2ETests.cs b/dotnet/test/E2E/RpcUiEphemeralQueryE2ETests.cs index 76043fd73c..a6b7877868 100644 --- a/dotnet/test/E2E/RpcUiEphemeralQueryE2ETests.cs +++ b/dotnet/test/E2E/RpcUiEphemeralQueryE2ETests.cs @@ -19,7 +19,10 @@ namespace GitHub.Copilot.Test.E2E; public class RpcUiEphemeralQueryE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "rpc_ui_ephemeral_query", output) { - [Fact] + // TODO(cli-1.0.81-2): CLI 1.0.81-5 still fails session.ui.ephemeralQuery against the + // recorded snapshot on macOS ("Failed to get response from the AI model"). Re-enable + // once the runtime fix ships. + [Fact(Skip = "Blocked on CLI 1.0.81-5 session.ui.ephemeralQuery regression on macOS")] public async Task Should_Answer_Ephemeral_Query() { await using var session = await CreateSessionAsync(); diff --git a/dotnet/test/E2E/SessionConfigE2ETests.cs b/dotnet/test/E2E/SessionConfigE2ETests.cs index 1bc4c52eb9..314ba2f0cf 100644 --- a/dotnet/test/E2E/SessionConfigE2ETests.cs +++ b/dotnet/test/E2E/SessionConfigE2ETests.cs @@ -31,7 +31,7 @@ public async Task Vision_Disabled_Then_Enabled_Via_SetModel() var session = await CreateSessionAsync(new SessionConfig { - Model = "claude-sonnet-4.5", + Model = "claude-sonnet-5", ModelCapabilities = new ModelCapabilitiesOverride { Supports = new ModelCapabilitiesOverrideSupports { Vision = false }, @@ -46,7 +46,7 @@ public async Task Vision_Disabled_Then_Enabled_Via_SetModel() // Switch vision on await session.SetModelAsync( - "claude-sonnet-4.5", + "claude-sonnet-5", reasoningEffort: null, modelCapabilities: new ModelCapabilitiesOverride { @@ -74,7 +74,7 @@ public async Task Vision_Enabled_Then_Disabled_Via_SetModel() var session = await CreateSessionAsync(new SessionConfig { - Model = "claude-sonnet-4.5", + Model = "claude-sonnet-5", ModelCapabilities = new ModelCapabilitiesOverride { Supports = new ModelCapabilitiesOverrideSupports { Vision = true }, @@ -89,7 +89,7 @@ public async Task Vision_Enabled_Then_Disabled_Via_SetModel() // Switch vision off await session.SetModelAsync( - "claude-sonnet-4.5", + "claude-sonnet-5", reasoningEffort: null, modelCapabilities: new ModelCapabilitiesOverride { @@ -216,7 +216,7 @@ public async Task Should_Forward_Custom_Provider_Headers_On_Create() { var session = await CreateSessionAsync(new SessionConfig { - Model = "claude-sonnet-4.5", + Model = "claude-sonnet-5", Provider = CreateProxyProvider("create-provider-header"), }); @@ -240,7 +240,7 @@ public async Task Should_Forward_Custom_Provider_Headers_On_Resume() var session2 = await ResumeSessionAsync(sessionId, new ResumeSessionConfig { - Model = "claude-sonnet-4.5", + Model = "claude-sonnet-5", Provider = CreateProxyProvider("resume-provider-header"), }); @@ -267,7 +267,7 @@ public async Task Should_Forward_Provider_Wire_Model() // tests for serialization coverage). var session = await CreateSessionAsync(new SessionConfig { - Model = "claude-sonnet-4.5", + Model = "claude-sonnet-5", Provider = new ProviderConfig { Type = "openai", @@ -300,14 +300,14 @@ public async Task Should_Use_Provider_Model_Id_As_Wire_Model() Type = "openai", BaseUrl = Ctx.ProxyUrl, ApiKey = "test-provider-key", - ModelId = "claude-sonnet-4.5", + ModelId = "claude-sonnet-5", }, }); await session.SendAndWaitAsync(new MessageOptions { Prompt = "What is 1+1?" }); var exchange = Assert.Single(await Ctx.GetExchangesAsync()); - Assert.Equal("claude-sonnet-4.5", exchange.Request.Model); + Assert.Equal("claude-sonnet-5", exchange.Request.Model); await session.DisposeAsync(); } @@ -598,7 +598,7 @@ public async Task Should_Enable_Citations_For_Anthropic_File_Attachments_On_Crea var session = await Ctx.CreateSessionAsync(client, new SessionConfig { OnPermissionRequest = PermissionHandler.ApproveAll, - Model = "claude-sonnet-4.5", + Model = "claude-sonnet-5", EnableCitations = true, Provider = CreateAnthropicProvider(), }); @@ -645,7 +645,7 @@ public async Task Should_Enable_Citations_For_Anthropic_File_Attachments_On_Resu var session2 = await Ctx.ResumeSessionAsync(resumeClient, sessionId, new ResumeSessionConfig { OnPermissionRequest = PermissionHandler.ApproveAll, - Model = "claude-sonnet-4.5", + Model = "claude-sonnet-5", EnableCitations = true, Provider = CreateAnthropicProvider(), }); @@ -834,8 +834,8 @@ private static ProviderConfig CreateAnthropicProvider() Type = "anthropic", BaseUrl = "https://anthropic-citations.invalid/v1", ApiKey = "test-provider-key", - ModelId = "claude-sonnet-4.5", - WireModel = "claude-sonnet-4.5", + ModelId = "claude-sonnet-5", + WireModel = "claude-sonnet-5", }; } diff --git a/dotnet/test/E2E/SessionE2ETests.cs b/dotnet/test/E2E/SessionE2ETests.cs index 27ef7437f7..aababb670b 100644 --- a/dotnet/test/E2E/SessionE2ETests.cs +++ b/dotnet/test/E2E/SessionE2ETests.cs @@ -17,7 +17,7 @@ public class SessionE2ETests(E2ETestFixture fixture, ITestOutputHelper output) : [Fact] public async Task ShouldCreateAndDisconnectSessions() { - var session = await CreateSessionAsync(new SessionConfig { Model = "claude-sonnet-4.5" }); + var session = await CreateSessionAsync(new SessionConfig { Model = "claude-sonnet-5" }); Assert.Matches(@"^[a-f0-9-]+$", session.SessionId); diff --git a/dotnet/test/E2E/SessionEventLoopLeakE2ETests.cs b/dotnet/test/E2E/SessionEventLoopLeakE2ETests.cs new file mode 100644 index 0000000000..40470109c5 --- /dev/null +++ b/dotnet/test/E2E/SessionEventLoopLeakE2ETests.cs @@ -0,0 +1,198 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +using GitHub.Copilot.Test.Harness; +using System.Collections; +using System.Reflection; +using Xunit; +using Xunit.Abstractions; + +namespace GitHub.Copilot.Test.E2E; + +/// +/// Regression coverage for the goroutine/task leak fixed alongside +/// github/copilot-sdk#2360: and +/// construct a +/// and start its event-dispatch consumer (ProcessEventsAsync) eagerly, before the +/// CLI confirms the session, so the CLI can route session-scoped requests to it while +/// session.create (or session.resume) is still being processed. Every failure path must +/// stop that consumer — otherwise each failed call leaks a background task forever, since +/// no caller ever receives the failed session to dispose it. Mirrors +/// go/internal/e2e/session_event_loop_leak_e2e_test.go. +/// +[Trait(E2ETestTraits.Backend, E2ETestTraits.CapiOnly)] +public class SessionEventLoopLeakE2ETests(E2ETestFixture fixture, ITestOutputHelper output) + : E2ETestBase(fixture, "session-event-loop-leak", output) +{ + private static readonly FieldInfo SessionsField = + typeof(CopilotClient).GetField("_sessions", BindingFlags.Instance | BindingFlags.NonPublic) + ?? throw new InvalidOperationException("CopilotClient._sessions was not found."); + + private static readonly FieldInfo EventChannelField = + typeof(CopilotSession).GetField("_eventChannel", BindingFlags.Instance | BindingFlags.NonPublic) + ?? throw new InvalidOperationException("CopilotSession._eventChannel was not found."); + + private static IDictionary GetSessionsMap(CopilotClient client) => + (IDictionary)SessionsField.GetValue(client)!; + + private static bool IsEventChannelClosed(CopilotSession session) + { + var channel = EventChannelField.GetValue(session)!; + var readerProperty = channel.GetType().GetProperty("Reader") + ?? throw new InvalidOperationException("Channel.Reader was not found."); + var reader = readerProperty.GetValue(channel)!; + var completionProperty = reader.GetType().GetProperty("Completion") + ?? throw new InvalidOperationException("ChannelReader.Completion was not found."); + var completion = (Task)completionProperty.GetValue(reader)!; + return completion.IsCompleted; + } + + /// + /// Continuously scans 's session dictionary on a dedicated, + /// tightly-spinning thread (not the thread pool, and no await-based yielding) so + /// that even a sub-millisecond in-flight registration window — as seen with a fast local + /// RPC failure like a nonexistent-session resume — is reliably observed. + /// + private sealed class SessionSniffer : IDisposable + { + private readonly List _seen = []; + private readonly Thread _thread; + private volatile bool _stop; + + public SessionSniffer(IDictionary sessions) + { + _thread = new Thread(() => + { + var iterations = 0L; + while (!_stop) + { + iterations++; + foreach (CopilotSession s in sessions.Values) + { + lock (_seen) + { + if (!_seen.Contains(s)) _seen.Add(s); + } + } + } + Iterations = iterations; + }) + { IsBackground = true }; + _thread.Start(); + } + + public long Iterations { get; private set; } + + public IReadOnlyList Stop() + { + _stop = true; + _thread.Join(); + lock (_seen) return [.. _seen]; + } + + public void Dispose() => _stop = true; + } + + [Fact] + public async Task CreateSessionAsync_Failure_Does_Not_Leak_The_Session_Or_Its_Event_Loop() + { + // An invalid per-session GitHub token, redirected at the replaying proxy, makes + // the real CLI reject session.create with a genuine RPC error (401 Unauthorized) + // — the same failure path a real user would hit, not a mocked transport. + var env = new Dictionary(Ctx.GetEnvironment()) + { + ["COPILOT_DEBUG_GITHUB_API_URL"] = Ctx.ProxyUrl, + }; + var client = Ctx.CreateClient(environment: env, autoInjectGitHubToken: false); + + async Task CreateFailingAsync() + { + var ex = await Assert.ThrowsAnyAsync(() => Ctx.CreateSessionAsync(client, new SessionConfig + { + GitHubToken = "invalid-token", + OnPermissionRequest = PermissionHandler.ApproveAll, + })); + Assert.Contains("401", ex.ToString(), StringComparison.OrdinalIgnoreCase); + } + + // Warm up: the first call establishes the CLI connection. + await CreateFailingAsync(); + + var sessions = GetSessionsMap(client); + + // The session is registered (and its event-loop consumer started) before the RPC + // completes, and is only ever removed inside CreateSessionAsync's own catch block — + // by the time a failed call *returns* to us, RemoveFromClient() has already run, so + // polling the dictionary after each await observes nothing. We must instead observe + // it concurrently, while each call is still in flight, to capture the real session + // object and verify its event channel actually got closed. + using var sniffer = new SessionSniffer(sessions); + + for (var i = 0; i < 20; i++) + { + await CreateFailingAsync(); + } + + var seen = sniffer.Stop(); + + Assert.True(seen.Count > 0, "Test did not observe any in-flight session registrations; cannot validate the fix."); + Assert.Empty(sessions); + + foreach (var s in seen) + { + Assert.True(IsEventChannelClosed(s), "A failed CreateSessionAsync's session had its event channel left open, leaking its background event-processing task."); + } + } + + [Fact] + public async Task ResumeSessionAsync_Failure_Does_Not_Leak_The_Session_Or_Its_Event_Loop() + { + // Use our own dedicated client rather than the shared fixture's ResumeSessionAsync + // helper: that helper spins up a brand-new CopilotClient for every call it makes + // (to support the multi-client resume scenarios it's designed for), so each call's + // pre-registered session would land in a different, short-lived client's dictionary + // that we'd never get to observe. A single, reused client lets us watch one + // dictionary across all 20 failed calls. + var client = Ctx.CreateClient(); + var sessions = GetSessionsMap(client); + + async Task ResumeNonExistentAsync() + { + await Assert.ThrowsAnyAsync(() => + Ctx.ResumeSessionAsync(client, "non-existent-leak-check-session", new ResumeSessionConfig + { + OnPermissionRequest = PermissionHandler.ApproveAll, + })); + } + + // Warm up: the first call establishes the CLI connection. + await ResumeNonExistentAsync(); + + // Same rationale as the CreateSessionAsync test above: the pre-registered session is + // already removed from the dictionary by the time a failed call returns, so we must + // observe it concurrently, while the call is still in flight, to actually validate + // that its event channel got closed rather than just that it got unregistered. + using var sniffer = new SessionSniffer(sessions); + + var baseline = sessions.Count; + for (var i = 0; i < 20; i++) + { + await ResumeNonExistentAsync(); + } + + var seen = sniffer.Stop(); + + Assert.True(seen.Count > 0, $"Test did not observe any in-flight session registrations (sniffer ran {sniffer.Iterations} iterations); cannot validate the fix."); + + Assert.True( + sessions.Count == baseline, + $"Expected no sessions left registered after 20 failed ResumeSessionAsync calls (baseline={baseline}), " + + $"but found {sessions.Count}. Failed ResumeSessionAsync calls must not leak the local session registration."); + + foreach (var s in seen) + { + Assert.True(IsEventChannelClosed(s), "A failed ResumeSessionAsync's session had its event channel left open, leaking its background event-processing task."); + } + } +} diff --git a/dotnet/test/E2E/SessionFsE2ETests.cs b/dotnet/test/E2E/SessionFsE2ETests.cs index cc02b5abfa..146e47d808 100644 --- a/dotnet/test/E2E/SessionFsE2ETests.cs +++ b/dotnet/test/E2E/SessionFsE2ETests.cs @@ -337,6 +337,8 @@ await session.SendAndWaitAsync(new MessageOptions var messages = await session.GetEventsAsync(); var toolResult = FindToolCallResult(messages, "get_big_string"); Assert.NotNull(toolResult); + // Windows reports the spilled file path with backslash separators. + toolResult = toolResult!.Replace('\\', '/'); Assert.Contains($"{SessionFsConfig.SessionStatePath}/temp/", toolResult); var match = System.Text.RegularExpressions.Regex.Match( diff --git a/dotnet/test/E2E/TelemetryExportE2ETests.cs b/dotnet/test/E2E/TelemetryExportE2ETests.cs index 48e3b53ad6..e2ad447e26 100644 --- a/dotnet/test/E2E/TelemetryExportE2ETests.cs +++ b/dotnet/test/E2E/TelemetryExportE2ETests.cs @@ -54,9 +54,6 @@ public async Task Should_Export_File_Telemetry_For_Sdk_Interactions() Assert.NotEmpty(spans); Assert.All(spans, span => Assert.Equal(sourceName, GetInstrumentationScopeName(span))); - // All spans for one SDK turn must share the same trace id and must not be in error state. - var traceIds = spans.Select(GetTraceId).Where(id => !string.IsNullOrEmpty(id)).Distinct().ToList(); - Assert.Single(traceIds); Assert.All(spans, span => Assert.NotEqual(2, GetStatusCode(span))); var invokeAgentSpan = AssertSpanWithOperation(spans, "invoke_agent"); @@ -65,10 +62,13 @@ public async Task Should_Export_File_Telemetry_For_Sdk_Interactions() "invoke_agent should be the root of the SDK turn trace."); var invokeAgentSpanId = GetSpanId(invokeAgentSpan); Assert.False(string.IsNullOrEmpty(invokeAgentSpanId)); + var invokeAgentTraceId = GetTraceId(invokeAgentSpan); + Assert.False(string.IsNullOrEmpty(invokeAgentTraceId)); var chatSpans = spans.Where(span => IsSpanWithOperation(span, "chat")).ToList(); Assert.NotEmpty(chatSpans); Assert.All(chatSpans, chat => Assert.Equal(invokeAgentSpanId, GetParentSpanId(chat))); + Assert.All(chatSpans, chat => Assert.Equal(invokeAgentTraceId, GetTraceId(chat))); Assert.Contains( chatSpans, span => (GetStringAttribute(span, "gen_ai.input.messages") ?? string.Empty).Contains(prompt, StringComparison.Ordinal)); @@ -78,6 +78,7 @@ public async Task Should_Export_File_Telemetry_For_Sdk_Interactions() var toolSpan = AssertSpanWithOperation(spans, "execute_tool"); Assert.Equal(invokeAgentSpanId, GetParentSpanId(toolSpan)); + Assert.Equal(invokeAgentTraceId, GetTraceId(toolSpan)); Assert.Equal(toolName, GetStringAttribute(toolSpan, "gen_ai.tool.name")); Assert.False(string.IsNullOrWhiteSpace(GetStringAttribute(toolSpan, "gen_ai.tool.call.id")), "execute_tool span should carry gen_ai.tool.call.id."); diff --git a/dotnet/test/Harness/E2ETestBackend.cs b/dotnet/test/Harness/E2ETestBackend.cs index 04808ed7a9..71c8f85761 100644 --- a/dotnet/test/Harness/E2ETestBackend.cs +++ b/dotnet/test/Harness/E2ETestBackend.cs @@ -15,7 +15,7 @@ internal enum E2ETestBackend internal static class E2ETestBackendConfiguration { internal const string EnvironmentVariable = "COPILOT_SDK_E2E_BACKEND"; - private const string AnthropicDefaultModel = "claude-sonnet-4.5"; + private const string AnthropicDefaultModel = "claude-sonnet-5"; private const string OpenAIDefaultModel = "gpt-4.1"; private const string FakeCredential = "fake-byok-credential-for-e2e-tests"; diff --git a/dotnet/test/Harness/E2ETestContext.cs b/dotnet/test/Harness/E2ETestContext.cs index af34de6ec7..2554c6a4d1 100644 --- a/dotnet/test/Harness/E2ETestContext.cs +++ b/dotnet/test/Harness/E2ETestContext.cs @@ -3,9 +3,9 @@ *--------------------------------------------------------------------------------------------*/ using Microsoft.Extensions.Logging; +using System.Collections.Concurrent; using System.Diagnostics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Text.RegularExpressions; namespace GitHub.Copilot.Test.Harness; @@ -13,6 +13,8 @@ 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); + private static readonly ConcurrentDictionary> s_preparedCliPaths = new(StringComparer.Ordinal); public string HomeDir { get; } public string WorkDir { get; } @@ -24,6 +26,8 @@ public sealed class E2ETestContext : IAsyncDisposable private readonly ReplayProxy _proxy; private readonly string _repoRoot; + private readonly Lazy _cliPath; + private readonly Lazy _legacyCliPath; private readonly object _clientsLock = new(); private readonly List _persistentClients = []; private readonly List _transientClients = []; @@ -35,10 +39,18 @@ private E2ETestContext(string homeDir, string workDir, string proxyUrl, ReplayPr ProxyUrl = proxyUrl; _proxy = proxy; _repoRoot = repoRoot; + _cliPath = GetCachedCliPath(repoRoot, "--print-path"); + _legacyCliPath = GetCachedCliPath(repoRoot, "--print-legacy-path"); } public static async Task CreateAsync() { + // A previous in-process context may have left this process's cwd inside a work + // directory that has since been deleted. getcwd() then fails, which breaks + // Process.Start below while it resolves the proxy executable. Repoint the cwd at + // the ambient value first; SetCurrentDirectory succeeds even if the old cwd is gone. + InProcessEnvIsolation.RestoreAmbientWorkingDirectory(); + var repoRoot = FindRepoRoot(); var homeDir = Path.Combine(Path.GetTempPath(), $"copilot-test-config-{Guid.NewGuid()}"); @@ -137,46 +149,56 @@ private static string FindRepoRoot() throw new InvalidOperationException("Could not find repository root"); } - private static string GetCliPath(string repoRoot) + private string GetCliPath() + => _cliPath.Value; + + public string GetLegacyCliPath() + => _legacyCliPath.Value; + + private static Lazy GetCachedCliPath(string repoRoot, string option) + { + var envPath = option == "--print-path" + ? Environment.GetEnvironmentVariable("COPILOT_CLI_PATH") + : null; + var cacheKey = $"{repoRoot}\0{option}\0{envPath}"; + return s_preparedCliPaths.GetOrAdd( + cacheKey, + _ => new Lazy( + () => PrepareCliPath(repoRoot, option), + LazyThreadSafetyMode.ExecutionAndPublication)); + } + + private static string PrepareCliPath(string repoRoot, string option) { var envPath = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"); - 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. - var githubModules = Path.Join(repoRoot, "nodejs", "node_modules", "@github"); - 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 + if (option == "--print-path" && !string.IsNullOrEmpty(envPath)) return envPath; + + var startInfo = new ProcessStartInfo { - 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))), + FileName = "node", + WorkingDirectory = Path.Join(repoRoot, "nodejs"), + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + Arguments = $"node_modules/tsx/dist/cli.mjs scripts/prepare-runtime.ts {option}", }; - } - 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}"; + using var process = Process.Start(startInfo) + ?? throw new InvalidOperationException("Failed to start Node.js runtime preparation."); + var stdout = process.StandardOutput.ReadToEndAsync(); + var stderr = process.StandardError.ReadToEndAsync(); + process.WaitForExit(); + var output = stdout.GetAwaiter().GetResult(); + var lines = output.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); + var cliPath = lines.Length == 0 ? string.Empty : lines[^1].Trim(); + var error = stderr.GetAwaiter().GetResult().Trim(); + if (process.ExitCode != 0 || string.IsNullOrEmpty(cliPath)) + throw new InvalidOperationException( + $"Failed to prepare the pinned Copilot CLI: {error}"); + if (!File.Exists(cliPath)) + throw new InvalidOperationException( + $"Pinned Copilot CLI was not created at {cliPath}."); + return cliPath; } public async Task ConfigureForTestAsync(string testFile, [CallerMemberName] string? testName = null) @@ -303,21 +325,20 @@ public CopilotClient CreateClient( // CopilotClient honors COPILOT_SDK_DEFAULT_CONNECTION (stdio by default, // or in-process); the CI matrix uses this to run the suite under both. // Tests that need a specific transport set options.Connection directly. - var cliPath = GetCliPath(_repoRoot); switch (options.Connection) { case null when !IsInProcess(null): // No explicit connection and not the in-process default: the // default resolves to stdio, so materialize it here so the // environment can be attached to the connection below. - options.Connection = RuntimeConnection.ForStdio(path: cliPath); + options.Connection = RuntimeConnection.ForStdio(path: GetCliPath()); break; case null: // In-process default: leave Connection unset so CopilotClient's // ResolveDefaultConnection honors COPILOT_SDK_DEFAULT_CONNECTION. break; case ChildProcessRuntimeConnection child when child.Path is null: - child.Path = cliPath; + child.Path = GetCliPath(); break; } @@ -477,6 +498,14 @@ public async ValueTask DisposeAsync() var isCI = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("GITHUB_ACTIONS")); try { await _proxy.StopAsync(skipWritingCache: isCI); } catch (Exception ex) when (IsTransientCleanupException(ex)) { errors.Add(ex); } + // The in-process worker inherits this process's cwd, so ApplyInProcessEnvironment + // may have pointed it at WorkDir. The assembly-level isolation attribute only + // restores it around tests that actually execute, so a statically skipped test + // can leave the cwd inside a directory this method is about to delete. Every + // later Process.Start would then fail resolving its executable because getcwd() + // returns ENOENT. Repoint the cwd before deleting anything. + InProcessEnvIsolation.RestoreAmbientWorkingDirectory(); + try { await DeleteDirectoryAsync(HomeDir); } catch (Exception ex) when (IsTransientCleanupException(ex)) { errors.Add(ex); } try { await DeleteDirectoryAsync(WorkDir); } catch (Exception ex) when (IsTransientCleanupException(ex)) { errors.Add(ex); } @@ -549,7 +578,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( @@ -558,7 +586,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/InProcessEnvIsolation.cs b/dotnet/test/Harness/InProcessEnvIsolation.cs index af06001eda..e7a9c98235 100644 --- a/dotnet/test/Harness/InProcessEnvIsolation.cs +++ b/dotnet/test/Harness/InProcessEnvIsolation.cs @@ -67,6 +67,14 @@ public static void NeutralizeAmbientCredentials() public static void SetWorkingDirectory(string path) => Directory.SetCurrentDirectory(path); + // Repoints the process working directory at its load-time value. Callers must not + // read Directory.GetCurrentDirectory() first: an in-process test can chdir into a + // temp work dir that the harness then deletes, so getcwd() would throw + // FileNotFoundException. SetCurrentDirectory to an absolute path succeeds + // regardless of whether the old cwd still exists. + public static void RestoreAmbientWorkingDirectory() => + Directory.SetCurrentDirectory(s_ambientCwd); + public static void RestoreAmbient() { // Unconditionally repoint the process cwd at its load-time value. We must @@ -74,7 +82,7 @@ public static void RestoreAmbient() // chdir into a temp work dir that the harness then deletes, so getcwd() // would throw FileNotFoundException. SetCurrentDirectory to an absolute // path succeeds regardless of whether the old cwd still exists. - Directory.SetCurrentDirectory(s_ambientCwd); + RestoreAmbientWorkingDirectory(); foreach (DictionaryEntry entry in Environment.GetEnvironmentVariables()) { diff --git a/dotnet/test/Unit/ClientSessionLifetimeTests.cs b/dotnet/test/Unit/ClientSessionLifetimeTests.cs index a30e6d3030..a59257b3da 100644 --- a/dotnet/test/Unit/ClientSessionLifetimeTests.cs +++ b/dotnet/test/Unit/ClientSessionLifetimeTests.cs @@ -3,6 +3,7 @@ *--------------------------------------------------------------------------------------------*/ #if NET8_0_OR_GREATER +using System.Collections.Concurrent; using System.Net; using System.Net.Sockets; using System.Diagnostics; @@ -10,6 +11,7 @@ using System.Runtime.CompilerServices; using System.Text; using System.Text.Json; +using GitHub.Copilot.Rpc; using Xunit; namespace GitHub.Copilot.Test.Unit; @@ -18,6 +20,192 @@ public sealed class ClientSessionLifetimeTests { private sealed record RpcRequestRecord(string Method, JsonElement Params); + [Theory] + [InlineData("static")] + [InlineData("")] + public async Task GitHubTokenProvider_Is_Mutually_Exclusive_With_Static_Token(string staticToken) + { + await using var client = new CopilotClient(); + var config = new SessionConfig + { + GitHubToken = staticToken, + GitHubTokenProvider = _ => Task.FromResult(GitHubTokenProviderResult.Cancel()) + }; + + var error = await Assert.ThrowsAsync(() => client.CreateSessionAsync(config)); + + Assert.Contains("cannot be used together", error.Message); + } + + [Fact] + public async Task GitHubTokenProvider_Is_Released_When_Session_Is_Deleted() + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); + var session = await client.CreateSessionAsync(new SessionConfig + { + GitHubTokenProvider = _ => Task.FromResult(GitHubTokenProviderResult.Cancel()) + }); + var registrationId = Assert.Single(server.Requests, request => request.Method == "session.create") + .Params.GetProperty("gitHubTokenProviderRegistrationId").GetString(); + + await client.DeleteSessionAsync(session.SessionId); + + var error = await Assert.ThrowsAsync(() => + server.SendRequestAsync("gitHubToken.getToken", TokenRequest(registrationId))); + Assert.Contains("Unknown GitHub token provider registration ID", error.Message); + await session.DisposeAsync(); + } + + [Fact] + public async Task GitHubTokenProvider_Is_Serialized_And_Maps_Callbacks() + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); + GitHubTokenProviderArgs? callbackArgs = null; + var session = await client.CreateSessionAsync(new SessionConfig + { + GitHubTokenProvider = args => + { + callbackArgs = args; + return Task.FromResult(GitHubTokenProviderResult.FromToken(new GitHubToken + { + AccessToken = "secret-token", + TokenType = "bearer", + ExpiresIn = 8 * 60 * 60 + })); + } + }); + var request = Assert.Single(server.Requests, request => request.Method == "session.create"); + var registrationId = request.Params.GetProperty("gitHubTokenProviderRegistrationId").GetString(); + Assert.False(string.IsNullOrEmpty(registrationId)); + Assert.False(request.Params.TryGetProperty("gitHubToken", out _)); + + var result = await server.SendRequestAsync("gitHubToken.getToken", new Dictionary + { + ["registrationId"] = registrationId, + ["host"] = "github.example.com", + ["sessionId"] = session.SessionId, + ["reason"] = "refresh" + }); + + Assert.True(result.TryGetProperty("kind", out var kind), result.ToString()); + Assert.Equal("token", kind.GetString()); + Assert.Equal("secret-token", result.GetProperty("accessToken").GetString()); + Assert.Equal(8 * 60 * 60, result.GetProperty("expiresIn").GetInt64()); + Assert.NotNull(callbackArgs); + Assert.Equal("github.example.com", callbackArgs.Host); + Assert.Equal(session.SessionId, callbackArgs.SessionId); + Assert.Equal(GitHubTokenRequestReason.Refresh, callbackArgs.Reason); + Assert.DoesNotContain("secret-token", new GitHubToken + { + AccessToken = "secret-token", + ExpiresIn = 8 * 60 * 60 + }.ToString()); + + await session.DisposeAsync(); + var error = await Assert.ThrowsAsync(() => + server.SendRequestAsync("gitHubToken.getToken", new Dictionary + { + ["registrationId"] = registrationId, + ["host"] = "github.com", + ["reason"] = "initial" + })); + Assert.Contains("Unknown GitHub token provider registration ID", error.Message); + + server.ClearRequests(); + var resumed = await client.ResumeSessionAsync("resumed-session", new ResumeSessionConfig + { + GitHubTokenProvider = _ => Task.FromResult(GitHubTokenProviderResult.Cancel()) + }); + var resumeRequest = Assert.Single(server.Requests, request => request.Method == "session.resume"); + Assert.False(string.IsNullOrEmpty( + resumeRequest.Params.GetProperty("gitHubTokenProviderRegistrationId").GetString())); + await resumed.DisposeAsync(); + } + + [Fact] + public async Task GitHubTokenProvider_Handles_Cancellation_Errors_And_Rollback() + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); + var cancelledSession = await client.CreateSessionAsync(new SessionConfig + { + GitHubTokenProvider = _ => Task.FromResult(GitHubTokenProviderResult.Cancel()) + }); + var cancelledId = Assert.Single(server.Requests, request => request.Method == "session.create") + .Params.GetProperty("gitHubTokenProviderRegistrationId").GetString(); + var cancelled = await server.SendRequestAsync("gitHubToken.getToken", TokenRequest(cancelledId)); + Assert.True(cancelled.TryGetProperty("kind", out var cancelledKind), cancelled.ToString()); + Assert.Equal("cancelled", cancelledKind.GetString()); + await cancelledSession.DisposeAsync(); + + server.ClearRequests(); + var providerSession = await client.CreateSessionAsync(new SessionConfig + { + GitHubTokenProvider = _ => Task.FromException( + new InvalidOperationException("provider failed")) + }); + var providerId = Assert.Single(server.Requests, request => request.Method == "session.create") + .Params.GetProperty("gitHubTokenProviderRegistrationId").GetString(); + var callbackError = await Assert.ThrowsAsync(() => + server.SendRequestAsync("gitHubToken.getToken", TokenRequest(providerId))); + Assert.Contains("provider failed", callbackError.Message); + await providerSession.DisposeAsync(); + + server.ClearRequests(); + server.FailSessionCreate(); + await Assert.ThrowsAsync(() => client.CreateSessionAsync(new SessionConfig + { + GitHubTokenProvider = _ => Task.FromResult(GitHubTokenProviderResult.Cancel()) + })); + var rolledBackId = Assert.Single(server.Requests, request => request.Method == "session.create") + .Params.GetProperty("gitHubTokenProviderRegistrationId").GetString(); + var rollbackError = await Assert.ThrowsAsync(() => + server.SendRequestAsync("gitHubToken.getToken", TokenRequest(rolledBackId))); + Assert.Contains("Unknown GitHub token provider registration ID", rollbackError.Message); + } + + [Fact] + public async Task GitHubTokenProvider_Resume_Replaces_Ownership() + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); + var first = await client.CreateSessionAsync(new SessionConfig + { + SessionId = "replacement-session", + GitHubTokenProvider = _ => Task.FromResult(GitHubTokenProviderResult.Cancel()) + }); + var firstId = Assert.Single(server.Requests, request => request.Method == "session.create") + .Params.GetProperty("gitHubTokenProviderRegistrationId").GetString(); + + await first.DisposeAsync(); + await Assert.ThrowsAsync(() => + server.SendRequestAsync("gitHubToken.getToken", TokenRequest(firstId))); + + server.ClearRequests(); + var resumed = await client.ResumeSessionAsync("replacement-session", new ResumeSessionConfig + { + GitHubTokenProvider = _ => Task.FromResult(GitHubTokenProviderResult.Cancel()) + }); + var secondId = Assert.Single(server.Requests, request => request.Method == "session.resume") + .Params.GetProperty("gitHubTokenProviderRegistrationId").GetString(); + + var result = await server.SendRequestAsync("gitHubToken.getToken", TokenRequest(secondId)); + Assert.Equal("cancelled", result.GetProperty("kind").GetString()); + + await resumed.DisposeAsync(); + await Assert.ThrowsAsync(() => + server.SendRequestAsync("gitHubToken.getToken", TokenRequest(secondId))); + } + + private static Dictionary TokenRequest(string? registrationId) => new() + { + ["registrationId"] = registrationId, + ["host"] = "github.com", + ["reason"] = "initial" + }; + [Fact] public async Task StopAsync_Requests_Runtime_Shutdown_For_Owned_Process() { @@ -182,6 +370,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 +464,142 @@ public async Task CreateSessionAsync_Omits_CustomAgent_ReasoningEffort_When_Unse Assert.False(agent.TryGetProperty("reasoningEffort", out _)); } + public static TheoryData CapiAutoTiers => new() + { + { AutoTier.Efficiency, "efficiency", null }, + { AutoTier.Balance, "balance", null }, + { AutoTier.Intelligence, "intelligence", null }, + { AutoTier.Efficiency, "efficiency", false }, + { AutoTier.Balance, "balance", false }, + { AutoTier.Intelligence, "intelligence", false }, + }; + + [Theory] + [MemberData(nameof(CapiAutoTiers))] + public async Task SessionRequests_Serialize_CapiAutoTier(AutoTier tier, string expectedTier, bool? enableWebSocketResponses) + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); + var capi = new CapiSessionOptions { AutoTier = tier, EnableWebSocketResponses = enableWebSocketResponses }; + + await using var created = await client.CreateSessionAsync(new SessionConfig + { + Model = "auto", + Capi = capi, + OnPermissionRequest = PermissionHandler.ApproveAll + }); + await using var resumed = await client.ResumeSessionAsync("resume-with-auto-tier", new ResumeSessionConfig + { + Model = "auto", + Capi = capi, + OnPermissionRequest = PermissionHandler.ApproveAll + }); + + foreach (var method in new[] { "session.create", "session.resume" }) + { + var request = Assert.Single(server.Requests, request => request.Method == method); + var serializedCapi = request.Params.GetProperty("capi"); + Assert.Equal(expectedTier, serializedCapi.GetProperty("autoTier").GetString()); + if (enableWebSocketResponses.HasValue) + { + Assert.Equal(enableWebSocketResponses.Value, serializedCapi.GetProperty("enableWebSocketResponses").GetBoolean()); + } + else + { + Assert.False(serializedCapi.TryGetProperty("enableWebSocketResponses", out _)); + } + } + } + + [Theory] + [InlineData(false, null)] + [InlineData(true, null)] + [InlineData(true, false)] + public async Task SessionRequests_Omit_CapiAutoTier_WhenUnset(bool includeCapi, bool? enableWebSocketResponses) + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); + var capi = includeCapi ? new CapiSessionOptions { EnableWebSocketResponses = enableWebSocketResponses } : null; + + await using var created = await client.CreateSessionAsync(new SessionConfig + { + Model = "auto", + Capi = capi, + OnPermissionRequest = PermissionHandler.ApproveAll + }); + await using var resumed = await client.ResumeSessionAsync("resume-without-auto-tier", new ResumeSessionConfig + { + Capi = capi, + OnPermissionRequest = PermissionHandler.ApproveAll + }); + + foreach (var method in new[] { "session.create", "session.resume" }) + { + var request = Assert.Single(server.Requests, request => request.Method == method); + Assert.Equal(includeCapi, request.Params.TryGetProperty("capi", out var serializedCapi)); + if (includeCapi) + { + Assert.False(serializedCapi.TryGetProperty("autoTier", out _)); + if (enableWebSocketResponses.HasValue) + { + Assert.Equal(enableWebSocketResponses.Value, serializedCapi.GetProperty("enableWebSocketResponses").GetBoolean()); + } + else + { + Assert.Empty(serializedCapi.EnumerateObject()); + } + } + } + } + + [Fact] + public async Task CreateSessionAsync_Forwards_AskUserVariant() + { + 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.CreateSessionAsync(new SessionConfig + { + AskUserVariant = AskUserVariant.Elicitation, + OnPermissionRequest = PermissionHandler.ApproveAll + }); + + var request = Assert.Single(server.Requests, request => request.Method == "session.create"); + Assert.Equal("elicitation", request.Params.GetProperty("askUserVariant").GetString()); + + server.ClearRequests(); + await using var defaultSession = await client.CreateSessionAsync(new SessionConfig + { + OnPermissionRequest = PermissionHandler.ApproveAll + }); + var defaultRequest = Assert.Single(server.Requests, request => request.Method == "session.create"); + Assert.False(defaultRequest.Params.TryGetProperty("askUserVariant", out _)); + } + + [Fact] + public async Task ResumeSessionAsync_Forwards_AskUserVariant_On_Cold_Resume() + { + 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("ask-user-variant", new ResumeSessionConfig + { + AskUserVariant = AskUserVariant.Legacy, + OnPermissionRequest = PermissionHandler.ApproveAll + }); + + var request = Assert.Single(server.Requests, request => request.Method == "session.resume"); + Assert.Equal("legacy", request.Params.GetProperty("askUserVariant").GetString()); + + server.ClearRequests(); + await using var defaultSession = await client.ResumeSessionAsync("ask-user-variant-default", new ResumeSessionConfig + { + OnPermissionRequest = PermissionHandler.ApproveAll + }); + var defaultRequest = Assert.Single(server.Requests, request => request.Method == "session.resume"); + Assert.False(defaultRequest.Params.TryGetProperty("askUserVariant", out _)); + } + [Fact] public async Task SessionRequests_Serialize_AdditionalDirectories() { @@ -275,16 +620,189 @@ public async Task SessionRequests_Serialize_AdditionalDirectories() server.ClearRequests(); - await using var resumed = await client.ResumeSessionAsync("resume-with-additional-directories", new ResumeSessionConfig + 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 EmptyMode_Create_Sends_Empty_IncludedBuiltinSkills() + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForUri(server.Url), + Mode = CopilotClientMode.Empty, + BaseDirectory = Path.GetTempPath(), + }); + + await using var created = await client.CreateSessionAsync(new SessionConfig + { + AvailableTools = [], + OnPermissionRequest = PermissionHandler.ApproveAll + }); + + var update = Assert.Single(server.Requests, request => request.Method == "session.options.update"); + Assert.True(update.Params.TryGetProperty("includedBuiltinSkills", out var skills)); + Assert.Equal(JsonValueKind.Array, skills.ValueKind); + Assert.Equal(0, skills.GetArrayLength()); + // Adjacent unconditional plugin isolation is still present. + Assert.True(update.Params.TryGetProperty("installedPlugins", out var plugins)); + Assert.Equal(0, plugins.GetArrayLength()); + } + + [Fact] + public async Task EmptyMode_Resume_Sends_Empty_IncludedBuiltinSkills() + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForUri(server.Url), + Mode = CopilotClientMode.Empty, + BaseDirectory = Path.GetTempPath(), + }); + + await using var resumed = await client.ResumeSessionAsync("resume-empty-skills", new ResumeSessionConfig + { + AvailableTools = [], + OnPermissionRequest = PermissionHandler.ApproveAll + }); + + var update = Assert.Single(server.Requests, request => request.Method == "session.options.update"); + Assert.True(update.Params.TryGetProperty("includedBuiltinSkills", out var skills)); + Assert.Equal(JsonValueKind.Array, skills.ValueKind); + Assert.Equal(0, skills.GetArrayLength()); + } + + [Fact] + public async Task EmptyMode_Resume_Preserves_Explicit_IncludedBuiltinSkills() + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForUri(server.Url), + Mode = CopilotClientMode.Empty, + BaseDirectory = Path.GetTempPath(), + }); + + await using var resumed = await client.ResumeSessionAsync("resume-selected-skills", new ResumeSessionConfig + { + AvailableTools = [], + IncludedBuiltinSkills = ["code-review"], + OnPermissionRequest = PermissionHandler.ApproveAll + }); + + var update = Assert.Single(server.Requests, request => request.Method == "session.options.update"); + var skills = update.Params.GetProperty("includedBuiltinSkills"); + Assert.Equal(["code-review"], skills.EnumerateArray().Select(value => value.GetString())); + } + + [Fact] + public async Task EmptyMode_Create_With_EnableSkills_Still_Sends_Empty_IncludedBuiltinSkills() + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForUri(server.Url), + Mode = CopilotClientMode.Empty, + BaseDirectory = Path.GetTempPath(), + }); + + // Caller opts into their own custom skills. Runtime-bundled built-ins must + // still be excluded: the empty post-patch cannot be weakened by the caller. + await using var created = await client.CreateSessionAsync(new SessionConfig + { + AvailableTools = [], + EnableSkills = true, + SkillDirectories = [Path.Combine(Path.GetTempPath(), "skills")], + OnPermissionRequest = PermissionHandler.ApproveAll + }); + + var update = Assert.Single(server.Requests, request => request.Method == "session.options.update"); + Assert.True(update.Params.TryGetProperty("includedBuiltinSkills", out var skills)); + Assert.Equal(JsonValueKind.Array, skills.ValueKind); + Assert.Equal(0, skills.GetArrayLength()); + } + + [Fact] + public async Task EmptyMode_Create_Preserves_Explicit_IncludedBuiltinSkills() + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForUri(server.Url), + Mode = CopilotClientMode.Empty, + BaseDirectory = Path.GetTempPath(), + }); + + await using var created = await client.CreateSessionAsync(new SessionConfig + { + AvailableTools = [], + IncludedBuiltinSkills = ["code-review"], + OnPermissionRequest = PermissionHandler.ApproveAll + }); + + var update = Assert.Single(server.Requests, request => request.Method == "session.options.update"); + var skills = update.Params.GetProperty("includedBuiltinSkills"); + Assert.Equal(["code-review"], skills.EnumerateArray().Select(value => value.GetString())); + } + + [Fact] + public async Task CopilotCliMode_Create_Does_Not_Inject_IncludedBuiltinSkills() + { + 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/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())); + // In the default copilot-cli mode with no overridable options set, no + // options patch is sent at all, so the field is never injected. + Assert.DoesNotContain(server.Requests, request => + request.Method == "session.options.update" + && request.Params.TryGetProperty("includedBuiltinSkills", out _)); } [Fact] @@ -447,6 +965,66 @@ public async Task Generated_Session_Rpc_Throws_When_Session_Disposed() await Assert.ThrowsAsync(() => session.Rpc.Model.GetCurrentAsync()); } + [Fact] + public async Task SendAndWaitAsync_Skips_Autopilot_Continuation_Idle() + { + 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.CreateSessionAsync(new SessionConfig + { + OnPermissionRequest = PermissionHandler.ApproveAll + }); + + var sendTask = session.SendAndWaitAsync(new MessageOptions { Prompt = "keep going" }); + await WaitForRequestAsync(server, "session.send"); + + var continuationIdleProcessed = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + using var subscription = session.On(idle => + { + if (idle.Data.Mode == SessionMode.Autopilot) + { + continuationIdleProcessed.TrySetResult(); + } + }); + + DispatchEvent(session, new AssistantMessageEvent + { + Id = Guid.NewGuid(), + Data = new AssistantMessageData + { + Content = "intermediate", + MessageId = "assistant-1" + } + }); + DispatchEvent(session, new SessionIdleEvent + { + Id = Guid.NewGuid(), + Data = new SessionIdleData { Mode = SessionMode.Autopilot } + }); + + await continuationIdleProcessed.Task.WaitAsync(TimeSpan.FromSeconds(5)); + Assert.False(sendTask.IsCompleted); + + DispatchEvent(session, new AssistantMessageEvent + { + Id = Guid.NewGuid(), + Data = new AssistantMessageData + { + Content = "final", + MessageId = "assistant-2" + } + }); + DispatchEvent(session, new SessionIdleEvent + { + Id = Guid.NewGuid(), + Data = new SessionIdleData { Mode = SessionMode.Interactive } + }); + + var result = await sendTask.WaitAsync(TimeSpan.FromSeconds(5)); + Assert.NotNull(result); + Assert.Equal("final", result.Data.Content); + } + [MethodImpl(MethodImplOptions.NoInlining)] private static async Task> CreateDroppedSessionAsync(CopilotClient client) { @@ -482,6 +1060,322 @@ 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 = DisableBypassPermissionsModes.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 CreateSessionAsync_Serializes_Future_ManagedSettings_Bypass_Mode() + { + 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 + { + ManagedSettings = new ManagedSettings + { + Permissions = new ManagedSettingsPermissions + { + DisableBypassPermissionsMode = "future-fail-closed-mode" + } + }, + OnPermissionRequest = PermissionHandler.ApproveAll + }); + + var request = Assert.Single(server.Requests, request => request.Method == "session.create"); + var permissions = request.Params.GetProperty("managedSettings").GetProperty("permissions"); + Assert.Equal( + "future-fail-closed-mode", + permissions.GetProperty("disableBypassPermissionsMode").GetString()); + } + + [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) @@ -547,9 +1441,13 @@ private sealed class FakeCopilotServer : IAsyncDisposable private readonly Task _serverTask; private readonly List _requests = []; private readonly object _requestsLock = new(); + private readonly ConcurrentDictionary> _pendingRequests = new(); + private NetworkStream? _stream; + private int _nextRequestId; private string? _lastSessionId; private bool _delayDestroy; private bool _failRuntimeShutdown; + private bool _failSessionCreate; private FakeCopilotServer(TcpListener listener) { @@ -611,6 +1509,31 @@ public void FailRuntimeShutdown() _failRuntimeShutdown = true; } + public void FailSessionCreate() + { + _failSessionCreate = true; + } + + public async Task SendRequestAsync(string method, Dictionary parameters) + { + var stream = _stream ?? throw new InvalidOperationException("Client is not connected."); + var id = Interlocked.Increment(ref _nextRequestId); + var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + if (!_pendingRequests.TryAdd(id, completion)) + { + throw new InvalidOperationException("Failed to track callback request."); + } + + await WriteMessageAsync(stream, new Dictionary + { + ["jsonrpc"] = "2.0", + ["id"] = id, + ["method"] = method, + ["params"] = parameters + }, _cts.Token); + return await completion.Task.WaitAsync(_cts.Token); + } + public async ValueTask DisposeAsync() { _allowDestroy.TrySetResult(); @@ -633,16 +1556,37 @@ private async Task RunAsync() { using var tcpClient = await _listener.AcceptTcpClientAsync(_cts.Token); using var stream = tcpClient.GetStream(); + _stream = stream; while (!_cts.Token.IsCancellationRequested) { - using var request = await ReadMessageAsync(stream, _cts.Token); - if (request is null) + using var message = await ReadMessageAsync(stream, _cts.Token); + if (message is null) { return; } - await HandleRequestAsync(stream, request.RootElement, _cts.Token); + var root = message.RootElement; + if (root.TryGetProperty("method", out _)) + { + await HandleRequestAsync(stream, root, _cts.Token); + continue; + } + + if (root.TryGetProperty("id", out var responseId) + && responseId.TryGetInt32(out var id) + && _pendingRequests.TryRemove(id, out var completion)) + { + if (root.TryGetProperty("error", out var error)) + { + completion.TrySetException(new InvalidOperationException( + error.GetProperty("message").GetString())); + } + else + { + completion.TrySetResult(root.GetProperty("result").Clone()); + } + } } } @@ -678,6 +1622,21 @@ private async Task HandleRequestAsync(Stream stream, JsonElement request, Cancel { _requests.Add(new RpcRequestRecord(method!, paramsElement)); } + if (method == "session.create" && _failSessionCreate) + { + _failSessionCreate = false; + await WriteMessageAsync(stream, new Dictionary + { + ["jsonrpc"] = "2.0", + ["id"] = id, + ["error"] = new Dictionary + { + ["code"] = -32000, + ["message"] = "session create failed" + } + }, cancellationToken); + return; + } object? result = method switch { "connect" => new Dictionary @@ -696,10 +1655,18 @@ private async Task HandleRequestAsync(Stream stream, JsonElement request, Cancel { ["messageId"] = "message-1" }, + "session.options.update" => new Dictionary + { + ["success"] = true + }, "session.mcp.oauth.handlePendingRequest" => new Dictionary { ["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 ad8b0644e9..2f213525f6 100644 --- a/dotnet/test/Unit/CloneTests.cs +++ b/dotnet/test/Unit/CloneTests.cs @@ -20,8 +20,16 @@ public void CopilotClientOptions_Clone_CopiesAllProperties() GitHubToken = "ghp_test", UseLoggedInUser = false, BaseDirectory = "/custom/copilot/home", + BuiltinPluginDirectories = ["/plugins/core", "/plugins/github"], EnableRemoteSessions = true, SessionIdleTimeoutSeconds = 600, + ClientInfo = new CopilotClientInfo + { + ApplicationName = "example-app", + ApplicationVersion = "1.0.0", + IntegrationName = "example-integration", + IntegrationVersion = "2.0.0", + }, }; var clone = original.Clone(); @@ -33,8 +41,11 @@ 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); + Assert.Same(original.ClientInfo, clone.ClientInfo); } [Fact] @@ -70,6 +81,7 @@ public void SessionConfig_Clone_CopiesAllProperties() ReasoningEffort = "high", ReasoningSummary = ReasoningSummary.Detailed, ContextTier = ContextTier.LongContext, + AskUserVariant = AskUserVariant.Elicitation, ConfigDirectory = "/config", AvailableTools = ["tool1", "tool2"], ExcludedTools = ["tool3"], @@ -78,6 +90,7 @@ public void SessionConfig_Clone_CopiesAllProperties() AdditionalDirectories = ["/shared", "/generated"], Streaming = true, EnableCitations = true, + EnableFileChangeTracking = true, EnableSessionTelemetry = false, EnableExperimentalMode = true, EnableOnDemandInstructionDiscovery = true, @@ -100,6 +113,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 }, @@ -116,6 +130,7 @@ public void SessionConfig_Clone_CopiesAllProperties() Assert.Equal(original.ReasoningEffort, clone.ReasoningEffort); Assert.Equal(original.ReasoningSummary, clone.ReasoningSummary); Assert.Equal(original.ContextTier, clone.ContextTier); + Assert.Equal(original.AskUserVariant, clone.AskUserVariant); Assert.Equal(original.ConfigDirectory, clone.ConfigDirectory); Assert.Equal(original.AvailableTools, clone.AvailableTools); Assert.Equal(original.ExcludedTools, clone.ExcludedTools); @@ -124,6 +139,7 @@ public void SessionConfig_Clone_CopiesAllProperties() 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); @@ -140,6 +156,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); @@ -162,6 +179,7 @@ public void SessionConfig_Clone_CollectionsAreIndependent() SkillDirectories = ["/skills"], InstructionDirectories = ["/instructions"], DisabledSkills = ["skill1"], + DisabledMcpServers = ["server1"], }; var clone = original.Clone(); @@ -176,6 +194,7 @@ public void SessionConfig_Clone_CollectionsAreIndependent() clone.SkillDirectories!.Add("/more"); clone.InstructionDirectories!.Add("/more-instructions"); clone.DisabledSkills!.Add("skill99"); + clone.DisabledMcpServers!.Add("server99"); // Original is unaffected Assert.Single(original.AvailableTools!); @@ -187,6 +206,7 @@ public void SessionConfig_Clone_CollectionsAreIndependent() Assert.Single(original.SkillDirectories!); Assert.Single(original.InstructionDirectories!); Assert.Single(original.DisabledSkills!); + Assert.Single(original.DisabledMcpServers!); } [Fact] @@ -214,6 +234,7 @@ public void ResumeSessionConfig_Clone_CollectionsAreIndependent() SkillDirectories = ["/skills"], InstructionDirectories = ["/instructions"], DisabledSkills = ["skill1"], + DisabledMcpServers = ["server1"], }; var clone = original.Clone(); @@ -228,6 +249,7 @@ public void ResumeSessionConfig_Clone_CollectionsAreIndependent() clone.SkillDirectories!.Add("/more"); clone.InstructionDirectories!.Add("/more-instructions"); clone.DisabledSkills!.Add("skill99"); + clone.DisabledMcpServers!.Add("server99"); // Original is unaffected Assert.Single(original.AvailableTools!); @@ -239,6 +261,7 @@ public void ResumeSessionConfig_Clone_CollectionsAreIndependent() Assert.Single(original.SkillDirectories!); Assert.Single(original.InstructionDirectories!); Assert.Single(original.DisabledSkills!); + Assert.Single(original.DisabledMcpServers!); } [Fact] @@ -299,6 +322,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); diff --git a/dotnet/test/Unit/CopilotToolTests.cs b/dotnet/test/Unit/CopilotToolTests.cs index c3f5861492..19fa6258be 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/E2ETestBackendTests.cs b/dotnet/test/Unit/E2ETestBackendTests.cs index f7c39a5083..43b23c1a48 100644 --- a/dotnet/test/Unit/E2ETestBackendTests.cs +++ b/dotnet/test/Unit/E2ETestBackendTests.cs @@ -25,7 +25,7 @@ public void RejectsUnknownBackend() () => E2ETestBackendConfiguration.Parse("unknown")); [Theory] - [InlineData("anthropic-messages", "anthropic", null, "claude-sonnet-4.5")] + [InlineData("anthropic-messages", "anthropic", null, "claude-sonnet-5")] [InlineData("openai-responses", "openai", "responses", "gpt-4.1")] [InlineData("openai-completions", "openai", "completions", "gpt-4.1")] public void AppliesProvider( diff --git a/dotnet/test/Unit/GitHubTelemetryTests.cs b/dotnet/test/Unit/GitHubTelemetryTests.cs index 24e633387e..6d76af5742 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() { @@ -86,6 +140,89 @@ public async Task Connect_Does_Not_Opt_In_Without_Handler() "connect request should omit enableGitHubTelemetryForwarding (or send null) when no handler is registered"); } + [Fact] + public async Task Connect_Forwards_Declared_ClientInfo() + { + await using var server = await FakeTelemetryServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForUri(server.Url), + ClientInfo = new CopilotClientInfo + { + ApplicationName = "acme-developer-portal", + ApplicationVersion = "2.4.0", + IntegrationName = "copilot-assistant", + IntegrationVersion = "1.5.0", + }, + }); + await client.StartAsync(); + + var connectParams = server.LastConnectParams ?? throw new InvalidOperationException("connect was not captured."); + Assert.True(connectParams.TryGetProperty("clientInfo", out var clientInfo)); + Assert.Equal("acme-developer-portal", clientInfo.GetProperty("editorName").GetString()); + Assert.Equal("2.4.0", clientInfo.GetProperty("editorVersion").GetString()); + Assert.Equal("copilot-assistant", clientInfo.GetProperty("extensionName").GetString()); + Assert.Equal("1.5.0", clientInfo.GetProperty("extensionVersion").GetString()); + } + + [Fact] + public async Task Connect_Omits_ClientInfo_When_Unset() + { + await using var server = await FakeTelemetryServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForUri(server.Url), + }); + await client.StartAsync(); + + var connectParams = server.LastConnectParams ?? throw new InvalidOperationException("connect was not captured."); + Assert.False( + connectParams.TryGetProperty("clientInfo", out _), + "connect request should omit clientInfo when none was declared"); + } + + [Fact] + public async Task Connect_Omits_Empty_ClientInfo_Fields() + { + await using var server = await FakeTelemetryServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForUri(server.Url), + ClientInfo = new CopilotClientInfo { ApplicationName = "example-app", ApplicationVersion = "" }, + }); + await client.StartAsync(); + + var connectParams = server.LastConnectParams ?? throw new InvalidOperationException("connect was not captured."); + Assert.True(connectParams.TryGetProperty("clientInfo", out var clientInfo)); + Assert.Equal("example-app", clientInfo.GetProperty("editorName").GetString()); + Assert.False(clientInfo.TryGetProperty("editorVersion", out _)); + Assert.False(clientInfo.TryGetProperty("extensionName", out _)); + Assert.False(clientInfo.TryGetProperty("extensionVersion", out _)); + } + + [Fact] + public async Task Connect_Omits_All_Empty_ClientInfo() + { + await using var server = await FakeTelemetryServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForUri(server.Url), + ClientInfo = new CopilotClientInfo + { + ApplicationName = "", + ApplicationVersion = "", + IntegrationName = "", + IntegrationVersion = "", + }, + }); + await client.StartAsync(); + + var connectParams = server.LastConnectParams ?? throw new InvalidOperationException("connect was not captured."); + Assert.False( + connectParams.TryGetProperty("clientInfo", out _), + "connect request should omit an all-empty clientInfo"); + } + [Fact] public async Task CreateSession_Does_Not_Opt_In_Without_Handler() { @@ -266,6 +403,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); @@ -347,6 +488,7 @@ 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" }, @@ -375,6 +517,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/MSBuildTargetsTests.cs b/dotnet/test/Unit/MSBuildTargetsTests.cs index a7d9cc0256..0dd3073956 100644 --- a/dotnet/test/Unit/MSBuildTargetsTests.cs +++ b/dotnet/test/Unit/MSBuildTargetsTests.cs @@ -48,6 +48,7 @@ public async Task PreinstalledCliBinaryPath_IsHonored_DownloadSkipped_AndCopiedT var outputPath = sandbox.ExpectedOutputBinary(); Assert.True(File.Exists(outputPath), $"Expected CLI to be copied to '{outputPath}'.\n{result.FailureMessage()}"); Assert.Equal(File.ReadAllText(preinstalled), File.ReadAllText(outputPath)); + Assert.True(File.Exists(Path.Combine(Path.GetDirectoryName(outputPath)!, ".copilot-explicit-cli"))); } [Fact] @@ -105,6 +106,41 @@ public async Task PreinstalledCliBinaryPath_WithSkipCliDownload_StillCopiesToOut Assert.True(File.Exists(sandbox.ExpectedOutputBinary()), result.FailureMessage()); } + [Fact] + public async Task RuntimePackageAssets_AreFilteredAndCopiedToOutput() + { + using var sandbox = MSBuildSandbox.Create(); + var preinstalled = sandbox.WritePreinstalledBinary("fake-cli-contents"); + sandbox.WriteRuntimeCacheAsset("prebuilds", GetNpmPlatform(), "runtime.node", "runtime"); + sandbox.WriteRuntimeCacheAsset("prebuilds", GetNpmPlatform(), + OperatingSystem.IsWindows() ? "copilot-runtime.exe" : "copilot-runtime", "wrapper"); + sandbox.WriteRuntimeCacheAsset("ripgrep", "bin", GetNpmPlatform(), "rg", "ripgrep"); + sandbox.WriteRuntimeCacheAsset("definitions", "future.json", "{}"); + sandbox.WriteRuntimeCacheAsset("copilot-sdk", "extension.js", "extension"); + sandbox.WriteRuntimeCacheAsset("preloads", "extension_bootstrap.mjs", "preload"); + sandbox.WriteRuntimeCacheAsset("sdk", "factory.js", "factory"); + sandbox.WriteRuntimeCacheAsset("app.js", "excluded"); + sandbox.WriteRuntimeCacheAsset("LICENSE.md", "excluded"); + sandbox.WriteRuntimeCacheAsset("README.md", "excluded"); + sandbox.WriteStaleOutputRuntimeAsset("obsolete", "tool", "stale"); + + var result = await sandbox.BuildAsync(new Dictionary + { + ["CopilotCliBinaryPath"] = preinstalled, + }); + + Assert.True(result.Succeeded, result.FailureMessage()); + Assert.Equal("ripgrep", File.ReadAllText(sandbox.ExpectedRuntimeAsset("ripgrep", "bin", GetNpmPlatform(), "rg"))); + Assert.Equal("{}", File.ReadAllText(sandbox.ExpectedRuntimeAsset("definitions", "future.json"))); + Assert.Equal("extension", File.ReadAllText(sandbox.ExpectedRuntimeAsset("copilot-sdk", "extension.js"))); + Assert.Equal("preload", File.ReadAllText(sandbox.ExpectedRuntimeAsset("preloads", "extension_bootstrap.mjs"))); + Assert.Equal("factory", File.ReadAllText(sandbox.ExpectedRuntimeAsset("sdk", "factory.js"))); + Assert.False(File.Exists(sandbox.ExpectedRuntimeAsset("app.js"))); + Assert.False(File.Exists(sandbox.ExpectedRuntimeAsset("LICENSE.md"))); + Assert.False(File.Exists(sandbox.ExpectedRuntimeAsset("README.md"))); + Assert.False(File.Exists(sandbox.ExpectedRuntimeAsset("obsolete", "tool"))); + } + [Fact] public async Task PreinstalledCliBinaryPath_NonExistentFile_FailsWithActionableError() { @@ -150,6 +186,17 @@ private static string FindTargetsFile([CallerFilePath] string? thisFile = null) "Could not locate GitHub.Copilot.SDK.targets relative to test assembly or source file."); } + private static string GetNpmPlatform() + { + var arch = System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture + == System.Runtime.InteropServices.Architecture.Arm64 + ? "arm64" + : "x64"; + if (OperatingSystem.IsWindows()) return $"win32-{arch}"; + if (OperatingSystem.IsMacOS()) return $"darwin-{arch}"; + return $"linux-{arch}"; + } + /// /// A throwaway directory containing a minimal csproj that imports the SDK targets /// file. Disposing removes the directory tree. @@ -203,6 +250,42 @@ public string ExpectedOutputBinary() return Path.Combine(ProjectDir, "bin", "Debug", "net8.0", "runtimes", rid, "native", BinaryName); } + public void WriteRuntimeCacheAsset(params string[] pathAndContents) + { + var pathParts = pathAndContents.Take(pathAndContents.Length - 1).ToArray(); + var path = Path.Combine(ProjectDir, "obj", "Debug", "net8.0", "copilot-cli", "0.0.0-test", + GetNpmPlatform()); + foreach (var part in pathParts) + { + path = Path.Combine(path, part); + } + Directory.CreateDirectory(Path.GetDirectoryName(path)!); + File.WriteAllText(path, pathAndContents[^1]); + } + + public string ExpectedRuntimeAsset(params string[] pathParts) + { + var path = Path.Combine(ProjectDir, "bin", "Debug", "net8.0", "runtimes", GetPortableRid(), "native"); + foreach (var part in pathParts) + { + path = Path.Combine(path, part); + } + return path; + } + + public void WriteStaleOutputRuntimeAsset(params string[] pathAndContents) + { + var relativeParts = pathAndContents.Take(pathAndContents.Length - 1).ToArray(); + var path = ExpectedRuntimeAsset(relativeParts); + Directory.CreateDirectory(Path.GetDirectoryName(path)!); + File.WriteAllText(path, pathAndContents[^1]); + var manifest = ExpectedRuntimeAsset(".copilot-runtime-assets"); + Directory.CreateDirectory(Path.GetDirectoryName(manifest)!); + File.WriteAllText( + manifest, + string.Join(Path.DirectorySeparatorChar.ToString(), relativeParts) + Environment.NewLine); + } + public async Task BuildAsync(IDictionary properties) { var args = new StringBuilder("build --nologo -clp:NoSummary"); diff --git a/dotnet/test/Unit/RuntimeConnectionUrlParsingTests.cs b/dotnet/test/Unit/RuntimeConnectionUrlParsingTests.cs new file mode 100644 index 0000000000..1b16456b0d --- /dev/null +++ b/dotnet/test/Unit/RuntimeConnectionUrlParsingTests.cs @@ -0,0 +1,42 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +using Xunit; +using System.Reflection; + +namespace GitHub.Copilot.Test.Unit; + +public class RuntimeConnectionUrlParsingTests +{ + [Fact] + public void ForUri_ParsesBracketedIpv6HostPort() + { + var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForUri("[::1]:9000") + }); + + Assert.Equal("::1", GetPrivateField(client, "_optionsHost")); + Assert.Equal(9000, GetPrivateField(client, "_optionsPort")); + } + + [Fact] + public void ForUri_ParsesHttpIpv6HostPort() + { + var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForUri("http://[::1]:7000") + }); + + Assert.Equal("::1", GetPrivateField(client, "_optionsHost")); + Assert.Equal(7000, GetPrivateField(client, "_optionsPort")); + } + + private static T? GetPrivateField(object instance, string name) + { + var field = instance.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic); + Assert.NotNull(field); + return (T?)field.GetValue(instance); + } +} diff --git a/dotnet/test/Unit/RuntimeWrapperTests.cs b/dotnet/test/Unit/RuntimeWrapperTests.cs new file mode 100644 index 0000000000..9a9dacb7d3 --- /dev/null +++ b/dotnet/test/Unit/RuntimeWrapperTests.cs @@ -0,0 +1,134 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +using GitHub.Copilot.Rpc; +using Xunit; + +namespace GitHub.Copilot.Test.Unit; + +[CollectionDefinition(Name, DisableParallelization = true)] +public sealed class RuntimeWrapperIsolationCollection +{ + public const string Name = "Runtime wrapper isolation"; +} + +[Collection(RuntimeWrapperIsolationCollection.Name)] +public sealed class RuntimeWrapperTests +{ +#if !NETFRAMEWORK + [Fact] + public async Task Managed_Launch_Fails_When_Bundled_Runtime_Pair_Is_Missing() + { + var originalBaseDirectory = AppContext.GetData("APP_CONTEXT_BASE_DIRECTORY"); + var emptyBaseDirectory = Path.Combine( + Path.GetTempPath(), + $"missing-copilot-runtime-{Guid.NewGuid():N}"); + Directory.CreateDirectory(emptyBaseDirectory); + + try + { + AppContext.SetData("APP_CONTEXT_BASE_DIRECTORY", emptyBaseDirectory); + await using var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForStdio(), + Environment = new Dictionary(), + }); + + var exception = await Assert.ThrowsAsync(() => client.StartAsync()); + + Assert.Contains("runtime wrapper not found", exception.Message, StringComparison.OrdinalIgnoreCase); + } + finally + { + AppContext.SetData("APP_CONTEXT_BASE_DIRECTORY", originalBaseDirectory); + Directory.Delete(emptyBaseDirectory); + } + } +#endif + + [Fact] + public async Task Explicit_Path_Does_Not_Require_Adjacent_Runtime_Node() + { + var explicitPath = Path.Combine( + Path.GetTempPath(), + $"missing-explicit-copilot-{Guid.NewGuid():N}"); + await using var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForStdio(path: explicitPath), + Environment = new Dictionary(), + }); + + var exception = await Assert.ThrowsAnyAsync(() => client.StartAsync()); + + Assert.DoesNotContain("runtime.node", exception.ToString(), StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public async Task Copilot_Cli_Path_Does_Not_Require_Adjacent_Runtime_Node() + { + var explicitPath = Path.Combine( + Path.GetTempPath(), + $"missing-environment-copilot-{Guid.NewGuid():N}"); + await using var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForStdio(), + Environment = new Dictionary { ["COPILOT_CLI_PATH"] = explicitPath }, + }); + + var exception = await Assert.ThrowsAnyAsync(() => client.StartAsync()); + + Assert.DoesNotContain("runtime.node", exception.ToString(), StringComparison.OrdinalIgnoreCase); + } + +#if !NETFRAMEWORK + [Fact] + public async Task Marked_Bundled_Explicit_Cli_Does_Not_Require_Runtime_Pair() + { + var originalBaseDirectory = AppContext.GetData("APP_CONTEXT_BASE_DIRECTORY"); + var baseDirectory = Path.Combine( + Path.GetTempPath(), + $"explicit-bundled-copilot-{Guid.NewGuid():N}"); + var rid = GetPortableRid(); + var nativeDirectory = Path.Combine(baseDirectory, "runtimes", rid, "native"); + Directory.CreateDirectory(nativeDirectory); + var cliPath = Path.Combine(nativeDirectory, OperatingSystem.IsWindows() ? "copilot.exe" : "copilot"); + await File.WriteAllTextAsync(cliPath, "not an executable"); + await File.WriteAllTextAsync(Path.Combine(nativeDirectory, ".copilot-explicit-cli"), "explicit"); + + try + { + AppContext.SetData("APP_CONTEXT_BASE_DIRECTORY", baseDirectory); + await using var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForStdio(), + Environment = new Dictionary(), + }); + + var exception = await Assert.ThrowsAnyAsync(() => client.StartAsync()); + + Assert.DoesNotContain("runtime wrapper", exception.ToString(), StringComparison.OrdinalIgnoreCase); + Assert.Contains(cliPath, exception.ToString(), StringComparison.OrdinalIgnoreCase); + } + finally + { + AppContext.SetData("APP_CONTEXT_BASE_DIRECTORY", originalBaseDirectory); + Directory.Delete(baseDirectory, recursive: true); + } + } +#endif + + private static string GetPortableRid() + { + var os = OperatingSystem.IsWindows() ? "win" + : OperatingSystem.IsMacOS() ? "osx" + : "linux"; + var architecture = System.Runtime.InteropServices.RuntimeInformation.OSArchitecture switch + { + System.Runtime.InteropServices.Architecture.X64 => "x64", + System.Runtime.InteropServices.Architecture.Arm64 => "arm64", + _ => throw new PlatformNotSupportedException(), + }; + return $"{os}-{architecture}"; + } +} diff --git a/dotnet/test/Unit/SerializationTests.cs b/dotnet/test/Unit/SerializationTests.cs index acad44f19c..2414093797 100644 --- a/dotnet/test/Unit/SerializationTests.cs +++ b/dotnet/test/Unit/SerializationTests.cs @@ -411,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()); @@ -428,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()); @@ -479,6 +483,7 @@ public void SessionRequests_CanSerializeCitationAgentExclusionAndLimits_WithSdkO createRequestType, ("SessionId", "session-id"), ("EnableCitations", true), + ("EnableFileChangeTracking", true), ("ExcludedBuiltInAgents", excludedAgents), ("SessionLimits", new SessionLimitsConfig { MaxAiCredits = 12.5 })); @@ -486,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()); @@ -494,6 +500,7 @@ public void SessionRequests_CanSerializeCitationAgentExclusionAndLimits_WithSdkO resumeRequestType, ("SessionId", "session-id"), ("EnableCitations", true), + ("EnableFileChangeTracking", true), ("ExcludedBuiltInAgents", excludedAgents), ("SessionLimits", new SessionLimitsConfig { MaxAiCredits = 7.25 })); @@ -501,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()); } @@ -622,6 +630,46 @@ public void ResumeSessionConfigClone_PreservesExpAssignments() Assert.Equal("exp-resume", clone.ExpAssignments!.Configs[0].Id); } + [Fact] + public void SessionRequests_CanSerializeFeatureFlags_WithSdkOptions() + { + var options = GetSerializerOptions(); + var flags = new Dictionary + { + ["ENABLED_TEST_FLAG"] = true, + ["DISABLED_TEST_FLAG"] = false, + }; + + foreach (var requestName in new[] { "CreateSessionRequest", "ResumeSessionRequest" }) + { + var requestType = GetNestedType(typeof(CopilotClient), requestName); + var request = CreateInternalRequest( + requestType, + ("SessionId", "session-id"), + ("FeatureFlags", flags)); + using var document = JsonDocument.Parse( + JsonSerializer.Serialize(request, requestType, options)); + var serializedFlags = document.RootElement.GetProperty("featureFlags"); + Assert.True(serializedFlags.GetProperty("ENABLED_TEST_FLAG").GetBoolean()); + Assert.False(serializedFlags.GetProperty("DISABLED_TEST_FLAG").GetBoolean()); + } + } + + [Fact] + public void SessionConfigClone_CopiesFeatureFlags() + { + var config = new SessionConfig + { + FeatureFlags = new Dictionary { ["TEST_FLAG"] = true }, + }; + + var clone = config.Clone(); + clone.FeatureFlags!["TEST_FLAG"] = false; + + Assert.True(config.FeatureFlags["TEST_FLAG"]); + Assert.False(clone.FeatureFlags["TEST_FLAG"]); + } + [Fact] public void CreateSessionRequest_CanSerializeEnableSessionTelemetry_WithSdkOptions() { diff --git a/dotnet/test/Unit/SessionEventSerializationTests.cs b/dotnet/test/Unit/SessionEventSerializationTests.cs index 64e28a5aee..8aea505737 100644 --- a/dotnet/test/Unit/SessionEventSerializationTests.cs +++ b/dotnet/test/Unit/SessionEventSerializationTests.cs @@ -9,6 +9,45 @@ namespace GitHub.Copilot.Test.Unit; public class SessionEventSerializationTests { + public static TheoryData AutoTiers => new() + { + { AutoTier.Efficiency, "efficiency" }, + { AutoTier.Balance, "balance" }, + { AutoTier.Intelligence, "intelligence" }, + { null, null }, + }; + + [Theory] + [MemberData(nameof(AutoTiers))] + public void SessionEvent_Deserializes_AutoTier(AutoTier? expectedTier, string? wireTier) + { + foreach (var eventType in new[] { "session.start", "session.resume" }) + { + var autoTierProperty = wireTier is null ? "" : $""", "autoTier": "{wireTier}" """; + var json = $$""" + { + "id": "11111111-1111-1111-1111-111111111111", + "timestamp": "2026-08-28T00:00:00Z", + "parentId": null, + "type": "{{eventType}}", + "data": { + "sessionId": "test-session", "version": 1, + "producer": "copilot", "copilotVersion": "1.0.82-1", + "startTime": "2026-08-28T00:00:00Z", + "resumeTime": "2026-08-28T00:00:00Z", "eventCount": 1 + {{autoTierProperty}} + } + } + """; + + var sessionEvent = SessionEvent.FromJson(json); + var actualTier = eventType == "session.start" + ? Assert.IsType(sessionEvent).Data.AutoTier + : Assert.IsType(sessionEvent).Data.AutoTier; + Assert.Equal(expectedTier, actualTier); + } + } + public static TheoryData JsonElementBackedEvents => new() { { @@ -368,4 +407,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 d8588699c4..801ad8556c 100644 --- a/go/README.md +++ b/go/README.md @@ -20,7 +20,10 @@ go get github.com/github/copilot-sdk/go Try the interactive chat sample (from the repo root): ```bash -cd go/samples +cd nodejs +npm ci +export COPILOT_CLI_PATH="$(npm run --silent prepare:runtime -- --print-path)" +cd ../go/samples go run chat.go ``` @@ -104,7 +107,7 @@ Follow these steps to embed the CLI: 1. Run `go get -tool github.com/github/copilot-sdk/go/cmd/bundler`. This is a one-time setup step per project. 2. Run `go tool bundler` in your build environment just before building your application. -That's it! When your application calls `copilot.NewClient` without a `Connection` field (or with an empty `StdioConnection{}`) and no `COPILOT_CLI_PATH` environment variable, the SDK will automatically install the embedded CLI to a cache directory and use it for all operations. +That's it! When your application calls `copilot.NewClient` without a `Connection` field (or with an empty `StdioConnection{}`), the SDK automatically installs the embedded `copilot-runtime` executable and adjacent `runtime.node` to a cache directory for managed child-process connections. The bundler prepares the native runtime library required by the [in-process transport](#in-process-transport-experimental). It is included in the application only when building with the `copilot_inprocess` build tag. @@ -138,6 +141,9 @@ Resolution and requirements: always takes precedence. - Set `COPILOT_CLI_PATH` only when using an externally provisioned compatible runtime package; otherwise the bundled runtime is used. No `PATH` lookup is performed. - Embedded runtime versions are isolated in separate cache directories. Start fails loudly if the native runtime is unavailable. +- Managed child-process start fails if the embedded `copilot-runtime` and + `runtime.node` pair is unavailable; explicit paths and `COPILOT_CLI_PATH` + remain direct overrides. - Linux in-process bundles include both glibc and musl runtime packages and select the matching package automatically at startup. - Only one native runtime version may be loaded per process. @@ -195,7 +201,7 @@ Event types: `SessionLifecycleCreated`, `SessionLifecycleDeleted`, `SessionLifec - `URIConnection{URL, ConnectionToken}` — connect to an already-running runtime (no process spawned) - `InProcessConnection{}` — **Experimental.** Host the runtime in-process via the native FFI library instead of spawning a child process. See [In-process transport](#in-process-transport-experimental) below. - When `Path` is empty for stdio/tcp, the SDK uses the bundled CLI (or `COPILOT_CLI_PATH` env var). + When `Path` is empty for stdio/tcp, the SDK uses `COPILOT_CLI_PATH` when set, then the bundled `copilot-runtime` and adjacent `runtime.node`. `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 (default: current process working directory) @@ -222,8 +228,10 @@ Event types: `SessionLifecycleCreated`, `SessionLifecycleDeleted`, `SessionLifec - `InfiniteSessions` (\*InfiniteSessionConfig): Automatic context compaction configuration - `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. +- `GitHubTokenProvider` (GitHubTokenProvider): Acquires session-scoped GitHub tokens on demand. Return `GitHubTokenResult` with a positive `ExpiresIn` value (production GitHub tokens typically use `8 * 60 * 60` seconds), or `GitHubTokenCancelled`. Cannot be combined with `GitHubToken`. - `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. +- `OnUserInputRequest` (UserInputHandler): Handler for legacy question-and-answer requests from the agent. Enables the legacy `ask_user` tool. See [User Input Requests](#user-input-requests) section. +- `AskUserVariant` (AskUserVariant): Selects the model-facing shape of the `ask_user` tool. The zero value preserves legacy behavior; use `AskUserVariantElicitation` with `OnElicitationRequest`. - `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. - `OnElicitationRequest` (ElicitationHandler): Handler for elicitation requests from the server. See [Elicitation Requests](#elicitation-requests-serverclient) section. @@ -237,6 +245,25 @@ Event types: `SessionLifecycleCreated`, `SessionLifecycleDeleted`, `SessionLifec - `Streaming` (*bool): Enable streaming delta events (nil = runtime default) - `Commands` ([]CommandDefinition): Slash-commands. See [Commands](#commands) section. - `OnElicitationRequest` (ElicitationHandler): Elicitation handler. See [Elicitation Requests](#elicitation-requests-serverclient) section. +- `AskUserVariant` (AskUserVariant): Selects the model-facing shape of the `ask_user` tool on cold resume. Re-supply `AskUserVariantElicitation` with `OnElicitationRequest`; the zero value preserves legacy behavior. +- `GitHubTokenProvider` (GitHubTokenProvider): Replaces the session-scoped token provider when resuming. Cannot be combined with `GitHubToken`. + +```go +session, err := client.CreateSession(ctx, &copilot.SessionConfig{ + GitHubTokenProvider: func(args copilot.GitHubTokenProviderArgs) (*copilot.GitHubTokenProviderResult, error) { + token, err := acquireToken(args.Host) + if err != nil { + return nil, err + } + return copilot.GitHubTokenResult(&copilot.GitHubToken{ + AccessToken: token, + ExpiresIn: 8 * 60 * 60, + }), nil + }, +}) +``` + +Initial acquisition runs during session creation or resume. Cancellation, provider errors, and invalid token responses reject that operation instead of falling back to ambient authentication. Idle sessions refresh only before their next credential-consuming operation; there is no background refresh timer. ### Session @@ -751,7 +778,7 @@ To let a specific custom tool bypass the permission prompt entirely, set `SkipPe ## User Input Requests -Enable the agent to ask questions to the user using the `ask_user` tool by providing an `OnUserInputRequest` handler: +Enable the legacy question-and-answer `ask_user` tool by providing an `OnUserInputRequest` handler: ```go session, err := client.CreateSession(context.Background(), &copilot.SessionConfig{ diff --git a/go/client.go b/go/client.go index eb95130300..ba77402ddb 100644 --- a/go/client.go +++ b/go/client.go @@ -36,8 +36,10 @@ import ( "fmt" "log" "net" + "net/netip" "os" "os/exec" + "path/filepath" "regexp" "strconv" "strings" @@ -144,19 +146,23 @@ func validateEnvironmentOptions(connection RuntimeConnection, opts *ClientOption // } // defer client.Stop() type Client struct { - options ClientOptions - process *exec.Cmd - client *jsonrpc2.Client - actualPort int - actualHost string - state connectionState - sessions map[string]*Session - sessionsMux sync.Mutex - isExternalServer bool - conn net.Conn // stores net.Conn for external TCP connections - useStdio bool // resolved value from options - useInProcess bool // true for InProcessConnection (FFI transport) - ffiHost inProcessHost + options ClientOptions + process *exec.Cmd + client *jsonrpc2.Client + actualPort int + actualHost string + state connectionState + sessions map[string]*Session + sessionsMux sync.Mutex + gitHubTokenProviders map[string]GitHubTokenProvider + gitHubTokenProvidersMux sync.RWMutex + sessionOperations map[string]*sessionOperation + sessionOperationsMux sync.Mutex + isExternalServer bool + conn net.Conn // stores net.Conn for external TCP connections + useStdio bool // resolved value from options + useInProcess bool // true for InProcessConnection (FFI transport) + ffiHost inProcessHost // resolved process options for the spawned runtime (zero values for URIConnection) cliPath string cliArgs []string @@ -188,6 +194,11 @@ type Client struct { internalRPC *rpc.InternalServerRPC } +type sessionOperation struct { + mutex sync.Mutex + users int +} + // NewClient creates a new Copilot runtime client with the given options. // // If options is nil, default options are used (spawns the bundled runtime over @@ -214,17 +225,24 @@ func NewClient(options *ClientOptions) *Client { opts := ClientOptions{} client := &Client{ - options: opts, - state: stateDisconnected, - sessions: make(map[string]*Session), - actualHost: "localhost", - isExternalServer: false, - useStdio: true, + options: opts, + state: stateDisconnected, + sessions: make(map[string]*Session), + gitHubTokenProviders: make(map[string]GitHubTokenProvider), + actualHost: "localhost", + isExternalServer: false, + useStdio: true, } 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. @@ -324,6 +342,18 @@ func NewClient(options *ClientOptions) *Client { return client } +func resolveRuntimeExecutable(explicitPath, bundledRuntimePath string) (string, error) { + if explicitPath != "" { + return explicitPath, nil + } + if bundledRuntimePath == "" { + return "", errors.New( + "managed Copilot runtime unavailable: the embedded bundle does not contain copilot-runtime and adjacent runtime.node; regenerate the bundle, provide an explicit path, or set COPILOT_CLI_PATH", + ) + } + return bundledRuntimePath, nil +} + const defaultConnectionEnvVar = "COPILOT_SDK_DEFAULT_CONNECTION" // resolveDefaultConnection selects the transport when no explicit connection @@ -372,13 +402,31 @@ func setEnvValue(env []string, key string, value string) []string { // parseCLIURL parses a CLI URL into host and port components. // -// Supports formats: "host:port", "http://host:port", "https://host:port", or just "port". +// Supports formats: "host:port", "[ipv6]:port", "http://host:port", "https://host:port", or just "port". // Panics if the URL format is invalid or the port is out of range. func parseCLIURL(url string) (string, int) { // Remove protocol if present cleanURL, _ := strings.CutPrefix(url, "https://") cleanURL, _ = strings.CutPrefix(cleanURL, "http://") + // Use the standard parser only for the bracketed IPv6 form. Keep the + // existing host:port parsing behavior for all other inputs. + if strings.HasPrefix(cleanURL, "[") { + host, portStr, err := net.SplitHostPort(cleanURL) + if err != nil { + panic(fmt.Sprintf("Invalid port in URIConnection: %s", url)) + } + addr, err := netip.ParseAddr(host) + if err != nil || !addr.Is6() { + panic(fmt.Sprintf("Invalid URIConnection format: %s", url)) + } + port, err := strconv.Atoi(portStr) + if err != nil || port <= 0 || port > 65535 { + panic(fmt.Sprintf("Invalid port in URIConnection: %s", url)) + } + return host, port + } + // Parse host:port or port format var host string var portStr string @@ -386,15 +434,13 @@ func parseCLIURL(url string) (string, int) { host = before portStr = after } else { - // Only port provided - portStr = before + portStr = cleanURL } if host == "" { host = "localhost" } - // Validate port port, err := strconv.Atoi(portStr) if err != nil || port <= 0 || port > 65535 { panic(fmt.Sprintf("Invalid port in URIConnection: %s", url)) @@ -453,6 +499,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{ @@ -526,6 +587,7 @@ func (c *Client) Stop() error { c.sessionsMux.Lock() c.sessions = make(map[string]*Session) c.sessionsMux.Unlock() + c.clearGitHubTokenProviders() c.startStopMux.Lock() defer c.startStopMux.Unlock() @@ -641,6 +703,7 @@ func (c *Client) ForceStop() { c.sessionsMux.Lock() c.sessions = make(map[string]*Session) c.sessionsMux.Unlock() + c.clearGitHubTokenProviders() c.startStopMux.Lock() defer c.startStopMux.Unlock() @@ -750,10 +813,27 @@ func extractTransformCallbacks(config *SystemMessageConfig) (*SystemMessageConfi return wireConfig, callbacks } +func hasManagedSettings(enableManagedSettings *bool, managedSettings *ManagedSettings) bool { + return (enableManagedSettings != nil && *enableManagedSettings) || managedSettings != nil +} + +func validateAskUserVariant(variant AskUserVariant) error { + if variant != "" && variant != AskUserVariantLegacy && variant != AskUserVariantElicitation { + return fmt.Errorf("invalid AskUserVariant %q: expected %q, %q, or unset", variant, AskUserVariantLegacy, AskUserVariantElicitation) + } + return nil +} + func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Session, error) { if config == nil { config = &SessionConfig{} } + if config.GitHubToken != "" && config.GitHubTokenProvider != nil { + return nil, fmt.Errorf("GitHubToken and GitHubTokenProvider cannot be used together") + } + if err := validateAskUserVariant(config.AskUserVariant); err != nil { + return nil, err + } if err := c.ensureConnected(ctx); err != nil { return nil, err @@ -761,6 +841,14 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses c.applyConfigDefaultsForMode(config) + registrationID := c.registerGitHubTokenProvider(config.GitHubTokenProvider) + registrationTransferred := false + defer func() { + if !registrationTransferred { + c.unregisterGitHubTokenProvider(registrationID) + } + }() + req := createSessionRequest{} req.Model = config.Model req.ClientName = config.ClientName @@ -793,8 +881,10 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses req.Capi = config.Capi req.Providers = config.Providers req.Models = config.Models + req.AskUserVariant = config.AskUserVariant req.EnableSessionTelemetry = config.EnableSessionTelemetry req.EnableCitations = config.EnableCitations + req.EnableFileChangeTracking = config.EnableFileChangeTracking req.SessionLimits = config.SessionLimits req.IsExperimentalMode = config.EnableExperimentalMode req.SkipCustomInstructions = config.SkipCustomInstructions @@ -814,11 +904,15 @@ 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 req.Memory = config.Memory req.GitHubToken = config.GitHubToken + req.GitHubTokenProviderRegistrationID = registrationID req.RemoteSession = config.RemoteSession req.Cloud = config.Cloud req.Canvases = config.Canvases @@ -829,7 +923,11 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses req.ExtensionSDKPath = config.ExtensionSDKPath req.ExtensionInfo = config.ExtensionInfo req.ExpAssignments = config.ExpAssignments + if config.FeatureFlags != nil { + req.FeatureFlags = &config.FeatureFlags + } req.EnableManagedSettings = config.EnableManagedSettings + req.ManagedSettings = config.ManagedSettings if len(config.Commands) > 0 { cmds := make([]wireCommand, 0, len(config.Commands)) @@ -904,6 +1002,19 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses } req.SessionID = localSessionID + // unregisterSession removes only the session created by this call and stops + // its event consumer. The latter is essential on CreateSession error paths: + // newSession starts processEvents eagerly, and no caller receives the failed + // session to disconnect it. + unregisterSession := func(sessionID string, s *Session) { + c.sessionsMux.Lock() + if c.sessions[sessionID] == s { + delete(c.sessions, sessionID) + } + c.sessionsMux.Unlock() + s.stopEventProcessing() + } + // initializeSession creates the session, wires up handlers, and registers // it in the sessions map. Invoked from the read loop the instant the // session.create response arrives (synchronously, before the next @@ -914,7 +1025,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses sessionID, c.client, "", - config.EnableManagedSettings != nil && *config.EnableManagedSettings, + hasManagedSettings(config.EnableManagedSettings, config.ManagedSettings), ) s.registerTools(config.Tools) @@ -957,17 +1068,13 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses if c.options.SessionFS != nil { if config.CreateSessionFSProvider == nil { - c.sessionsMux.Lock() - delete(c.sessions, sessionID) - c.sessionsMux.Unlock() + unregisterSession(sessionID, s) return nil, fmt.Errorf("CreateSessionFSProvider is required in session config when SessionFS is enabled in client options") } provider := config.CreateSessionFSProvider(s) if c.options.SessionFS.Capabilities != nil && c.options.SessionFS.Capabilities.Sqlite { if _, ok := provider.(SessionFSSqliteProvider); !ok { - c.sessionsMux.Lock() - delete(c.sessions, sessionID) - c.sessionsMux.Unlock() + unregisterSession(sessionID, s) return nil, fmt.Errorf("SessionFS capabilities declare SQLite support but the provider does not implement SessionFSSqliteProvider") } } @@ -1024,9 +1131,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses result, err := c.client.RequestWithInlineResponse(ctx, "session.create", req, inlineCb) if err != nil { if registeredSessionID != "" { - c.sessionsMux.Lock() - delete(c.sessions, registeredSessionID) - c.sessionsMux.Unlock() + unregisterSession(registeredSessionID, session) } return nil, fmt.Errorf("failed to create session: %w", err) } @@ -1034,9 +1139,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses var response createSessionResponse if err := json.Unmarshal(result, &response); err != nil { if registeredSessionID != "" { - c.sessionsMux.Lock() - delete(c.sessions, registeredSessionID) - c.sessionsMux.Unlock() + unregisterSession(registeredSessionID, session) } return nil, fmt.Errorf("failed to unmarshal response: %w", err) } @@ -1046,9 +1149,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses } if localSessionID != "" && response.SessionID != "" && response.SessionID != localSessionID { - c.sessionsMux.Lock() - delete(c.sessions, registeredSessionID) - c.sessionsMux.Unlock() + unregisterSession(registeredSessionID, session) return nil, fmt.Errorf("session.create returned sessionId %s but the caller requested %s", response.SessionID, localSessionID) } if config.OnMCPAuthRequest != nil { @@ -1068,10 +1169,17 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses CustomAgentsLocalOnly: config.CustomAgentsLocalOnly, CoauthorEnabled: config.CoauthorEnabled, ManageScheduleEnabled: config.ManageScheduleEnabled, + IncludedBuiltinSkills: config.IncludedBuiltinSkills, }); err != nil { return nil, err } + if registrationID != "" { + session.setGitHubTokenProviderRegistrationRelease(func() { + c.unregisterGitHubTokenProvider(registrationID) + }) + registrationTransferred = true + } return session, nil } @@ -1099,9 +1207,18 @@ func (c *Client) ResumeSession(ctx context.Context, sessionID string, config *Re // Tools: []copilot.Tool{myNewTool}, // }) func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, config *ResumeSessionConfig) (*Session, error) { + unlockSession := c.lockSessionOperation(sessionID) + defer unlockSession() + if config == nil { config = &ResumeSessionConfig{} } + if config.GitHubToken != "" && config.GitHubTokenProvider != nil { + return nil, fmt.Errorf("GitHubToken and GitHubTokenProvider cannot be used together") + } + if err := validateAskUserVariant(config.AskUserVariant); err != nil { + return nil, err + } if err := c.ensureConnected(ctx); err != nil { return nil, err @@ -1109,6 +1226,14 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, c.applyResumeDefaultsForMode(config) + registrationID := c.registerGitHubTokenProvider(config.GitHubTokenProvider) + registrationTransferred := false + defer func() { + if !registrationTransferred { + c.unregisterGitHubTokenProvider(registrationID) + } + }() + var req resumeSessionRequest req.SessionID = sessionID req.ClientName = config.ClientName @@ -1124,6 +1249,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, req.Capi = config.Capi req.Providers = config.Providers req.Models = config.Models + req.AskUserVariant = config.AskUserVariant req.EnableSessionTelemetry = config.EnableSessionTelemetry req.IsExperimentalMode = config.EnableExperimentalMode req.SkipCustomInstructions = config.SkipCustomInstructions @@ -1140,6 +1266,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 @@ -1193,11 +1320,15 @@ 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 req.Memory = config.Memory req.GitHubToken = config.GitHubToken + req.GitHubTokenProviderRegistrationID = registrationID req.RemoteSession = config.RemoteSession req.Canvases = config.Canvases req.OpenCanvases = config.OpenCanvases @@ -1208,7 +1339,11 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, req.ExtensionSDKPath = config.ExtensionSDKPath req.ExtensionInfo = config.ExtensionInfo req.ExpAssignments = config.ExpAssignments + if config.FeatureFlags != nil { + req.FeatureFlags = &config.FeatureFlags + } req.EnableManagedSettings = config.EnableManagedSettings + req.ManagedSettings = config.ManagedSettings if config.OnPermissionRequest != nil { req.RequestPermission = Bool(true) } @@ -1244,7 +1379,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, sessionID, c.client, "", - config.EnableManagedSettings != nil && *config.EnableManagedSettings, + hasManagedSettings(config.EnableManagedSettings, config.ManagedSettings), ) session.registerTools(config.Tools) @@ -1282,22 +1417,36 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, } c.sessionsMux.Lock() + replacedSession := c.sessions[sessionID] c.sessions[sessionID] = session c.sessionsMux.Unlock() + restoreReplacedSession := func() { + c.sessionsMux.Lock() + if current := c.sessions[sessionID]; current == nil || current == session { + if replacedSession != nil { + c.sessions[sessionID] = replacedSession + } else { + delete(c.sessions, sessionID) + } + } + c.sessionsMux.Unlock() + // newSession starts processEvents eagerly, before the RPC confirms the + // resume; every failure path here restores the previously-registered + // session (if any) but never returns this failed one to the caller, so + // its event consumer must be stopped here or it leaks forever. + session.stopEventProcessing() + } + if c.options.SessionFS != nil { if config.CreateSessionFSProvider == nil { - c.sessionsMux.Lock() - delete(c.sessions, sessionID) - c.sessionsMux.Unlock() + restoreReplacedSession() return nil, fmt.Errorf("CreateSessionFSProvider is required in session config when SessionFS is enabled in client options") } provider := config.CreateSessionFSProvider(session) if c.options.SessionFS.Capabilities != nil && c.options.SessionFS.Capabilities.Sqlite { if _, ok := provider.(SessionFSSqliteProvider); !ok { - c.sessionsMux.Lock() - delete(c.sessions, sessionID) - c.sessionsMux.Unlock() + restoreReplacedSession() return nil, fmt.Errorf("SessionFS capabilities declare SQLite support but the provider does not implement SessionFSSqliteProvider") } } @@ -1306,17 +1455,13 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, result, err := c.client.Request(ctx, "session.resume", req) if err != nil { - c.sessionsMux.Lock() - delete(c.sessions, sessionID) - c.sessionsMux.Unlock() + restoreReplacedSession() return nil, fmt.Errorf("failed to resume session: %w", err) } var response resumeSessionResponse if err := json.Unmarshal(result, &response); err != nil { - c.sessionsMux.Lock() - delete(c.sessions, sessionID) - c.sessionsMux.Unlock() + restoreReplacedSession() return nil, fmt.Errorf("failed to unmarshal response: %w", err) } @@ -1325,9 +1470,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, "sessionId": sessionID, "eventType": "mcp.oauth_required", }); err != nil { - c.sessionsMux.Lock() - delete(c.sessions, sessionID) - c.sessionsMux.Unlock() + restoreReplacedSession() return nil, err } } @@ -1341,10 +1484,21 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, CustomAgentsLocalOnly: config.CustomAgentsLocalOnly, CoauthorEnabled: config.CoauthorEnabled, ManageScheduleEnabled: config.ManageScheduleEnabled, + IncludedBuiltinSkills: config.IncludedBuiltinSkills, }); err != nil { + restoreReplacedSession() return nil, err } + if registrationID != "" { + session.setGitHubTokenProviderRegistrationRelease(func() { + c.unregisterGitHubTokenProvider(registrationID) + }) + registrationTransferred = true + } + if replacedSession != nil && replacedSession != session { + replacedSession.releaseGitHubTokenProviderRegistration() + } return session, nil } @@ -1436,6 +1590,9 @@ func (c *Client) GetSessionMetadata(ctx context.Context, sessionID string) (*Ses // log.Fatal(err) // } func (c *Client) DeleteSession(ctx context.Context, sessionID string) error { + unlockSession := c.lockSessionOperation(sessionID) + defer unlockSession() + if err := c.ensureConnected(ctx); err != nil { return err } @@ -1460,8 +1617,12 @@ func (c *Client) DeleteSession(ctx context.Context, sessionID string) error { // Remove from local sessions map if present c.sessionsMux.Lock() + session := c.sessions[sessionID] delete(c.sessions, sessionID) c.sessionsMux.Unlock() + if session != nil { + session.releaseGitHubTokenProviderRegistration() + } return nil } @@ -1815,6 +1976,10 @@ func (c *Client) verifyProtocolVersion(ctx context.Context) error { if c.options.OnGitHubTelemetry != nil { connectReq.EnableGitHubTelemetryForwarding = Bool(true) } + // Declare the integrating host's identity so the runtime attributes the + // telemetry it emits on this connection to a consistent surface instead of + // its own build. Nil when the app didn't supply it. + connectReq.ClientInfo = c.options.ClientInfo.toWire() rawConnectResult, err := c.client.Request(ctx, "connect", connectReq) if err != nil { var rpcErr *jsonrpc2.Error @@ -1851,8 +2016,9 @@ func (c *Client) verifyProtocolVersion(ctx context.Context) error { } type connectHandshakeRequest struct { - Token *string `json:"token,omitempty"` - EnableGitHubTelemetryForwarding *bool `json:"enableGitHubTelemetryForwarding,omitempty"` + Token *string `json:"token,omitempty"` + EnableGitHubTelemetryForwarding *bool `json:"enableGitHubTelemetryForwarding,omitempty"` + ClientInfo *rpc.ConnectClientInfo `json:"clientInfo,omitempty"` } // stderrBufferSize is the maximum number of bytes kept from the CLI process's @@ -1869,14 +2035,13 @@ func (c *Client) startCLIServer(ctx context.Context) error { return c.startInProcess(ctx) } - cliPath := c.cliPath - if cliPath == "" { - // If no CLI path is provided, attempt to use the embedded CLI if available - cliPath = embeddedcli.Path() + bundledRuntimePath := "" + if c.cliPath == "" { + bundledRuntimePath = embeddedcli.RuntimePath() } - if cliPath == "" { - // Default to "copilot" in PATH if no embedded CLI is available and no custom path is set - cliPath = "copilot" + cliPath, err := resolveRuntimeExecutable(c.cliPath, bundledRuntimePath) + if err != nil { + return err } // Start with user-provided CLIArgs, then add SDK-managed args @@ -2003,15 +2168,7 @@ func (c *Client) startCLIServer(ctx context.Context) error { // Create JSON-RPC client immediately c.client = jsonrpc2.NewClient(stdin, stdout) c.client.SetProcessDone(c.processDone, c.processErrorPtr) - c.client.SetOnClose(func() { - // Run in a goroutine to avoid deadlocking with Stop/ForceStop, - // which hold startStopMux while waiting for readLoop to finish. - go func() { - c.startStopMux.Lock() - defer c.startStopMux.Unlock() - c.state = stateDisconnected - }() - }) + c.client.SetOnClose(c.handleConnectionClose) c.RPC = rpc.NewServerRPC(c.client) c.internalRPC = rpc.NewInternalServerRPC(c.client) c.setupNotificationHandler() @@ -2085,24 +2242,21 @@ func (c *Client) startInProcess(ctx context.Context) error { return errors.New("in-process transport unavailable: rebuild with -tags copilot_inprocess on a supported platform") } - runtimePath := c.cliPath - if runtimePath == "" { - // The in-process transport does not resolve a bare command name from PATH - // (unlike the child-process transport). - if p := getEnvValue(c.options.Env, "COPILOT_CLI_PATH"); p != "" { - runtimePath = p - } + cliEntrypoint := c.cliPath + if cliEntrypoint == "" { + cliEntrypoint = getEnvValue(c.options.Env, "COPILOT_CLI_PATH") } + runtimePath := cliEntrypoint if runtimePath == "" { - runtimePath = embeddedcli.Path() + runtimePath = embeddedcli.RuntimePath() } if runtimePath == "" { - return errors.New("in-process runtime unavailable: set COPILOT_CLI_PATH to a compatible runtime package or build with the bundled embedded runtime") + return errors.New("in-process runtime unavailable: build with the bundled embedded runtime or set COPILOT_CLI_PATH to a compatible runtime package") } config := c.inProcessHostConfig() - host, err := createInProcessHost(runtimePath, config) + host, err := createInProcessHost(runtimePath, cliEntrypoint, config) if err != nil { return err } @@ -2130,15 +2284,7 @@ func (c *Client) startInProcess(ctx context.Context) error { } c.client = jsonrpc2.NewClient(host.Writer(), host.Reader()) - c.client.SetOnClose(func() { - // Run in a goroutine to avoid deadlocking with Stop/ForceStop, which hold - // startStopMux while waiting for readLoop to finish. - go func() { - c.startStopMux.Lock() - defer c.startStopMux.Unlock() - c.state = stateDisconnected - }() - }) + c.client.SetOnClose(c.handleConnectionClose) c.RPC = rpc.NewServerRPC(c.client) c.internalRPC = rpc.NewInternalServerRPC(c.client) c.setupNotificationHandler() @@ -2288,13 +2434,7 @@ func (c *Client) connectViaTCP(ctx context.Context) error { if c.processDone != nil { c.client.SetProcessDone(c.processDone, c.processErrorPtr) } - c.client.SetOnClose(func() { - go func() { - c.startStopMux.Lock() - defer c.startStopMux.Unlock() - c.state = stateDisconnected - }() - }) + c.client.SetOnClose(c.handleConnectionClose) c.RPC = rpc.NewServerRPC(c.client) c.internalRPC = rpc.NewInternalServerRPC(c.client) c.setupNotificationHandler() @@ -2325,14 +2465,14 @@ func (c *Client) setupNotificationHandler() { // payload's sessionId. Always register the global handlers so the generated // hooks.invoke handler is wired to our dispatcher. handlers := &rpc.ClientGlobalAPIHandlers{ - Hooks: &hooksAdapter{client: c}, + Hooks: &hooksAdapter{client: c}, + GitHubToken: &gitHubTokenAdapter{client: c}, } + if c.options.RequestHandler != nil { + llmInference := c.RPC.LlmInference handlers.LlmInference = newCopilotRequestAdapter(c.options.RequestHandler, func() *rpc.ServerLlmInferenceAPI { - if c.RPC == nil { - return nil - } - return c.RPC.LlmInference + return llmInference }) } if c.options.OnGitHubTelemetry != nil { @@ -2341,6 +2481,107 @@ func (c *Client) setupNotificationHandler() { rpc.RegisterClientGlobalAPIHandlers(c.client, handlers) } +func (c *Client) registerGitHubTokenProvider(provider GitHubTokenProvider) string { + if provider == nil { + return "" + } + registrationID := uuid.NewString() + c.gitHubTokenProvidersMux.Lock() + if c.gitHubTokenProviders == nil { + c.gitHubTokenProviders = make(map[string]GitHubTokenProvider) + } + c.gitHubTokenProviders[registrationID] = provider + c.gitHubTokenProvidersMux.Unlock() + return registrationID +} + +func (c *Client) unregisterGitHubTokenProvider(registrationID string) { + if registrationID == "" { + return + } + c.gitHubTokenProvidersMux.Lock() + delete(c.gitHubTokenProviders, registrationID) + c.gitHubTokenProvidersMux.Unlock() +} + +func (c *Client) clearGitHubTokenProviders() { + c.gitHubTokenProvidersMux.Lock() + c.gitHubTokenProviders = make(map[string]GitHubTokenProvider) + c.gitHubTokenProvidersMux.Unlock() +} + +func (c *Client) handleConnectionClose() { + c.clearGitHubTokenProviders() + // Avoid deadlocking with Stop/ForceStop, which hold startStopMux while + // waiting for the JSON-RPC read loop to finish. + go func() { + c.startStopMux.Lock() + defer c.startStopMux.Unlock() + c.state = stateDisconnected + }() +} + +func (c *Client) lockSessionOperation(sessionID string) func() { + c.sessionOperationsMux.Lock() + if c.sessionOperations == nil { + c.sessionOperations = make(map[string]*sessionOperation) + } + operation := c.sessionOperations[sessionID] + if operation == nil { + operation = &sessionOperation{} + c.sessionOperations[sessionID] = operation + } + operation.users++ + c.sessionOperationsMux.Unlock() + + operation.mutex.Lock() + return func() { + operation.mutex.Unlock() + c.sessionOperationsMux.Lock() + operation.users-- + if operation.users == 0 { + delete(c.sessionOperations, sessionID) + } + c.sessionOperationsMux.Unlock() + } +} + +type gitHubTokenAdapter struct { + client *Client +} + +func (a *gitHubTokenAdapter) GetToken(request *rpc.GitHubTokenAcquireRequest) (rpc.GitHubTokenAcquireResult, error) { + if request == nil { + return nil, fmt.Errorf("missing GitHub token acquire request") + } + a.client.gitHubTokenProvidersMux.RLock() + provider := a.client.gitHubTokenProviders[request.RegistrationID] + a.client.gitHubTokenProvidersMux.RUnlock() + if provider == nil { + return nil, fmt.Errorf("unknown GitHub token provider registration ID %q", request.RegistrationID) + } + + result, err := provider(GitHubTokenProviderArgs{ + Host: request.Host, + SessionID: request.SessionID, + Reason: request.Reason, + }) + if err != nil { + return nil, err + } + if result != nil && result.Cancelled { + return &rpc.GitHubTokenAcquireResultCancelled{}, nil + } + if result == nil || result.Token == nil { + return nil, fmt.Errorf("GitHub token provider returned neither a token nor cancellation") + } + return &rpc.GitHubTokenAcquireResultToken{ + AccessToken: result.Token.AccessToken, + TokenType: result.Token.TokenType, + ExpiresIn: result.Token.ExpiresIn, + }, nil +} + // gitHubTelemetryAdapter adapts the OnGitHubTelemetry option to the generated // rpc.GitHubTelemetryHandler interface. type gitHubTelemetryAdapter struct { diff --git a/go/client_test.go b/go/client_test.go index 14131bc4ce..ee0e078fb1 100644 --- a/go/client_test.go +++ b/go/client_test.go @@ -24,6 +24,35 @@ import ( // This file is for unit tests. Where relevant, prefer to add e2e tests in e2e/*.test.go instead +func TestResolveRuntimeExecutable(t *testing.T) { + t.Run("managed launch requires bundled runtime pair", func(t *testing.T) { + _, err := resolveRuntimeExecutable("", "") + if err == nil || !strings.Contains(err.Error(), "copilot-runtime and adjacent runtime.node") { + t.Fatalf("expected missing managed runtime error, got %v", err) + } + }) + + t.Run("explicit path does not require bundled runtime pair", func(t *testing.T) { + path, err := resolveRuntimeExecutable("/explicit/copilot", "") + if err != nil { + t.Fatal(err) + } + if path != "/explicit/copilot" { + t.Fatalf("resolveRuntimeExecutable() = %q", path) + } + }) + + t.Run("managed launch selects bundled wrapper", func(t *testing.T) { + path, err := resolveRuntimeExecutable("", "/bundle/copilot-runtime") + if err != nil { + t.Fatal(err) + } + if path != "/bundle/copilot-runtime" { + t.Fatalf("resolveRuntimeExecutable() = %q", path) + } + }) +} + func TestClient_URLParsing(t *testing.T) { t.Run("should parse port-only URL format", func(t *testing.T) { client := NewClient(&ClientOptions{ @@ -49,6 +78,15 @@ func TestClient_URLParsing(t *testing.T) { } }) + t.Run("should parse bracketed IPv6 host:port URL format", func(t *testing.T) { + client := NewClient(&ClientOptions{ + Connection: URIConnection{URL: "[::1]:9000"}, + }) + if client.actualPort != 9000 || client.actualHost != "::1" { + t.Errorf("Expected [::1]:9000, got %s:%d", client.actualHost, client.actualPort) + } + }) + t.Run("should parse http://host:port URL format", func(t *testing.T) { client := NewClient(&ClientOptions{ Connection: URIConnection{URL: "http://localhost:7000"}, @@ -58,6 +96,24 @@ func TestClient_URLParsing(t *testing.T) { } }) + t.Run("should parse http://[ipv6]:port URL format", func(t *testing.T) { + client := NewClient(&ClientOptions{ + Connection: URIConnection{URL: "http://[::1]:7000"}, + }) + if client.actualPort != 7000 || client.actualHost != "::1" { + t.Errorf("Expected [::1]:7000, got %s:%d", client.actualHost, client.actualPort) + } + }) + + t.Run("should panic for bracketed non-IPv6 host", func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("Expected panic for invalid bracketed host") + } + }() + NewClient(&ClientOptions{Connection: URIConnection{URL: "[not-ipv6]:1234"}}) + }) + t.Run("should parse https://host:port URL format", func(t *testing.T) { client := NewClient(&ClientOptions{ Connection: URIConnection{URL: "https://example.com:443"}, @@ -139,6 +195,300 @@ 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() + }) +} + +func TestClient_ClientInfo(t *testing.T) { + findConnect := func(requests []startupRPCRequest) map[string]any { + t.Helper() + for _, request := range requests { + if request.Method != "connect" { + continue + } + var params map[string]any + if err := json.Unmarshal(request.Params, ¶ms); err != nil { + t.Fatalf("decode connect params: %v", err) + } + return params + } + t.Fatal("connect was not called") + return nil + } + + t.Run("forwards a declared identity on the connect handshake", func(t *testing.T) { + url, requests, cleanup := newStartupRPCServer(t) + defer cleanup() + + client := NewClient(&ClientOptions{ + Connection: URIConnection{URL: url}, + ClientInfo: &ClientInfo{ + ApplicationName: "acme-developer-portal", + ApplicationVersion: "2.4.0", + IntegrationName: "copilot-assistant", + IntegrationVersion: "1.5.0", + }, + }) + if err := client.Start(t.Context()); err != nil { + t.Fatalf("Start failed: %v", err) + } + defer client.ForceStop() + + params := findConnect(requests()) + want := map[string]any{ + "editorName": "acme-developer-portal", + "editorVersion": "2.4.0", + "extensionName": "copilot-assistant", + "extensionVersion": "1.5.0", + } + if !reflect.DeepEqual(params["clientInfo"], want) { + t.Fatalf("clientInfo = %v, want %v", params["clientInfo"], want) + } + }) + + t.Run("omits clientInfo when unset", func(t *testing.T) { + url, requests, cleanup := newStartupRPCServer(t) + defer cleanup() + + client := NewClient(&ClientOptions{Connection: URIConnection{URL: url}}) + if err := client.Start(t.Context()); err != nil { + t.Fatalf("Start failed: %v", err) + } + defer client.ForceStop() + + if _, ok := findConnect(requests())["clientInfo"]; ok { + t.Fatal("clientInfo should be omitted when unset") + } + }) + + t.Run("omits empty fields", func(t *testing.T) { + url, requests, cleanup := newStartupRPCServer(t) + defer cleanup() + + client := NewClient(&ClientOptions{ + Connection: URIConnection{URL: url}, + ClientInfo: &ClientInfo{ApplicationName: "example-app"}, + }) + if err := client.Start(t.Context()); err != nil { + t.Fatalf("Start failed: %v", err) + } + defer client.ForceStop() + + want := map[string]any{"editorName": "example-app"} + if got := findConnect(requests())["clientInfo"]; !reflect.DeepEqual(got, want) { + t.Fatalf("clientInfo = %v, want %v", got, want) + } + }) + + t.Run("omits clientInfo when all fields empty", func(t *testing.T) { + url, requests, cleanup := newStartupRPCServer(t) + defer cleanup() + + client := NewClient(&ClientOptions{ + Connection: URIConnection{URL: url}, + ClientInfo: &ClientInfo{}, + }) + if err := client.Start(t.Context()); err != nil { + t.Fatalf("Start failed: %v", err) + } + defer client.ForceStop() + + if _, ok := findConnect(requests())["clientInfo"]; ok { + t.Fatal("clientInfo should be omitted when all fields are empty") + } + }) +} + +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{ @@ -213,6 +563,66 @@ func newRuntimeShutdownRpcPair(t *testing.T) (*jsonrpc2.Client, *jsonrpc2.Client } func TestClient_ForwardsCapiOptionsToSessionRequests(t *testing.T) { + tests := []struct { + name string + capi *CapiSessionOptions + want map[string]any + }{ + {"omitted", nil, nil}, + {"empty", &CapiSessionOptions{}, map[string]any{}}, + {"websocket only", &CapiSessionOptions{EnableWebSocketResponses: Bool(false)}, map[string]any{"enableWebSocketResponses": false}}, + {"efficiency", &CapiSessionOptions{AutoTier: AutoTierEfficiency}, map[string]any{"autoTier": "efficiency"}}, + {"balance", &CapiSessionOptions{AutoTier: AutoTierBalance}, map[string]any{"autoTier": "balance"}}, + {"intelligence", &CapiSessionOptions{AutoTier: AutoTierIntelligence}, map[string]any{"autoTier": "intelligence"}}, + {"efficiency with websocket", &CapiSessionOptions{AutoTier: AutoTierEfficiency, EnableWebSocketResponses: Bool(false)}, map[string]any{"autoTier": "efficiency", "enableWebSocketResponses": false}}, + {"balance with websocket", &CapiSessionOptions{AutoTier: AutoTierBalance, EnableWebSocketResponses: Bool(false)}, map[string]any{"autoTier": "balance", "enableWebSocketResponses": false}}, + {"intelligence with websocket", &CapiSessionOptions{AutoTier: AutoTierIntelligence, EnableWebSocketResponses: Bool(false)}, map[string]any{"autoTier": "intelligence", "enableWebSocketResponses": false}}, + } + for _, tt := range tests { + t.Run(tt.name, func(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{ + Model: "auto", + Capi: tt.capi, + }) + if err != nil { + t.Fatalf("CreateSession failed: %v", err) + } + assertCapiOptions(t, <-createParams, tt.want) + + 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-capi","workspacePath":"/workspace"}`), nil + }) + + _, err = client.ResumeSessionWithOptions(t.Context(), "resumed-capi", &ResumeSessionConfig{ + Model: "auto", + Capi: tt.capi, + }) + if err != nil { + t.Fatalf("ResumeSessionWithOptions failed: %v", err) + } + assertCapiOptions(t, <-resumeParams, tt.want) + }) + } +} + +func TestClient_ForwardsAskUserVariantToSessionRequests(t *testing.T) { rpcClient, server, _ := newRuntimeShutdownRpcPair(t) t.Cleanup(server.Stop) client := &Client{ @@ -221,34 +631,74 @@ func TestClient_ForwardsCapiOptionsToSessionRequests(t *testing.T) { sessions: make(map[string]*Session), } - createParams := make(chan json.RawMessage, 1) + createParams := make(chan json.RawMessage, 2) 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{ - Capi: &CapiSessionOptions{EnableWebSocketResponses: Bool(false)}, + resumeParams := make(chan json.RawMessage, 2) + server.SetRequestHandler("session.resume", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { + resumeParams <- append(json.RawMessage(nil), params...) + sessionID := sessionIDFromParams(t, params) + return []byte(`{"sessionId":"` + sessionID + `","workspacePath":"/workspace"}`), nil }) - if err != nil { + + if _, err := client.CreateSession(t.Context(), &SessionConfig{ + SessionID: "ask-user-create", + AskUserVariant: AskUserVariantElicitation, + }); err != nil { t.Fatalf("CreateSession failed: %v", err) } - assertCapiEnableWebSocketResponses(t, <-createParams) + if _, err := client.ResumeSession(t.Context(), "ask-user-cold-resume", &ResumeSessionConfig{ + AskUserVariant: AskUserVariantLegacy, + }); err != nil { + t.Fatalf("ResumeSession failed: %v", err) + } + if _, err := client.CreateSession(t.Context(), &SessionConfig{SessionID: "ask-user-default-create"}); err != nil { + t.Fatalf("CreateSession with default failed: %v", err) + } + if _, err := client.ResumeSession(t.Context(), "ask-user-default-cold-resume", nil); err != nil { + t.Fatalf("ResumeSession with default failed: %v", err) + } - 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-capi","workspacePath":"/workspace"}`), nil - }) + assertAskUserVariant(t, <-createParams, "elicitation") + assertAskUserVariant(t, <-resumeParams, "legacy") + assertAskUserVariant(t, <-createParams, "") + assertAskUserVariant(t, <-resumeParams, "") +} - _, err = client.ResumeSessionWithOptions(t.Context(), "resumed-capi", &ResumeSessionConfig{ - Capi: &CapiSessionOptions{EnableWebSocketResponses: Bool(false)}, - }) - if err != nil { - t.Fatalf("ResumeSessionWithOptions failed: %v", err) +func TestClient_RejectsInvalidAskUserVariant(t *testing.T) { + client := &Client{} + + if _, err := client.CreateSession(t.Context(), &SessionConfig{ + AskUserVariant: AskUserVariant("unknown"), + }); err == nil || !strings.Contains(err.Error(), "AskUserVariant") { + t.Fatalf("CreateSession error = %v, want invalid AskUserVariant error", err) + } + if _, err := client.ResumeSession(t.Context(), "cold-resume", &ResumeSessionConfig{ + AskUserVariant: AskUserVariant("unknown"), + }); err == nil || !strings.Contains(err.Error(), "AskUserVariant") { + t.Fatalf("ResumeSession error = %v, want invalid AskUserVariant error", err) + } +} + +func assertAskUserVariant(t *testing.T, params json.RawMessage, want string) { + t.Helper() + var payload map[string]any + if err := json.Unmarshal(params, &payload); err != nil { + t.Fatalf("failed to decode request params: %v", err) + } + got, present := payload["askUserVariant"] + if want == "" { + if present { + t.Fatalf("askUserVariant = %v, want omitted", got) + } + return + } + if got != want { + t.Fatalf("askUserVariant = %v, want %q", got, want) } - assertCapiEnableWebSocketResponses(t, <-resumeParams) } func TestClient_ForwardsAdditionalDirectoriesToSessionRequests(t *testing.T) { @@ -398,14 +848,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) { @@ -414,17 +865,18 @@ 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) { +func assertCapiOptions(t *testing.T, params json.RawMessage, want map[string]any) { t.Helper() var decoded map[string]any @@ -432,12 +884,18 @@ func assertCapiEnableWebSocketResponses(t *testing.T, params json.RawMessage) { t.Fatalf("failed to unmarshal request params: %v", err) } + if want == nil { + if _, present := decoded["capi"]; present { + t.Fatalf("expected capi to be omitted, got %v", decoded["capi"]) + } + return + } capi, ok := decoded["capi"].(map[string]any) if !ok { t.Fatalf("expected capi object in request params, got %T", decoded["capi"]) } - if capi["enableWebSocketResponses"] != false { - t.Fatalf("expected capi.enableWebSocketResponses=false, got %v", capi["enableWebSocketResponses"]) + if !reflect.DeepEqual(capi, want) { + t.Fatalf("expected capi %v, got %v", want, capi) } } @@ -445,6 +903,7 @@ func assertNewSessionOptions( t *testing.T, params json.RawMessage, expectedCitations bool, + expectedFileChangeTracking bool, expectedAgent string, expectedCredits float64, ) { @@ -457,6 +916,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"]) @@ -489,6 +951,225 @@ func sessionIDFromParams(t *testing.T, params json.RawMessage) string { return decoded.SessionID } +func TestClient_CreateSessionFailureClosesRegisteredSession(t *testing.T) { + tests := []struct { + name string + response func(string) (json.RawMessage, *jsonrpc2.Error) + wantErrSub string + }{ + { + name: "RPC failure", + response: func(string) (json.RawMessage, *jsonrpc2.Error) { + return nil, &jsonrpc2.Error{Code: -32000, Message: "session creation failed"} + }, + wantErrSub: "failed to create session", + }, + { + name: "invalid response", + response: func(string) (json.RawMessage, *jsonrpc2.Error) { + return json.RawMessage(`"invalid"`), nil + }, + wantErrSub: "failed to unmarshal response", + }, + { + name: "session ID mismatch", + response: func(string) (json.RawMessage, *jsonrpc2.Error) { + return json.RawMessage(`{"sessionId":"different-session"}`), nil + }, + wantErrSub: "but the caller requested failed-session", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rpcClient, server, _ := newRuntimeShutdownRpcPair(t) + t.Cleanup(server.Stop) + client := &Client{ + client: rpcClient, + RPC: rpc.NewServerRPC(rpcClient), + sessions: make(map[string]*Session), + } + + captured := make(chan *Session, 1) + server.SetRequestHandler("session.create", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { + sessionID := sessionIDFromParams(t, params) + client.sessionsMux.Lock() + session := client.sessions[sessionID] + client.sessionsMux.Unlock() + captured <- session + return tt.response(sessionID) + }) + + _, err := client.CreateSession(t.Context(), &SessionConfig{SessionID: "failed-session"}) + if err == nil || !strings.Contains(err.Error(), tt.wantErrSub) { + t.Fatalf("CreateSession error = %v, want substring %q", err, tt.wantErrSub) + } + + session := <-captured + if session == nil { + t.Fatal("session was not registered before session.create") + } + assertSessionEventChannelClosed(t, session) + assertSessionNotRegistered(t, client, "failed-session") + }) + } +} + +func TestClient_CreateSessionInitializationFailureClosesRegisteredSession(t *testing.T) { + rpcClient, server, _ := newRuntimeShutdownRpcPair(t) + t.Cleanup(server.Stop) + client := &Client{ + client: rpcClient, + RPC: rpc.NewServerRPC(rpcClient), + sessions: make(map[string]*Session), + options: ClientOptions{SessionFS: &SessionFSConfig{ + InitialWorkingDirectory: "/", + SessionStatePath: "/session-state", + Conventions: rpc.SessionFSSetProviderConventionsPosix, + Capabilities: &SessionFSCapabilities{Sqlite: true}, + }}, + } + + var captured *Session + _, err := client.CreateSession(t.Context(), &SessionConfig{ + SessionID: "failed-session-fs", + CreateSessionFSProvider: func(session *Session) SessionFSProvider { + captured = session + return noSQLiteSessionFSProvider{} + }, + }) + if err == nil || !strings.Contains(err.Error(), "does not implement SessionFSSqliteProvider") { + t.Fatalf("CreateSession error = %v, want SQLite provider validation error", err) + } + if captured == nil { + t.Fatal("CreateSessionFSProvider did not receive the registered session") + } + assertSessionEventChannelClosed(t, captured) + assertSessionNotRegistered(t, client, "failed-session-fs") +} + +func TestClient_ResumeSessionFailureClosesRegisteredSession(t *testing.T) { + tests := []struct { + name string + response func(string) (json.RawMessage, *jsonrpc2.Error) + wantErrSub string + }{ + { + name: "RPC failure", + response: func(string) (json.RawMessage, *jsonrpc2.Error) { + return nil, &jsonrpc2.Error{Code: -32000, Message: "session resume failed"} + }, + wantErrSub: "failed to resume session", + }, + { + name: "invalid response", + response: func(string) (json.RawMessage, *jsonrpc2.Error) { + return json.RawMessage(`"invalid"`), nil + }, + wantErrSub: "failed to unmarshal response", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rpcClient, server, _ := newRuntimeShutdownRpcPair(t) + t.Cleanup(server.Stop) + client := &Client{ + client: rpcClient, + RPC: rpc.NewServerRPC(rpcClient), + sessions: make(map[string]*Session), + } + + captured := make(chan *Session, 1) + server.SetRequestHandler("session.resume", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { + sessionID := sessionIDFromParams(t, params) + client.sessionsMux.Lock() + session := client.sessions[sessionID] + client.sessionsMux.Unlock() + captured <- session + return tt.response(sessionID) + }) + + _, err := client.ResumeSession(t.Context(), "resumed-session", &ResumeSessionConfig{}) + if err == nil || !strings.Contains(err.Error(), tt.wantErrSub) { + t.Fatalf("ResumeSession error = %v, want substring %q", err, tt.wantErrSub) + } + + session := <-captured + if session == nil { + t.Fatal("session was not registered before session.resume") + } + assertSessionEventChannelClosed(t, session) + assertSessionNotRegistered(t, client, "resumed-session") + }) + } +} + +func TestClient_ResumeSessionInitializationFailureClosesRegisteredSession(t *testing.T) { + rpcClient, server, _ := newRuntimeShutdownRpcPair(t) + t.Cleanup(server.Stop) + client := &Client{ + client: rpcClient, + RPC: rpc.NewServerRPC(rpcClient), + sessions: make(map[string]*Session), + options: ClientOptions{SessionFS: &SessionFSConfig{ + InitialWorkingDirectory: "/", + SessionStatePath: "/session-state", + Conventions: rpc.SessionFSSetProviderConventionsPosix, + Capabilities: &SessionFSCapabilities{Sqlite: true}, + }}, + } + + var captured *Session + _, err := client.ResumeSession(t.Context(), "resumed-session-fs", &ResumeSessionConfig{ + CreateSessionFSProvider: func(session *Session) SessionFSProvider { + captured = session + return noSQLiteSessionFSProvider{} + }, + }) + if err == nil || !strings.Contains(err.Error(), "does not implement SessionFSSqliteProvider") { + t.Fatalf("ResumeSession error = %v, want SQLite provider validation error", err) + } + if captured == nil { + t.Fatal("CreateSessionFSProvider did not receive the registered session") + } + assertSessionEventChannelClosed(t, captured) + assertSessionNotRegistered(t, client, "resumed-session-fs") +} + +func assertSessionEventChannelClosed(t *testing.T, session *Session) { + t.Helper() + select { + case <-session.eventDone: + case <-time.After(time.Second): + t.Fatal("timed out waiting for session event processing to stop") + } +} + +func assertSessionNotRegistered(t *testing.T, client *Client, sessionID string) { + t.Helper() + client.sessionsMux.Lock() + defer client.sessionsMux.Unlock() + if _, ok := client.sessions[sessionID]; ok { + t.Fatalf("session %q is still registered", sessionID) + } +} + +type noSQLiteSessionFSProvider struct{} + +func (noSQLiteSessionFSProvider) ReadFile(string) (string, error) { return "", nil } +func (noSQLiteSessionFSProvider) WriteFile(string, string, *int) error { return nil } +func (noSQLiteSessionFSProvider) AppendFile(string, string, *int) error { return nil } +func (noSQLiteSessionFSProvider) Exists(string) (bool, error) { return false, nil } +func (noSQLiteSessionFSProvider) Stat(string) (*SessionFSFileInfo, error) { return nil, nil } +func (noSQLiteSessionFSProvider) MakeDirectory(string, bool, *int) error { return nil } +func (noSQLiteSessionFSProvider) ReadDirectory(string) ([]string, error) { return nil, nil } +func (noSQLiteSessionFSProvider) ReadDirectoryWithTypes(string) ([]rpc.SessionFSReaddirWithTypesEntry, error) { + return nil, nil +} +func (noSQLiteSessionFSProvider) Remove(string, bool, bool) error { return nil } +func (noSQLiteSessionFSProvider) Rename(string, string) error { return nil } + func assertRuntimeShutdownNotCalled(t *testing.T, shutdownCalled <-chan struct{}) { t.Helper() select { @@ -891,15 +1572,36 @@ func TestClient_SessionIdleTimeoutSeconds(t *testing.T) { }) } -func findCLIPathForTest() string { - base, err := filepath.Abs("../nodejs/node_modules/@github") - if err == nil { - matches, _ := filepath.Glob(filepath.Join(base, "copilot-*", "index.js")) - if len(matches) > 0 { - return matches[0] +func findCLIPathForTest(t *testing.T) string { + t.Helper() + + if cliPath := os.Getenv("COPILOT_CLI_PATH"); cliPath != "" { + if info, err := os.Stat(cliPath); err == nil && !info.IsDir() { + return cliPath } + t.Fatalf("COPILOT_CLI_PATH does not point to a file: %s", cliPath) + } + + nodeDir, err := filepath.Abs("../nodejs") + if err != nil { + t.Fatal(err) + } + command := exec.Command( + "node", + "node_modules/tsx/dist/cli.mjs", + "scripts/prepare-runtime.ts", + "--print-path", + ) + command.Dir = nodeDir + output, err := command.CombinedOutput() + if err != nil { + t.Fatalf("failed to prepare pinned Copilot CLI: %v\n%s", err, output) } - return "" + cliPath := strings.TrimSpace(string(output)) + if info, err := os.Stat(cliPath); err != nil || info.IsDir() { + t.Fatalf("prepared Copilot CLI path is not a file: %q", cliPath) + } + return cliPath } func TestCreateSessionRequest_ClientName(t *testing.T) { @@ -1103,9 +1805,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) @@ -1117,13 +1821,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) @@ -1135,11 +1842,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) @@ -1153,10 +1885,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) { @@ -1698,10 +2448,7 @@ func TestListModelsHandlerCachesResults(t *testing.T) { } func TestClient_StartContextCancellationDoesNotKillProcess(t *testing.T) { - cliPath := findCLIPathForTest() - if cliPath == "" { - t.Skip("CLI not found") - } + cliPath := findCLIPathForTest(t) client := NewClient(&ClientOptions{Connection: StdioConnection{Path: cliPath}}) t.Cleanup(func() { client.ForceStop() }) @@ -1724,10 +2471,7 @@ func TestClient_StartContextCancellationDoesNotKillProcess(t *testing.T) { } func TestClient_StartStopRace(t *testing.T) { - cliPath := findCLIPathForTest() - if cliPath == "" { - t.Skip("CLI not found") - } + cliPath := findCLIPathForTest(t) client := NewClient(&ClientOptions{Connection: StdioConnection{Path: cliPath}}) defer client.ForceStop() errChan := make(chan error) @@ -1891,6 +2635,175 @@ func TestClient_MCPAuthInterestRegistration(t *testing.T) { }) } +func findRequest(requests []recordedRequest, method string) (recordedRequest, bool) { + for _, r := range requests { + if r.Method == method { + return r, true + } + } + return recordedRequest{}, false +} + +func TestClient_EmptyModeIncludedBuiltinSkills(t *testing.T) { + t.Run("create post-patch sends empty includedBuiltinSkills", func(t *testing.T) { + client, requests, cleanup := newInMemoryClientWithOptions(t, &ClientOptions{ + Mode: ModeEmpty, + BaseDirectory: "/tmp/copilot-test", + }) + defer cleanup() + + session, err := client.CreateSession(t.Context(), &SessionConfig{ + OnPermissionRequest: PermissionHandler.ApproveAll, + OnEvent: func(SessionEvent) {}, + AvailableTools: []string{}, + }) + if err != nil { + t.Fatalf("CreateSession failed: %v", err) + } + defer session.Disconnect() + + update, ok := findRequest(requests.snapshot(), "session.options.update") + if !ok { + t.Fatalf("expected session.options.update in %+v", requests.snapshot()) + } + skills, present := update.Params["includedBuiltinSkills"] + if !present { + t.Fatalf("expected includedBuiltinSkills in patch, got %+v", update.Params) + } + if arr, isArr := skills.([]any); !isArr || len(arr) != 0 { + t.Fatalf("expected includedBuiltinSkills=[], got %#v", skills) + } + }) + + t.Run("resume post-patch sends empty includedBuiltinSkills", func(t *testing.T) { + client, requests, cleanup := newInMemoryClientWithOptions(t, &ClientOptions{ + Mode: ModeEmpty, + BaseDirectory: "/tmp/copilot-test", + }) + defer cleanup() + + session, err := client.ResumeSession(t.Context(), "session-empty", &ResumeSessionConfig{ + OnPermissionRequest: PermissionHandler.ApproveAll, + OnEvent: func(SessionEvent) {}, + AvailableTools: []string{}, + }) + if err != nil { + t.Fatalf("ResumeSession failed: %v", err) + } + defer session.Disconnect() + + update, ok := findRequest(requests.snapshot(), "session.options.update") + if !ok { + t.Fatalf("expected session.options.update in %+v", requests.snapshot()) + } + if arr, isArr := update.Params["includedBuiltinSkills"].([]any); !isArr || len(arr) != 0 { + t.Fatalf("expected includedBuiltinSkills=[], got %#v", update.Params["includedBuiltinSkills"]) + } + }) + + t.Run("resume preserves explicit built-in skill allowlist", func(t *testing.T) { + client, requests, cleanup := newInMemoryClientWithOptions(t, &ClientOptions{ + Mode: ModeEmpty, + BaseDirectory: "/tmp/copilot-test", + }) + defer cleanup() + + session, err := client.ResumeSessionWithOptions(t.Context(), "resume-skills", &ResumeSessionConfig{ + OnPermissionRequest: PermissionHandler.ApproveAll, + AvailableTools: []string{}, + IncludedBuiltinSkills: []string{"code-review"}, + }) + if err != nil { + t.Fatalf("ResumeSessionWithOptions failed: %v", err) + } + defer session.Disconnect() + + update, ok := findRequest(requests.snapshot(), "session.options.update") + if !ok { + t.Fatalf("expected session.options.update in %+v", requests.snapshot()) + } + skills, ok := update.Params["includedBuiltinSkills"].([]any) + if !ok || len(skills) != 1 || skills[0] != "code-review" { + t.Fatalf("expected includedBuiltinSkills=[code-review], got %#v", update.Params["includedBuiltinSkills"]) + } + }) + + t.Run("caller opting into custom skills keeps includedBuiltinSkills empty", func(t *testing.T) { + client, requests, cleanup := newInMemoryClientWithOptions(t, &ClientOptions{ + Mode: ModeEmpty, + BaseDirectory: "/tmp/copilot-test", + }) + defer cleanup() + + session, err := client.CreateSession(t.Context(), &SessionConfig{ + OnPermissionRequest: PermissionHandler.ApproveAll, + OnEvent: func(SessionEvent) {}, + AvailableTools: []string{}, + EnableSkills: Bool(true), + SkillDirectories: []string{"/tmp/custom-skills"}, + }) + if err != nil { + t.Fatalf("CreateSession failed: %v", err) + } + defer session.Disconnect() + + update, ok := findRequest(requests.snapshot(), "session.options.update") + if !ok { + t.Fatalf("expected session.options.update in %+v", requests.snapshot()) + } + if arr, isArr := update.Params["includedBuiltinSkills"].([]any); !isArr || len(arr) != 0 { + t.Fatalf("expected includedBuiltinSkills=[], got %#v", update.Params["includedBuiltinSkills"]) + } + }) + + t.Run("explicit built-in skill allowlist is preserved in empty mode", func(t *testing.T) { + client, requests, cleanup := newInMemoryClientWithOptions(t, &ClientOptions{ + Mode: ModeEmpty, + BaseDirectory: "/tmp/copilot-test", + }) + defer cleanup() + + session, err := client.CreateSession(t.Context(), &SessionConfig{ + OnPermissionRequest: PermissionHandler.ApproveAll, + AvailableTools: []string{}, + IncludedBuiltinSkills: []string{"code-review"}, + }) + if err != nil { + t.Fatalf("CreateSession failed: %v", err) + } + defer session.Disconnect() + + update, ok := findRequest(requests.snapshot(), "session.options.update") + if !ok { + t.Fatalf("expected session.options.update in %+v", requests.snapshot()) + } + skills, ok := update.Params["includedBuiltinSkills"].([]any) + if !ok || len(skills) != 1 || skills[0] != "code-review" { + t.Fatalf("expected includedBuiltinSkills=[code-review], got %#v", update.Params["includedBuiltinSkills"]) + } + }) + + t.Run("copilot-cli mode does not inject includedBuiltinSkills", func(t *testing.T) { + client, requests, cleanup := newInMemoryClient(t) + defer cleanup() + + session, err := client.CreateSession(t.Context(), &SessionConfig{ + OnPermissionRequest: PermissionHandler.ApproveAll, + OnEvent: func(SessionEvent) {}, + }) + if err != nil { + t.Fatalf("CreateSession failed: %v", err) + } + defer session.Disconnect() + + for _, r := range requests.snapshot() { + if _, present := r.Params["includedBuiltinSkills"]; present { + t.Fatalf("did not expect includedBuiltinSkills in %s params %+v", r.Method, r.Params) + } + } + }) +} + type recordedRequest struct { Method string Params map[string]any @@ -1923,13 +2836,18 @@ func (r *requestRecorder) clear() { func newInMemoryClient(t *testing.T) (*Client, *requestRecorder, func()) { t.Helper() + return newInMemoryClientWithOptions(t, &ClientOptions{}) +} + +func newInMemoryClientWithOptions(t *testing.T, opts *ClientOptions) (*Client, *requestRecorder, func()) { + t.Helper() stdinR, stdinW := io.Pipe() stdoutR, stdoutW := io.Pipe() rpcClient := jsonrpc2.NewClient(stdinW, stdoutR) rpcClient.Start() - client := NewClient(&ClientOptions{}) + client := NewClient(opts) client.client = rpcClient client.RPC = rpc.NewServerRPC(rpcClient) client.state = stateConnected @@ -2228,7 +3146,7 @@ func TestResumeSessionRequest_Commands(t *testing.T) { req := resumeSessionRequest{ SessionID: "s1", Commands: []wireCommand{ - {Name: "deploy", Description: "Deploy the app"}, + {Name: "deploy"}, }, } data, err := json.Marshal(req) @@ -2250,6 +3168,9 @@ func TestResumeSessionRequest_Commands(t *testing.T) { if cmd0["name"] != "deploy" { t.Errorf("Expected command name 'deploy', got %v", cmd0["name"]) } + if description, ok := cmd0["description"]; !ok || description != "" { + t.Errorf("Expected empty command description, got %v", description) + } }) t.Run("omits commands from JSON when empty", func(t *testing.T) { @@ -3478,3 +4399,275 @@ func TestResumeSessionRequest_ExpAssignments(t *testing.T) { } }) } + +func TestSessionRequests_FeatureFlags(t *testing.T) { + featureFlags := map[string]bool{ + "ENABLED_TEST_FLAG": true, + "DISABLED_TEST_FLAG": false, + } + + for _, tc := range []struct { + name string + req any + }{ + {"create", createSessionRequest{FeatureFlags: &featureFlags}}, + {"resume", resumeSessionRequest{SessionID: "s1", FeatureFlags: &featureFlags}}, + } { + t.Run(tc.name, func(t *testing.T) { + data, err := json.Marshal(tc.req) + 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) + } + got := payload["featureFlags"].(map[string]any) + if got["ENABLED_TEST_FLAG"] != true || got["DISABLED_TEST_FLAG"] != false { + t.Errorf("Unexpected featureFlags: %v", got) + } + }) + } + + emptyFlags := map[string]bool{} + for _, tc := range []struct { + name string + req any + }{ + {"create empty", createSessionRequest{FeatureFlags: &emptyFlags}}, + {"resume empty", resumeSessionRequest{SessionID: "s1", FeatureFlags: &emptyFlags}}, + {"create unset", createSessionRequest{}}, + {"resume unset", resumeSessionRequest{SessionID: "s1"}}, + } { + t.Run(tc.name, func(t *testing.T) { + data, err := json.Marshal(tc.req) + 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) + } + _, present := payload["featureFlags"] + if strings.Contains(tc.name, "empty") != present { + t.Errorf("featureFlags presence = %v for %s", present, tc.name) + } + }) + } +} + +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("accepts future bypass-permissions modes", func(t *testing.T) { + req := createSessionRequest{ManagedSettings: &ManagedSettings{ + Permissions: &ManagedSettingsPermissions{ + DisableBypassPermissionsMode: DisableBypassPermissionsMode("future-fail-closed-mode"), + }, + }} + 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) + } + perms := m["managedSettings"].(map[string]any)["permissions"].(map[string]any) + if perms["disableBypassPermissionsMode"] != "future-fail-closed-mode" { + t.Errorf("Expected future mode preserved, got %v", perms["disableBypassPermissionsMode"]) + } + }) + + 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/cmd/bundler/main.go b/go/cmd/bundler/main.go index e63d1fde66..7ee078eea0 100644 --- a/go/cmd/bundler/main.go +++ b/go/cmd/bundler/main.go @@ -8,7 +8,7 @@ // --platform: Target platform using Go conventions (linux/amd64, linux/arm64, darwin/amd64, darwin/arm64, windows/amd64, windows/arm64). Defaults to current platform. // --output: Output directory for embedded artifacts. Defaults to the current directory. // --cli-version: CLI version to download. If not specified, automatically detects from the copilot-sdk version in go.mod. -// --check-only: Check that embedded CLI version matches the detected version from package-lock.json without downloading. Exits with error if versions don't match. +// --check-only: Check that embedded CLI version matches the detected version from package.json without downloading. Exits with error if versions don't match. package main import ( @@ -19,6 +19,9 @@ import ( "encoding/json" "flag" "fmt" + "go/build" + "go/parser" + "go/token" "io" "net/http" "os" @@ -33,10 +36,12 @@ import ( const ( // Keep these URLs centralized so reviewers can verify all outbound calls in one place. - sdkModule = "github.com/github/copilot-sdk/go" - packageLockURLFmt = "https://raw.githubusercontent.com/github/copilot-sdk/%s/nodejs/package-lock.json" - tarballURLFmt = "https://registry.npmjs.org/@github/copilot-%s/-/copilot-%s-%s.tgz" - licenseTarballFmt = "https://registry.npmjs.org/@github/copilot/-/copilot-%s.tgz" + sdkModule = "github.com/github/copilot-sdk/go" + packageJSONURLFmt = "https://raw.githubusercontent.com/github/copilot-sdk/%s/nodejs/package.json" + packageLockURLFmt = "https://raw.githubusercontent.com/github/copilot-sdk/%s/nodejs/package-lock.json" + tarballURLFmt = "https://registry.npmjs.org/@github/copilot-%s/-/copilot-%s-%s.tgz" + licenseTarballFmt = "https://registry.npmjs.org/@github/copilot/-/copilot-%s.tgz" + defaultPackageName = "main" ) // Platform info: npm package suffix, binary name @@ -89,23 +94,27 @@ func main() { return } + pkgName, err := detectPackageName(*output, goos, goarch) + if err != nil { + fmt.Fprintf(os.Stderr, "Warning: failed to detect package name: %v; using package %s\n", err, pkgName) + } + fmt.Printf("Building bundle for %s (CLI version %s)\n", *platform, version) - binaryPath, sha256Hash, runtimeArtifactPath, runtimeHash, err := buildBundle(info, version, outputPath, goos) + bundle, err := buildBundle(info, version, outputPath, goos) if err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } - var muslBinaryPath, muslRuntimeArtifactPath string - var muslBinaryHash, muslRuntimeHash []byte + var muslBundle bundleArtifacts if goos == "linux" { muslInfo := platformInfo{ npmPlatform: strings.Replace(info.npmPlatform, "linux-", "linuxmusl-", 1), binaryName: info.binaryName, } muslOutputPath := filepath.Join(*output, defaultOutputFileName(version, "linuxmusl", goarch, info.binaryName)) - muslBinaryPath, muslBinaryHash, muslRuntimeArtifactPath, muslRuntimeHash, err = buildBundle( + muslBundle, err = buildBundle( muslInfo, version, muslOutputPath, @@ -116,21 +125,33 @@ func main() { os.Exit(1) } } + if err := downloadCLILicense(version, outputPath); err != nil { + fmt.Fprintf(os.Stderr, "Error: failed to download CLI license: %v\n", err) + os.Exit(1) + } // Generate the Go file with embed directive if err := generateGoFile( goos, goarch, - binaryPath, + bundle.binaryPath, version, - sha256Hash, - runtimeArtifactPath, - runtimeHash, - muslBinaryPath, - muslBinaryHash, - muslRuntimeArtifactPath, - muslRuntimeHash, - "main", + bundle.binaryHash, + bundle.runtimeArtifactPath, + bundle.runtimeHash, + bundle.wrapperArtifactPath, + bundle.wrapperHash, + bundle.assetsArtifactPath, + bundle.assetsHash, + muslBundle.binaryPath, + muslBundle.binaryHash, + muslBundle.runtimeArtifactPath, + muslBundle.runtimeHash, + muslBundle.wrapperArtifactPath, + muslBundle.wrapperHash, + muslBundle.assetsArtifactPath, + muslBundle.assetsHash, + pkgName, ); err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) @@ -186,10 +207,64 @@ func validPlatforms() []string { return result } +// detectPackageName reads package clauses from files that match the target +// platform and build constraints. It returns defaultPackageName with an error +// when detection fails. +func detectPackageName(dir, goos, goarch string) (string, error) { + if dir == "" { + dir = "." + } + + entries, err := os.ReadDir(dir) + if err != nil { + return defaultPackageName, fmt.Errorf("failed to read package directory %q: %w", dir, err) + } + + buildContext := build.Default + buildContext.GOOS = goos + buildContext.GOARCH = goarch + + packageName := "" + for _, entry := range entries { + name := entry.Name() + if entry.IsDir() || !strings.HasSuffix(name, ".go") || + strings.HasPrefix(name, ".") || strings.HasPrefix(name, "_") || + strings.HasSuffix(name, "_test.go") || strings.HasPrefix(name, "zcopilot_") { + continue + } + matches, err := buildContext.MatchFile(dir, name) + if err != nil { + return defaultPackageName, fmt.Errorf("failed to evaluate build constraints in %q: %w", filepath.Join(dir, name), err) + } + if !matches { + continue + } + + path := filepath.Join(dir, name) + file, err := parser.ParseFile(token.NewFileSet(), path, nil, parser.PackageClauseOnly) + if err != nil { + return defaultPackageName, fmt.Errorf("failed to parse package clause in %q: %w", path, err) + } + + if packageName == "" { + packageName = file.Name.Name + continue + } + if packageName != file.Name.Name { + return defaultPackageName, fmt.Errorf("multiple packages %q and %q found in %q", packageName, file.Name.Name, dir) + } + } + + if packageName == "" { + return defaultPackageName, fmt.Errorf("no Go package found in %q", dir) + } + return packageName, nil +} + // detectCLIVersion detects the CLI version by: // 1. Running "go list -m" to get the copilot-sdk version from the user's go.mod -// 2. Fetching the package-lock.json from the SDK repo at that version -// 3. Extracting the @github/copilot CLI version from it +// 2. Fetching package.json from the SDK repo at that version +// 3. Extracting the pinned Copilot CLI version from it func detectCLIVersion() (string, error) { // Get the SDK version from the user's go.mod sdkVersion, err := getSDKVersion() @@ -199,7 +274,7 @@ func detectCLIVersion() (string, error) { fmt.Printf("Found copilot-sdk %s in go.mod\n", sdkVersion) - // Fetch package-lock.json from the SDK repo at that version + // Fetch package.json from the SDK repo at that version cliVersion, err := fetchCLIVersionFromRepo(sdkVersion) if err != nil { return "", fmt.Errorf("failed to fetch CLI version: %w", err) @@ -227,7 +302,7 @@ func getSDKVersion() (string, error) { return version, nil } -// fetchCLIVersionFromRepo fetches package-lock.json from GitHub and extracts the CLI version. +// fetchCLIVersionFromRepo fetches package.json from GitHub and extracts the CLI version. func fetchCLIVersionFromRepo(sdkVersion string) (string, error) { // Convert Go module version to Git ref // v0.1.0 -> v0.1.0 @@ -245,7 +320,7 @@ func fetchCLIVersionFromRepo(sdkVersion string) (string, error) { } } - url := fmt.Sprintf(packageLockURLFmt, gitRef) + url := fmt.Sprintf(packageJSONURLFmt, gitRef) fmt.Printf("Fetching %s...\n", url) resp, err := http.Get(url) @@ -255,7 +330,35 @@ func fetchCLIVersionFromRepo(sdkVersion string) (string, error) { defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return "", fmt.Errorf("failed to fetch package-lock.json: %s", resp.Status) + return "", fmt.Errorf("failed to fetch package.json: %s", resp.Status) + } + + var packageJSON struct { + CopilotCLIVersion string `json:"copilotCliVersion"` + } + + if err := json.NewDecoder(resp.Body).Decode(&packageJSON); err != nil { + return "", fmt.Errorf("failed to parse package.json: %w", err) + } + + if packageJSON.CopilotCLIVersion == "" { + return fetchLegacyCLIVersionFromRepo(gitRef) + } + + return packageJSON.CopilotCLIVersion, nil +} + +func fetchLegacyCLIVersionFromRepo(gitRef string) (string, error) { + url := fmt.Sprintf(packageLockURLFmt, gitRef) + fmt.Printf("Falling back to %s...\n", url) + + resp, err := http.Get(url) + if err != nil { + return "", fmt.Errorf("failed to fetch legacy package-lock.json: %w", err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("failed to fetch legacy package-lock.json: %s", resp.Status) } var packageLock struct { @@ -263,16 +366,13 @@ func fetchCLIVersionFromRepo(sdkVersion string) (string, error) { Version string `json:"version"` } `json:"packages"` } - if err := json.NewDecoder(resp.Body).Decode(&packageLock); err != nil { - return "", fmt.Errorf("failed to parse package-lock.json: %w", err) + return "", fmt.Errorf("failed to parse legacy package-lock.json: %w", err) } - pkg, ok := packageLock.Packages["node_modules/@github/copilot"] if !ok || pkg.Version == "" { - return "", fmt.Errorf("could not find @github/copilot version in package-lock.json") + return "", fmt.Errorf("could not find copilotCliVersion in package.json or @github/copilot in package-lock.json") } - return pkg.Version, nil } @@ -286,96 +386,131 @@ func isHex(s string) bool { return true } -// buildBundle downloads the CLI binary (and, when the CLI package ships it, the -// native in-process runtime library) and writes them to outputPath's directory. -// It returns the CLI bundle path and hash, plus the runtime-library artifact path -// and hash (both empty when the package does not ship the runtime library). -func buildBundle(info platformInfo, cliVersion, outputPath, goos string) (string, []byte, string, []byte, error) { +type bundleArtifacts struct { + binaryPath string + binaryHash []byte + runtimeArtifactPath string + runtimeHash []byte + wrapperArtifactPath string + wrapperHash []byte + assetsArtifactPath string + assetsHash []byte +} + +// buildBundle downloads the CLI and native runtime artifacts from one platform package. +func buildBundle(info platformInfo, cliVersion, outputPath, goos string) (bundleArtifacts, error) { outputDir := filepath.Dir(outputPath) if outputDir == "" { outputDir = "." } runtimeArtifactPath := filepath.Join(outputDir, runtimeLibArtifactName(cliVersion, info.npmPlatform, goos)) + wrapperArtifactPath := filepath.Join(outputDir, runtimeWrapperArtifactName(cliVersion, info.npmPlatform, info.binaryName)) + assetsArtifactPath := filepath.Join(outputDir, runtimeAssetsArtifactName(cliVersion, info.npmPlatform)) - // Check if output already exists - if _, err := os.Stat(outputPath); err == nil { + if filesExist(outputPath, runtimeArtifactPath, wrapperArtifactPath, assetsArtifactPath) { // Idempotent output avoids re-downloading in CI or local rebuilds. - fmt.Printf("Output %s already exists, skipping download\n", outputPath) - sha256Hash, err := sha256FileFromCompressed(outputPath) + fmt.Printf("Output runtime bundle for %s already exists, skipping download\n", info.npmPlatform) + binaryHash, err := sha256FileFromCompressed(outputPath) if err != nil { - return "", nil, "", nil, fmt.Errorf("failed to hash existing output: %w", err) + return bundleArtifacts{}, fmt.Errorf("failed to hash existing output: %w", err) } - if err := downloadCLILicense(cliVersion, outputPath); err != nil { - return "", nil, "", nil, fmt.Errorf("failed to download CLI license: %w", err) + runtimeHash, err := sha256FileFromCompressed(runtimeArtifactPath) + if err != nil { + return bundleArtifacts{}, fmt.Errorf("failed to hash existing runtime.node: %w", err) } - // Reuse an existing runtime-library artifact if present. - if _, err := os.Stat(runtimeArtifactPath); err == nil { - runtimeHash, err := sha256FileFromCompressed(runtimeArtifactPath) - if err != nil { - return "", nil, "", nil, fmt.Errorf("failed to hash existing runtime library: %w", err) - } - return outputPath, sha256Hash, runtimeArtifactPath, runtimeHash, nil + wrapperHash, err := sha256FileFromCompressed(wrapperArtifactPath) + if err != nil { + return bundleArtifacts{}, fmt.Errorf("failed to hash existing runtime wrapper: %w", err) } - return outputPath, sha256Hash, "", nil, nil + assetsHash, err := sha256File(assetsArtifactPath) + if err != nil { + return bundleArtifacts{}, fmt.Errorf("failed to hash existing runtime assets: %w", err) + } + return bundleArtifacts{outputPath, binaryHash, runtimeArtifactPath, runtimeHash, wrapperArtifactPath, wrapperHash, assetsArtifactPath, assetsHash}, nil } + // Create temp directory for download tempDir, err := os.MkdirTemp("", "copilot-bundler-*") if err != nil { - return "", nil, "", nil, fmt.Errorf("failed to create temp dir: %w", err) + return bundleArtifacts{}, fmt.Errorf("failed to create temp dir: %w", err) } defer os.RemoveAll(tempDir) - // Download the binary binaryPath, tarballPath, err := downloadCLIBinary(info.npmPlatform, info.binaryName, cliVersion, tempDir) if err != nil { - return "", nil, "", nil, fmt.Errorf("failed to download CLI binary: %w", err) + return bundleArtifacts{}, fmt.Errorf("failed to download CLI binary: %w", err) } - // Create output directory if needed if outputDir != "." { if err := os.MkdirAll(outputDir, 0755); err != nil { - return "", nil, "", nil, fmt.Errorf("failed to create output directory: %w", err) + return bundleArtifacts{}, fmt.Errorf("failed to create output directory: %w", err) } } - sha256Hash, err := sha256File(binaryPath) + binaryHash, err := sha256File(binaryPath) if err != nil { - return "", nil, "", nil, fmt.Errorf("failed to hash output binary: %w", err) + return bundleArtifacts{}, fmt.Errorf("failed to hash output binary: %w", err) } if err := compressZstdFile(binaryPath, outputPath); err != nil { - return "", nil, "", nil, fmt.Errorf("failed to write output binary: %w", err) - } - if err := downloadCLILicense(cliVersion, outputPath); err != nil { - return "", nil, "", nil, fmt.Errorf("failed to download CLI license: %w", err) + return bundleArtifacts{}, fmt.Errorf("failed to write output binary: %w", err) } - // Extract the native in-process runtime library from the same tarball, if the - // package ships it (older CLI versions do not). Missing is not an error — the - // generated file simply omits the runtime embed for that platform. rawLibPath := filepath.Join(tempDir, "runtime.node") - found, err := extractOptionalFileFromTarball(tarballPath, tempDir, - "package/prebuilds/"+info.npmPlatform+"/runtime.node", "runtime.node") + if err := extractFileFromTarball( + tarballPath, + tempDir, + "package/prebuilds/"+info.npmPlatform+"/runtime.node", + "runtime.node", + ); err != nil { + return bundleArtifacts{}, fmt.Errorf("runtime package is missing prebuilds/%s/runtime.node: %w", info.npmPlatform, err) + } + runtimeHash, err := sha256File(rawLibPath) if err != nil { - return "", nil, "", nil, fmt.Errorf("failed to extract runtime library: %w", err) + return bundleArtifacts{}, fmt.Errorf("failed to hash runtime.node: %w", err) } - var runtimeHash []byte - returnedRuntimeArtifact := "" - if found { - runtimeHash, err = sha256File(rawLibPath) - if err != nil { - return "", nil, "", nil, fmt.Errorf("failed to hash runtime library: %w", err) - } - if err := compressZstdFile(rawLibPath, runtimeArtifactPath); err != nil { - return "", nil, "", nil, fmt.Errorf("failed to write runtime library: %w", err) - } - returnedRuntimeArtifact = runtimeArtifactPath - fmt.Printf("Successfully created %s\n", runtimeArtifactPath) - } else { - fmt.Printf("Package %s does not ship a runtime library; in-process transport unavailable for this platform bundle\n", info.npmPlatform) + if err := compressZstdFile(rawLibPath, runtimeArtifactPath); err != nil { + return bundleArtifacts{}, fmt.Errorf("failed to write runtime.node: %w", err) + } + + wrapperName := runtimeWrapperName(info.binaryName) + rawWrapperPath := filepath.Join(tempDir, wrapperName) + if err := extractFileFromTarball( + tarballPath, + tempDir, + "package/prebuilds/"+info.npmPlatform+"/"+wrapperName, + wrapperName, + ); err != nil { + return bundleArtifacts{}, fmt.Errorf("runtime package is missing prebuilds/%s/%s: %w", info.npmPlatform, wrapperName, err) + } + wrapperHash, err := sha256File(rawWrapperPath) + if err != nil { + return bundleArtifacts{}, fmt.Errorf("failed to hash runtime wrapper: %w", err) + } + if err := compressZstdFile(rawWrapperPath, wrapperArtifactPath); err != nil { + return bundleArtifacts{}, fmt.Errorf("failed to write runtime wrapper: %w", err) + } + if err := createRuntimeAssetsArchive(tarballPath, assetsArtifactPath, info); err != nil { + return bundleArtifacts{}, fmt.Errorf("failed to write runtime assets: %w", err) + } + assetsHash, err := sha256File(assetsArtifactPath) + if err != nil { + return bundleArtifacts{}, fmt.Errorf("failed to hash runtime assets: %w", err) } fmt.Printf("Successfully created %s\n", outputPath) - return outputPath, sha256Hash, returnedRuntimeArtifact, runtimeHash, nil + fmt.Printf("Successfully created %s\n", runtimeArtifactPath) + fmt.Printf("Successfully created %s\n", wrapperArtifactPath) + fmt.Printf("Successfully created %s\n", assetsArtifactPath) + return bundleArtifacts{outputPath, binaryHash, runtimeArtifactPath, runtimeHash, wrapperArtifactPath, wrapperHash, assetsArtifactPath, assetsHash}, nil +} + +func filesExist(paths ...string) bool { + for _, path := range paths { + if _, err := os.Stat(path); err != nil { + return false + } + } + return true } // runtimeLibArtifactName builds the compressed runtime-library artifact filename. @@ -383,6 +518,117 @@ func runtimeLibArtifactName(version, npmPlatform, goos string) string { return fmt.Sprintf("zcopilotruntime_%s_%s.%s.zst", version, npmPlatform, runtimeLibExt(goos)) } +func runtimeWrapperArtifactName(version, npmPlatform, binaryName string) string { + return fmt.Sprintf("zcopilotruntimewrapper_%s_%s_%s.zst", version, npmPlatform, runtimeWrapperName(binaryName)) +} + +func runtimeAssetsArtifactName(version, npmPlatform string) string { + return fmt.Sprintf("zcopilotruntimeassets_%s_%s.tgz", version, npmPlatform) +} + +func runtimeWrapperName(binaryName string) string { + if filepath.Ext(binaryName) == ".exe" { + return "copilot-runtime.exe" + } + return "copilot-runtime" +} + +var hostlessExcludedTopLevel = map[string]bool{ + "app.js": true, "assets": true, "changelog.json": true, "copilot": true, "copilot.exe": true, + "foundry-local-sdk": true, "index.js": true, "napi-oop-runtime": true, "LICENSE.md": true, + "npm-loader.js": true, "package.json": true, "pvrecorder": true, "queries": true, "README.md": true, + "sea-loader.js": true, "webview": true, +} + +func hostlessRuntimePath(name, npmPlatform, wrapperName string) (string, bool) { + relative, ok := strings.CutPrefix(name, "package/") + if !ok { + return "", false + } + parts := strings.Split(relative, "/") + topLevel := parts[0] + fileName := parts[len(parts)-1] + if hostlessExcludedTopLevel[topLevel] || + (strings.HasPrefix(topLevel, "tree-sitter") && strings.HasSuffix(topLevel, ".wasm")) || + (strings.HasPrefix(topLevel, "voice-") && strings.HasSuffix(topLevel, ".js")) || + fileName == "cli-native.node" || fileName == "runtime.node" || fileName == wrapperName || + strings.HasPrefix(fileName, "copilot-runtime-bin") { + return "", false + } + for _, part := range parts { + if part == "mediaremote-adapter" { + return "", false + } + } + if topLevel == "prebuilds" { + if len(parts) < 3 || parts[1] != npmPlatform { + return "", false + } + return strings.Join(parts[2:], "/"), true + } + return relative, true +} + +func createRuntimeAssetsArchive(tarballPath, outputPath string, info platformInfo) error { + sourceFile, err := os.Open(tarballPath) + if err != nil { + return err + } + defer sourceFile.Close() + gzipReader, err := gzip.NewReader(sourceFile) + if err != nil { + return err + } + defer gzipReader.Close() + outputFile, err := os.Create(outputPath) + if err != nil { + return err + } + defer outputFile.Close() + gzipWriter := gzip.NewWriter(outputFile) + tarWriter := tar.NewWriter(gzipWriter) + count := 0 + sourceTar := tar.NewReader(gzipReader) + for { + header, err := sourceTar.Next() + if err == io.EOF { + break + } + if err != nil { + return err + } + if header.Typeflag != tar.TypeReg { + continue + } + destination, include := hostlessRuntimePath( + header.Name, + info.npmPlatform, + runtimeWrapperName(info.binaryName), + ) + if !include { + continue + } + outputHeader := &tar.Header{ + Name: destination, Mode: header.Mode, Size: header.Size, Typeflag: tar.TypeReg, + Uid: 0, Gid: 0, + } + if err := tarWriter.WriteHeader(outputHeader); err != nil { + return err + } + if _, err := io.Copy(tarWriter, sourceTar); err != nil { + return err + } + count++ + } + if count == 0 { + return fmt.Errorf("runtime package contains no retained assets") + } + if err := tarWriter.Close(); err != nil { + return err + } + return gzipWriter.Close() +} + // runtimeLibExt returns the shared-library extension for the target OS. func runtimeLibExt(goos string) string { switch goos { @@ -406,10 +652,18 @@ func generateGoFile( sha256Hash []byte, runtimeArtifactPath string, runtimeHash []byte, + wrapperArtifactPath string, + wrapperHash []byte, + assetsArtifactPath string, + assetsHash []byte, muslBinaryPath string, muslBinaryHash []byte, muslRuntimeArtifactPath string, muslRuntimeHash []byte, + muslWrapperArtifactPath string, + muslWrapperHash []byte, + muslAssetsArtifactPath string, + muslAssetsHash []byte, pkgName string, ) error { binaryName := filepath.Base(binaryPath) @@ -428,12 +682,20 @@ func generateGoFile( licenseName, cliVersion, hashBase64, - "", - nil, - "", - nil, - "", - nil, + runtimeArtifactPath, + runtimeHash, + wrapperArtifactPath, + wrapperHash, + assetsArtifactPath, + assetsHash, + muslBinaryPath, + muslBinaryHash, + muslRuntimeArtifactPath, + muslRuntimeHash, + muslWrapperArtifactPath, + muslWrapperHash, + muslAssetsArtifactPath, + muslAssetsHash, ) if err := os.WriteFile(defaultPath, []byte(defaultContent), 0644); err != nil { return err @@ -449,10 +711,18 @@ func generateGoFile( hashBase64, runtimeArtifactPath, runtimeHash, + wrapperArtifactPath, + wrapperHash, + assetsArtifactPath, + assetsHash, muslBinaryPath, muslBinaryHash, muslRuntimeArtifactPath, muslRuntimeHash, + muslWrapperArtifactPath, + muslWrapperHash, + muslAssetsArtifactPath, + muslAssetsHash, ) if err := os.WriteFile(inProcessPath, []byte(inProcessContent), 0644); err != nil { return err @@ -472,24 +742,48 @@ func generatedGoFileContent( hashBase64, runtimeArtifactPath string, runtimeHash []byte, + wrapperArtifactPath string, + wrapperHash []byte, + assetsArtifactPath string, + assetsHash []byte, muslBinaryPath string, muslBinaryHash []byte, muslRuntimeArtifactPath string, muslRuntimeHash []byte, + muslWrapperArtifactPath string, + muslWrapperHash []byte, + muslAssetsArtifactPath string, + muslAssetsHash []byte, ) string { runtimeEmbed := "" runtimeConfig := "" runtimeReader := "" - if runtimeArtifactPath != "" { + if runtimeArtifactPath != "" && wrapperArtifactPath != "" && assetsArtifactPath != "" { runtimeArtifactName := filepath.Base(runtimeArtifactPath) runtimeHashBase64 := base64.StdEncoding.EncodeToString(runtimeHash) + wrapperArtifactName := filepath.Base(wrapperArtifactPath) + wrapperHashBase64 := base64.StdEncoding.EncodeToString(wrapperHash) + assetsArtifactName := filepath.Base(assetsArtifactPath) + assetsHashBase64 := base64.StdEncoding.EncodeToString(assetsHash) runtimeEmbed = fmt.Sprintf(` //go:embed %s var localEmbeddedCopilotRuntimeLib []byte -`, runtimeArtifactName) + +//go:embed %s +var localEmbeddedCopilotRuntimeExecutable []byte + +//go:embed %s +var localEmbeddedCopilotRuntimeAssets []byte +`, runtimeArtifactName, wrapperArtifactName, assetsArtifactName) runtimeConfig = fmt.Sprintf(` - RuntimeLib: runtimeLibReader(), - RuntimeLibHash: mustDecodeBase64(%q),`, runtimeHashBase64) + RuntimeLib: runtimeLibReader(), + RuntimeLibHash: mustDecodeBase64(%q), + RuntimeNode: runtimeLibReader(), + RuntimeNodeHash: mustDecodeBase64(%q), + RuntimeExecutable: runtimeExecutableReader(), + RuntimeExecutableHash: mustDecodeBase64(%q), + RuntimeAssets: bytes.NewReader(localEmbeddedCopilotRuntimeAssets), + RuntimeAssetsHash: mustDecodeBase64(%q),`, runtimeHashBase64, runtimeHashBase64, wrapperHashBase64, assetsHashBase64) runtimeReader = ` func runtimeLibReader() io.Reader { r, err := zstd.NewReader(bytes.NewReader(localEmbeddedCopilotRuntimeLib)) @@ -498,29 +792,53 @@ func runtimeLibReader() io.Reader { } return r } + +func runtimeExecutableReader() io.Reader { + r, err := zstd.NewReader(bytes.NewReader(localEmbeddedCopilotRuntimeExecutable)) + if err != nil { + panic("failed to create zstd reader: " + err.Error()) + } + return r +} ` } muslEmbed := "" muslConfig := "" muslReaders := "" - if muslBinaryPath != "" && muslRuntimeArtifactPath != "" { + if muslBinaryPath != "" && muslRuntimeArtifactPath != "" && muslWrapperArtifactPath != "" && muslAssetsArtifactPath != "" { muslBinaryName := filepath.Base(muslBinaryPath) muslBinaryHashBase64 := base64.StdEncoding.EncodeToString(muslBinaryHash) muslRuntimeName := filepath.Base(muslRuntimeArtifactPath) muslRuntimeHashBase64 := base64.StdEncoding.EncodeToString(muslRuntimeHash) + muslWrapperName := filepath.Base(muslWrapperArtifactPath) + muslWrapperHashBase64 := base64.StdEncoding.EncodeToString(muslWrapperHash) + muslAssetsName := filepath.Base(muslAssetsArtifactPath) + muslAssetsHashBase64 := base64.StdEncoding.EncodeToString(muslAssetsHash) muslEmbed = fmt.Sprintf(` //go:embed %s var localEmbeddedCopilotCLILinuxMusl []byte //go:embed %s var localEmbeddedCopilotRuntimeLibLinuxMusl []byte -`, muslBinaryName, muslRuntimeName) + +//go:embed %s +var localEmbeddedCopilotRuntimeExecutableLinuxMusl []byte + +//go:embed %s +var localEmbeddedCopilotRuntimeAssetsLinuxMusl []byte +`, muslBinaryName, muslRuntimeName, muslWrapperName, muslAssetsName) muslConfig = fmt.Sprintf(` - LinuxMuslCli: linuxMuslCLIReader(), - LinuxMuslCliHash: mustDecodeBase64(%q), - LinuxMuslRuntimeLib: linuxMuslRuntimeLibReader(), - LinuxMuslRuntimeLibHash: mustDecodeBase64(%q),`, muslBinaryHashBase64, muslRuntimeHashBase64) + LinuxMuslCli: linuxMuslCLIReader(), + LinuxMuslCliHash: mustDecodeBase64(%q), + LinuxMuslRuntimeLib: linuxMuslRuntimeLibReader(), + LinuxMuslRuntimeLibHash: mustDecodeBase64(%q), + LinuxMuslRuntimeNode: linuxMuslRuntimeLibReader(), + LinuxMuslRuntimeNodeHash: mustDecodeBase64(%q), + LinuxMuslRuntimeExecutable: linuxMuslRuntimeExecutableReader(), + LinuxMuslRuntimeExecutableHash: mustDecodeBase64(%q), + LinuxMuslRuntimeAssets: bytes.NewReader(localEmbeddedCopilotRuntimeAssetsLinuxMusl), + LinuxMuslRuntimeAssetsHash: mustDecodeBase64(%q),`, muslBinaryHashBase64, muslRuntimeHashBase64, muslRuntimeHashBase64, muslWrapperHashBase64, muslAssetsHashBase64) muslReaders = ` func linuxMuslCLIReader() io.Reader { r, err := zstd.NewReader(bytes.NewReader(localEmbeddedCopilotCLILinuxMusl)) @@ -537,6 +855,14 @@ func linuxMuslRuntimeLibReader() io.Reader { } return r } + +func linuxMuslRuntimeExecutableReader() io.Reader { + r, err := zstd.NewReader(bytes.NewReader(localEmbeddedCopilotRuntimeExecutableLinuxMusl)) + if err != nil { + panic("failed to create zstd reader: " + err.Error()) + } + return r +} ` } diff --git a/go/cmd/bundler/main_test.go b/go/cmd/bundler/main_test.go index badc791359..9c09559b66 100644 --- a/go/cmd/bundler/main_test.go +++ b/go/cmd/bundler/main_test.go @@ -1,26 +1,189 @@ package main import ( + "archive/tar" + "bytes" + "compress/gzip" "go/parser" "go/token" + "io" "os" "path/filepath" "strings" "testing" ) -func TestGenerateGoFileGatesRuntimeEmbed(t *testing.T) { +func TestCreateRuntimeAssetsArchiveRetainsUnknownAssetsAndFiltersCLIContent(t *testing.T) { + dir := t.TempDir() + source := filepath.Join(dir, "package.tgz") + output := filepath.Join(dir, "assets.tgz") + writeTarGz(t, source, map[string]string{ + "package/prebuilds/linux-x64/runtime.node": "runtime", + "package/prebuilds/linux-x64/copilot-runtime": "wrapper", + "package/ripgrep/bin/linux-x64/rg": "ripgrep", + "package/definitions/future.json": "{}", + "package/copilot-sdk/extension.js": "extension", + "package/preloads/extension_bootstrap.mjs": "preload", + "package/sdk/factory.js": "factory", + "package/app.js": "excluded", + "package/LICENSE.md": "excluded", + "package/README.md": "excluded", + }) + + if err := createRuntimeAssetsArchive(source, output, platformInfo{ + npmPlatform: "linux-x64", + binaryName: "copilot", + }); err != nil { + t.Fatal(err) + } + + files := readTarGz(t, output) + if files["ripgrep/bin/linux-x64/rg"] != "ripgrep" || + files["definitions/future.json"] != "{}" || + files["copilot-sdk/extension.js"] != "extension" || + files["preloads/extension_bootstrap.mjs"] != "preload" || + files["sdk/factory.js"] != "factory" { + t.Fatalf("retained assets = %#v", files) + } + for _, excluded := range []string{ + "runtime.node", "copilot-runtime", "app.js", "LICENSE.md", "README.md", + } { + if _, ok := files[excluded]; ok { + t.Fatalf("excluded asset %q was retained", excluded) + } + } +} + +func writeTarGz(t *testing.T, path string, files map[string]string) { + t.Helper() + var buffer bytes.Buffer + gzipWriter := gzip.NewWriter(&buffer) + tarWriter := tar.NewWriter(gzipWriter) + for name, content := range files { + header := &tar.Header{Name: name, Mode: 0755, Size: int64(len(content)), Typeflag: tar.TypeReg} + if err := tarWriter.WriteHeader(header); err != nil { + t.Fatal(err) + } + if _, err := tarWriter.Write([]byte(content)); err != nil { + t.Fatal(err) + } + } + if err := tarWriter.Close(); err != nil { + t.Fatal(err) + } + if err := gzipWriter.Close(); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(path, buffer.Bytes(), 0644); err != nil { + t.Fatal(err) + } +} + +func readTarGz(t *testing.T, path string) map[string]string { + t.Helper() + file, err := os.Open(path) + if err != nil { + t.Fatal(err) + } + defer file.Close() + gzipReader, err := gzip.NewReader(file) + if err != nil { + t.Fatal(err) + } + files := map[string]string{} + tarReader := tar.NewReader(gzipReader) + for { + header, err := tarReader.Next() + if err == io.EOF { + return files + } + if err != nil { + t.Fatal(err) + } + content, err := io.ReadAll(tarReader) + if err != nil { + t.Fatal(err) + } + files[header.Name] = string(content) + } +} + +func TestDetectPackageName(t *testing.T) { + dir := t.TempDir() + files := map[string]string{ + "app_linux.go": "package application\n", + "app_test.go": "package application_test\n", + "app_windows.go": "package windowsapplication\n", + "tagged.go": "//go:build windows\n\npackage windowsapplication\n", + "zcopilot_linux_amd64.go": "package main\n", + "_ignored.go": "package ignored\n", + "zcopilot_inprocess_linux.go": "package main\n", + } + for name, content := range files { + if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0644); err != nil { + t.Fatal(err) + } + } + + for _, test := range []struct { + goos string + want string + }{ + {goos: "linux", want: "application"}, + {goos: "windows", want: "windowsapplication"}, + } { + t.Run(test.goos, func(t *testing.T) { + got, err := detectPackageName(dir, test.goos, "amd64") + if err != nil { + t.Fatal(err) + } + if got != test.want { + t.Fatalf("detectPackageName() = %q, want %q", got, test.want) + } + }) + } +} + +func TestDetectPackageNameFallsBackForMultiplePackages(t *testing.T) { + dir := t.TempDir() + for name, content := range map[string]string{ + "one.go": "package one\n", + "two.go": "package two\n", + } { + if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0644); err != nil { + t.Fatal(err) + } + } + + got, err := detectPackageName(dir, "linux", "amd64") + if err == nil { + t.Fatal("detectPackageName() succeeded for a directory containing multiple packages") + } + if got != defaultPackageName { + t.Fatalf("detectPackageName() = %q, want fallback %q", got, defaultPackageName) + } +} + +func TestGenerateGoFileEmbedsRuntimeWrapperPair(t *testing.T) { dir := t.TempDir() binaryPath := filepath.Join(dir, "copilot.zst") runtimePath := filepath.Join(dir, "runtime.node.zst") + wrapperPath := filepath.Join(dir, "copilot-runtime.zst") + assetsPath := filepath.Join(dir, "runtime-assets.tgz") muslBinaryPath := filepath.Join(dir, "copilot-musl.zst") muslRuntimePath := filepath.Join(dir, "runtime-musl.node.zst") + muslWrapperPath := filepath.Join(dir, "copilot-runtime-musl.zst") + muslAssetsPath := filepath.Join(dir, "runtime-assets-musl.tgz") for _, path := range []string{ binaryPath, licensePathForOutput(binaryPath), runtimePath, + wrapperPath, + assetsPath, muslBinaryPath, muslRuntimePath, + muslWrapperPath, + muslAssetsPath, } { if err := os.WriteFile(path, []byte("test"), 0644); err != nil { t.Fatal(err) @@ -36,10 +199,18 @@ func TestGenerateGoFileGatesRuntimeEmbed(t *testing.T) { hash, runtimePath, hash, + wrapperPath, + hash, + assetsPath, + hash, muslBinaryPath, hash, muslRuntimePath, hash, + muslWrapperPath, + hash, + muslAssetsPath, + hash, "main", ); err != nil { t.Fatal(err) @@ -52,8 +223,20 @@ func TestGenerateGoFileGatesRuntimeEmbed(t *testing.T) { if !strings.Contains(string(defaultSource), "//go:build !copilot_inprocess") { t.Fatal("default embed file does not exclude copilot_inprocess builds") } - if strings.Contains(string(defaultSource), "localEmbeddedCopilotRuntimeLib") { - t.Fatal("default embed file includes the native runtime") + if !strings.Contains(string(defaultSource), "localEmbeddedCopilotRuntimeExecutable") { + t.Fatal("default embed file does not include the runtime wrapper") + } + if !strings.Contains(string(defaultSource), "RuntimeNode:") { + t.Fatal("default embed file does not configure runtime.node") + } + if !strings.Contains(string(defaultSource), "RuntimeAssets:") { + t.Fatal("default embed file does not configure retained runtime assets") + } + if !strings.Contains(string(defaultSource), "localEmbeddedCopilotCLILinuxMusl") { + t.Fatal("default embed file does not include the Linux musl CLI") + } + if !strings.Contains(string(defaultSource), "localEmbeddedCopilotRuntimeLibLinuxMusl") { + t.Fatal("default embed file does not include the Linux musl runtime") } if _, err := parser.ParseFile(token.NewFileSet(), "zcopilot_linux_amd64.go", defaultSource, parser.AllErrors); err != nil { t.Fatalf("default generated source is invalid: %v", err) diff --git a/go/github_token_provider.go b/go/github_token_provider.go new file mode 100644 index 0000000000..8f1233b7cd --- /dev/null +++ b/go/github_token_provider.go @@ -0,0 +1,84 @@ +package copilot + +import ( + "fmt" + + "github.com/github/copilot-sdk/go/rpc" +) + +// GitHubTokenRequestReason describes why the runtime needs a GitHub token. +// +// Experimental: GitHubTokenRequestReason may change or be removed. +type GitHubTokenRequestReason = rpc.GitHubTokenAcquireReason + +const ( + // GitHubTokenRequestReasonInitial indicates the session needs its initial token. + GitHubTokenRequestReasonInitial = rpc.GitHubTokenAcquireReasonInitial + // GitHubTokenRequestReasonRefresh indicates the session needs a refreshed token. + GitHubTokenRequestReasonRefresh = rpc.GitHubTokenAcquireReasonRefresh +) + +// GitHubTokenProviderArgs contains the context for a GitHub token request. +// +// Experimental: GitHubTokenProviderArgs may change or be removed. +type GitHubTokenProviderArgs struct { + // Host is the effective GitHub host for which a token is needed. + Host string + // SessionID identifies the session receiving the token. It is nil before a + // cloud session has been assigned an ID. + SessionID *string + // Reason indicates whether this is the initial token or a refresh. + Reason GitHubTokenRequestReason +} + +// GitHubToken contains a GitHub access token returned by a provider. +// +// Experimental: GitHubToken may change or be removed. +type GitHubToken struct { + // AccessToken is the GitHub access token. + AccessToken string + // TokenType is the OAuth token type. The runtime defaults it to "bearer". + TokenType *string + // ExpiresIn is the required positive number of seconds remaining when the + // callback completes. Production GitHub tokens typically last eight hours. + ExpiresIn int64 +} + +// String returns a redacted description that never includes the access token. +func (t GitHubToken) String() string { + tokenType := "" + if t.TokenType != nil { + tokenType = *t.TokenType + } + return fmt.Sprintf("GitHubToken{TokenType:%q, ExpiresIn:%d, AccessToken:}", tokenType, t.ExpiresIn) +} + +// GoString returns a redacted Go-syntax description that never includes the access token. +func (t GitHubToken) GoString() string { + return t.String() +} + +// GitHubTokenProviderResult is the result of a GitHub token request. +// +// Experimental: GitHubTokenProviderResult may change or be removed. +type GitHubTokenProviderResult struct { + Cancelled bool + Token *GitHubToken +} + +// GitHubTokenResult returns a successful token-provider result. +func GitHubTokenResult(token *GitHubToken) *GitHubTokenProviderResult { + return &GitHubTokenProviderResult{Token: token} +} + +// GitHubTokenCancelled returns a result indicating that token acquisition was cancelled. +func GitHubTokenCancelled() *GitHubTokenProviderResult { + return &GitHubTokenProviderResult{Cancelled: true} +} + +// GitHubTokenProvider acquires session-scoped GitHub tokens on demand. Initial +// cancellation, errors, and invalid token responses reject session creation or +// resume instead of falling back to ambient authentication. +// +// Experimental: GitHubTokenProvider may change or be removed. +type GitHubTokenProvider func(args GitHubTokenProviderArgs) (*GitHubTokenProviderResult, error) diff --git a/go/github_token_provider_test.go b/go/github_token_provider_test.go new file mode 100644 index 0000000000..f00837379f --- /dev/null +++ b/go/github_token_provider_test.go @@ -0,0 +1,344 @@ +package copilot + +import ( + "encoding/json" + "errors" + "fmt" + "strings" + "sync" + "testing" + "time" + + "github.com/github/copilot-sdk/go/internal/jsonrpc2" + "github.com/github/copilot-sdk/go/rpc" +) + +func TestGitHubTokenProviderConfigValidation(t *testing.T) { + provider := func(GitHubTokenProviderArgs) (*GitHubTokenProviderResult, error) { + return GitHubTokenCancelled(), nil + } + + if _, err := NewClient(nil).CreateSession(t.Context(), &SessionConfig{ + GitHubToken: "static", + GitHubTokenProvider: provider, + }); err == nil || !strings.Contains(err.Error(), "cannot be used together") { + t.Fatalf("CreateSession error = %v", err) + } + if _, err := NewClient(nil).ResumeSession(t.Context(), "session", &ResumeSessionConfig{ + GitHubToken: "static", + GitHubTokenProvider: provider, + }); err == nil || !strings.Contains(err.Error(), "cannot be used together") { + t.Fatalf("ResumeSession error = %v", err) + } +} + +func TestGitHubTokenProviderCreateRequestAndCallback(t *testing.T) { + rpcClient, server, _ := newRuntimeShutdownRpcPair(t) + t.Cleanup(server.Stop) + client := &Client{ + client: rpcClient, + RPC: rpc.NewServerRPC(rpcClient), + sessions: make(map[string]*Session), + } + client.setupNotificationHandler() + + var createParams json.RawMessage + 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 + }) + server.SetRequestHandler("session.destroy", func(json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { + return []byte(`{}`), nil + }) + + var gotArgs GitHubTokenProviderArgs + session, err := client.CreateSession(t.Context(), &SessionConfig{ + GitHubTokenProvider: func(args GitHubTokenProviderArgs) (*GitHubTokenProviderResult, error) { + gotArgs = args + return GitHubTokenResult(&GitHubToken{ + AccessToken: "secret-token", + TokenType: String("bearer"), + ExpiresIn: 8 * 60 * 60, + }), nil + }, + }) + if err != nil { + t.Fatalf("CreateSession failed: %v", err) + } + + var wire struct { + RegistrationID string `json:"gitHubTokenProviderRegistrationId"` + GitHubToken string `json:"gitHubToken"` + } + if err := json.Unmarshal(createParams, &wire); err != nil { + t.Fatal(err) + } + if wire.RegistrationID == "" { + t.Fatal("gitHubTokenProviderRegistrationId was not serialized") + } + if wire.GitHubToken != "" { + t.Fatal("static gitHubToken should not be serialized") + } + + sessionID := session.SessionID + raw, rpcErr := server.Request(t.Context(), "gitHubToken.getToken", &rpc.GitHubTokenAcquireRequest{ + RegistrationID: wire.RegistrationID, + Host: "github.example.com", + SessionID: &sessionID, + Reason: rpc.GitHubTokenAcquireReasonRefresh, + }) + if rpcErr != nil { + t.Fatalf("getToken failed: %v", rpcErr) + } + var tokenResult struct { + Kind string `json:"kind"` + AccessToken string `json:"accessToken"` + ExpiresIn int64 `json:"expiresIn"` + } + if err := json.Unmarshal(raw, &tokenResult); err != nil { + t.Fatal(err) + } + if tokenResult.Kind != "token" || tokenResult.AccessToken != "secret-token" || tokenResult.ExpiresIn != 8*60*60 { + t.Fatalf("unexpected token result: %+v", tokenResult) + } + if gotArgs.Host != "github.example.com" || gotArgs.SessionID == nil || + *gotArgs.SessionID != sessionID || gotArgs.Reason != GitHubTokenRequestReasonRefresh { + t.Fatalf("unexpected callback args: %+v", gotArgs) + } + + if err := session.Disconnect(); err != nil { + t.Fatal(err) + } + if len(client.gitHubTokenProviders) != 0 { + t.Fatal("provider registration was not removed on disconnect") + } + if _, rpcErr := server.Request(t.Context(), "gitHubToken.getToken", &rpc.GitHubTokenAcquireRequest{ + RegistrationID: wire.RegistrationID, + Host: "github.com", + Reason: rpc.GitHubTokenAcquireReasonInitial, + }); rpcErr == nil { + t.Fatal("unknown registration ID should return a handler error") + } + + var resumeParams json.RawMessage + server.SetRequestHandler("session.resume", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { + resumeParams = append(json.RawMessage(nil), params...) + return []byte(`{"sessionId":"resumed-session","workspacePath":"/workspace"}`), nil + }) + resumed, err := client.ResumeSession(t.Context(), "resumed-session", &ResumeSessionConfig{ + GitHubTokenProvider: func(GitHubTokenProviderArgs) (*GitHubTokenProviderResult, error) { + return GitHubTokenCancelled(), nil + }, + }) + if err != nil { + t.Fatal(err) + } + if err := json.Unmarshal(resumeParams, &wire); err != nil { + t.Fatal(err) + } + if wire.RegistrationID == "" { + t.Fatal("resume did not serialize gitHubTokenProviderRegistrationId") + } + if err := resumed.Disconnect(); err != nil { + t.Fatal(err) + } +} + +func TestGitHubTokenProviderResultsErrorsAndRollback(t *testing.T) { + client := &Client{} + adapter := &gitHubTokenAdapter{client: client} + + cancelID := client.registerGitHubTokenProvider(func(GitHubTokenProviderArgs) (*GitHubTokenProviderResult, error) { + return GitHubTokenCancelled(), nil + }) + result, err := adapter.GetToken(&rpc.GitHubTokenAcquireRequest{RegistrationID: cancelID}) + if err != nil { + t.Fatal(err) + } + if _, ok := result.(*rpc.GitHubTokenAcquireResultCancelled); !ok { + t.Fatalf("result type = %T", result) + } + + sentinel := errors.New("provider failed") + errorID := client.registerGitHubTokenProvider(func(GitHubTokenProviderArgs) (*GitHubTokenProviderResult, error) { + return nil, sentinel + }) + if _, err := adapter.GetToken(&rpc.GitHubTokenAcquireRequest{RegistrationID: errorID}); !errors.Is(err, sentinel) { + t.Fatalf("provider error = %v", err) + } + + rpcClient, server, _ := newRuntimeShutdownRpcPair(t) + t.Cleanup(server.Stop) + rollbackClient := &Client{ + client: rpcClient, + RPC: rpc.NewServerRPC(rpcClient), + sessions: make(map[string]*Session), + } + server.SetRequestHandler("session.create", func(json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { + return nil, &jsonrpc2.Error{Code: -32000, Message: "create failed"} + }) + if _, err := rollbackClient.CreateSession(t.Context(), &SessionConfig{ + GitHubTokenProvider: func(GitHubTokenProviderArgs) (*GitHubTokenProviderResult, error) { + return GitHubTokenCancelled(), nil + }, + }); err == nil { + t.Fatal("expected create failure") + } + if len(rollbackClient.gitHubTokenProviders) != 0 { + t.Fatal("provider registration was not rolled back") + } +} + +func TestGitHubTokenStringRedactsAccessToken(t *testing.T) { + token := GitHubToken{AccessToken: "secret-token", ExpiresIn: 28_800} + + if got := fmt.Sprintf("%v %#v", token, token); strings.Contains(got, token.AccessToken) { + t.Fatalf("GitHubToken formatting exposed the access token: %s", got) + } +} + +func TestGitHubTokenProviderCleanupOnDisconnectError(t *testing.T) { + rpcClient, server, _ := newRuntimeShutdownRpcPair(t) + t.Cleanup(server.Stop) + server.SetRequestHandler("session.destroy", func(json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { + return nil, &jsonrpc2.Error{Code: -32000, Message: "destroy failed"} + }) + client := &Client{} + registrationID := client.registerGitHubTokenProvider(func(GitHubTokenProviderArgs) (*GitHubTokenProviderResult, error) { + return GitHubTokenCancelled(), nil + }) + session := newSession("cleanup-session", rpcClient, "", false) + session.setGitHubTokenProviderRegistrationRelease(func() { + client.unregisterGitHubTokenProvider(registrationID) + }) + + if err := session.Disconnect(); err == nil || !strings.Contains(err.Error(), "destroy failed") { + t.Fatalf("Disconnect error = %v", err) + } + if len(client.gitHubTokenProviders) != 0 { + t.Fatal("provider registration was not removed after disconnect failed") + } +} + +func TestGitHubTokenProviderReleaseBeforeOwnershipTransfer(t *testing.T) { + client := &Client{} + registrationID := client.registerGitHubTokenProvider(func(GitHubTokenProviderArgs) (*GitHubTokenProviderResult, error) { + return GitHubTokenCancelled(), nil + }) + session := &Session{} + + session.releaseGitHubTokenProviderRegistration() + session.setGitHubTokenProviderRegistrationRelease(func() { + client.unregisterGitHubTokenProvider(registrationID) + }) + + if len(client.gitHubTokenProviders) != 0 { + t.Fatal("provider registration was not removed after a pending session had already been retired") + } +} + +func TestGitHubTokenProviderCleanupOnDelete(t *testing.T) { + rpcClient, server, _ := newRuntimeShutdownRpcPair(t) + t.Cleanup(server.Stop) + server.SetRequestHandler("session.delete", func(json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { + return []byte(`{"success":true}`), nil + }) + client := &Client{ + client: rpcClient, + sessions: make(map[string]*Session), + state: stateConnected, + } + registrationID := client.registerGitHubTokenProvider(func(GitHubTokenProviderArgs) (*GitHubTokenProviderResult, error) { + return GitHubTokenCancelled(), nil + }) + session := newSession("delete-session", rpcClient, "", false) + session.setGitHubTokenProviderRegistrationRelease(func() { + client.unregisterGitHubTokenProvider(registrationID) + }) + client.sessions[session.SessionID] = session + + if err := client.DeleteSession(t.Context(), session.SessionID); err != nil { + t.Fatal(err) + } + if len(client.gitHubTokenProviders) != 0 { + t.Fatal("provider registration was not removed after session deletion") + } +} + +func TestSessionOperationsSerializeBySessionID(t *testing.T) { + client := &Client{} + unlockFirst := client.lockSessionOperation("same-session") + sameSessionAcquired := make(chan struct{}) + go func() { + unlock := client.lockSessionOperation("same-session") + close(sameSessionAcquired) + unlock() + }() + + select { + case <-sameSessionAcquired: + t.Fatal("same-session operation was not serialized") + case <-time.After(25 * time.Millisecond): + } + + otherSessionAcquired := make(chan struct{}) + go func() { + unlock := client.lockSessionOperation("other-session") + close(otherSessionAcquired) + unlock() + }() + select { + case <-otherSessionAcquired: + case <-time.After(time.Second): + t.Fatal("different-session operation was unnecessarily blocked") + } + + unlockFirst() + select { + case <-sameSessionAcquired: + case <-time.After(time.Second): + t.Fatal("same-session operation did not proceed after release") + } +} + +func TestGitHubTokenProvidersClearedOnConnectionClose(t *testing.T) { + client := &Client{state: stateConnected} + client.registerGitHubTokenProvider(func(GitHubTokenProviderArgs) (*GitHubTokenProviderResult, error) { + return GitHubTokenCancelled(), nil + }) + + client.handleConnectionClose() + + if len(client.gitHubTokenProviders) != 0 { + t.Fatal("provider registrations were not cleared after connection closure") + } +} + +func TestGitHubTokenProviderConcurrentRegistrationsAreIsolated(t *testing.T) { + client := &Client{} + adapter := &gitHubTokenAdapter{client: client} + idA := client.registerGitHubTokenProvider(func(GitHubTokenProviderArgs) (*GitHubTokenProviderResult, error) { + return GitHubTokenResult(&GitHubToken{AccessToken: "a", ExpiresIn: 1}), nil + }) + idB := client.registerGitHubTokenProvider(func(GitHubTokenProviderArgs) (*GitHubTokenProviderResult, error) { + return GitHubTokenResult(&GitHubToken{AccessToken: "b", ExpiresIn: 1}), nil + }) + + var wg sync.WaitGroup + for id, want := range map[string]string{idA: "a", idB: "b"} { + wg.Add(1) + go func() { + defer wg.Done() + result, err := adapter.GetToken(&rpc.GitHubTokenAcquireRequest{RegistrationID: id}) + if err != nil { + t.Error(err) + return + } + if got := result.(*rpc.GitHubTokenAcquireResultToken).AccessToken; got != want { + t.Errorf("token = %q, want %q", got, want) + } + }() + } + wg.Wait() +} diff --git a/go/inprocess_disabled.go b/go/inprocess_disabled.go index b86ed5ca36..c100e3db6a 100644 --- a/go/inprocess_disabled.go +++ b/go/inprocess_disabled.go @@ -6,6 +6,8 @@ import "errors" const inProcessAvailable = false -func createInProcessHost(string, inProcessHostConfig) (inProcessHost, error) { - return nil, errors.New("in-process transport unavailable") +var errInProcessUnavailable = errors.New("in-process transport unavailable") + +func createInProcessHost(string, string, inProcessHostConfig) (inProcessHost, error) { + return nil, errInProcessUnavailable } diff --git a/go/inprocess_enabled.go b/go/inprocess_enabled.go index c20013d8ab..2c30d5f863 100644 --- a/go/inprocess_enabled.go +++ b/go/inprocess_enabled.go @@ -6,6 +6,6 @@ import "github.com/github/copilot-sdk/go/internal/ffihost" const inProcessAvailable = true -func createInProcessHost(runtimePath string, config inProcessHostConfig) (inProcessHost, error) { - return ffihost.Create(runtimePath, config.Environment, config.Args) +func createInProcessHost(runtimePath, cliEntrypoint string, config inProcessHostConfig) (inProcessHost, error) { + return ffihost.Create(runtimePath, cliEntrypoint, config.Environment, config.Args) } diff --git a/go/internal/e2e/abort_e2e_test.go b/go/internal/e2e/abort_e2e_test.go index 0953456888..569518bca7 100644 --- a/go/internal/e2e/abort_e2e_test.go +++ b/go/internal/e2e/abort_e2e_test.go @@ -32,6 +32,7 @@ func TestAbortE2E(t *testing.T) { var mu sync.Mutex var events []copilot.SessionEvent firstDelta := make(chan *copilot.AssistantMessageDeltaData, 1) + sessionIdle := make(chan struct{}, 1) session.On(func(event copilot.SessionEvent) { mu.Lock() @@ -43,6 +44,12 @@ func TestAbortE2E(t *testing.T) { default: } } + if _, ok := event.Data.(*copilot.SessionIdleData); ok { + select { + case sessionIdle <- struct{}{}: + default: + } + } }) // Fire-and-forget — we'll abort before it finishes @@ -67,6 +74,11 @@ func TestAbortE2E(t *testing.T) { if err := session.Abort(t.Context()); err != nil { t.Fatalf("Abort failed: %v", err) } + select { + case <-sessionIdle: + case <-time.After(60 * time.Second): + t.Fatal("Timed out waiting for session to become idle after abort") + } mu.Lock() snapshot := make([]copilot.SessionEvent, len(events)) @@ -85,33 +97,15 @@ func TestAbortE2E(t *testing.T) { t.Error("Expected at least one assistant.message_delta event before abort") } - // Session should be usable after abort. Wait for the specific recovery - // message rather than racing against a late idle from the aborted turn. - recoveryReceived := make(chan *copilot.AssistantMessageData, 1) - session.On(func(event copilot.SessionEvent) { - if d, ok := event.Data.(*copilot.AssistantMessageData); ok { - if strings.Contains(strings.ToLower(d.Content), "abort_recovery_ok") { - select { - case recoveryReceived <- d: - default: - } - } - } + // Session should be usable after abort. + msg, err := session.SendAndWait(t.Context(), copilot.MessageOptions{ + Prompt: "Say 'abort_recovery_ok'.", }) - - go func() { - _, _ = session.Send(t.Context(), copilot.MessageOptions{ - Prompt: "Say 'abort_recovery_ok'.", - }) - }() - - select { - case msg := <-recoveryReceived: - if !strings.Contains(strings.ToLower(msg.Content), "abort_recovery_ok") { - t.Errorf("Expected recovery message to contain 'abort_recovery_ok', got %q", msg.Content) - } - case <-time.After(60 * time.Second): - t.Fatal("Timed out waiting for recovery message after abort") + if err != nil { + t.Fatalf("Recovery SendAndWait failed after abort: %v", err) + } + if content := assistantContent(t, msg); !strings.Contains(strings.ToLower(content), "abort_recovery_ok") { + t.Errorf("Expected recovery message to contain 'abort_recovery_ok', got %q", content) } }) @@ -144,6 +138,16 @@ func TestAbortE2E(t *testing.T) { } t.Cleanup(func() { _ = session.Disconnect() }) + sessionIdle := make(chan struct{}, 1) + session.On(func(event copilot.SessionEvent) { + if _, ok := event.Data.(*copilot.SessionIdleData); ok { + select { + case sessionIdle <- struct{}{}: + default: + } + } + }) + // Fire-and-forget go func() { _, _ = session.Send(t.Context(), copilot.MessageOptions{ @@ -172,33 +176,21 @@ func TestAbortE2E(t *testing.T) { case releaseTool <- "RELEASED_AFTER_ABORT": default: } - - // Session should be usable after abort - recoveryReceived := make(chan *copilot.AssistantMessageData, 1) - session.On(func(event copilot.SessionEvent) { - if d, ok := event.Data.(*copilot.AssistantMessageData); ok { - if strings.Contains(d.Content, "tool_abort_recovery_ok") { - select { - case recoveryReceived <- d: - default: - } - } - } - }) - - go func() { - _, _ = session.Send(t.Context(), copilot.MessageOptions{ - Prompt: "Say 'tool_abort_recovery_ok'.", - }) - }() - select { - case msg := <-recoveryReceived: - if !strings.Contains(msg.Content, "tool_abort_recovery_ok") { - t.Errorf("Expected recovery message to contain 'tool_abort_recovery_ok', got %q", msg.Content) - } + case <-sessionIdle: case <-time.After(60 * time.Second): - t.Fatal("Timed out waiting for recovery message after abort") + t.Fatal("Timed out waiting for session to become idle after tool abort") + } + + // Session should be usable after abort. + msg, err := session.SendAndWait(t.Context(), copilot.MessageOptions{ + Prompt: "Say 'tool_abort_recovery_ok'.", + }) + if err != nil { + t.Fatalf("Recovery SendAndWait failed after tool abort: %v", err) + } + if content := assistantContent(t, msg); !strings.Contains(content, "tool_abort_recovery_ok") { + t.Errorf("Expected recovery message to contain 'tool_abort_recovery_ok', got %q", content) } }) } diff --git a/go/internal/e2e/client_options_e2e_test.go b/go/internal/e2e/client_options_e2e_test.go index 86332eb6f1..9c7b77c984 100644 --- a/go/internal/e2e/client_options_e2e_test.go +++ b/go/internal/e2e/client_options_e2e_test.go @@ -329,7 +329,7 @@ func TestClientOptionsE2E(t *testing.T) { session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ SessionID: sessionID, ClientName: "go-sdk-e2e-client", - Model: "claude-sonnet-4.5", + Model: "claude-sonnet-5", ReasoningEffort: "low", ReasoningSummary: copilot.ReasoningSummaryNone, ContextTier: copilot.ContextTierLongContext, @@ -388,7 +388,7 @@ func TestClientOptionsE2E(t *testing.T) { expectedValues := map[string]any{ "sessionId": sessionID, "clientName": "go-sdk-e2e-client", - "model": "claude-sonnet-4.5", + "model": "claude-sonnet-5", "reasoningEffort": "low", "reasoningSummary": "none", "contextTier": "long_context", diff --git a/go/internal/e2e/copilot_request_helpers_test.go b/go/internal/e2e/copilot_request_helpers_test.go index 81d14f4d94..cd1ac63cf7 100644 --- a/go/internal/e2e/copilot_request_helpers_test.go +++ b/go/internal/e2e/copilot_request_helpers_test.go @@ -47,8 +47,8 @@ func sseFrame(eventType string, data map[string]any) string { func modelCatalogJSON(supportedEndpoints []string) string { model := map[string]any{ - "id": "claude-sonnet-4.5", - "name": "Claude Sonnet 4.5", + "id": "claude-sonnet-5", + "name": "Claude Sonnet 5", "object": "model", "vendor": "Anthropic", "version": "1", @@ -56,7 +56,7 @@ func modelCatalogJSON(supportedEndpoints []string) string { "model_picker_enabled": true, "capabilities": map[string]any{ "type": "chat", - "family": "claude-sonnet-4.5", + "family": "claude-sonnet-5", "tokenizer": "o200k_base", "limits": map[string]any{ "max_context_window_tokens": 200000, @@ -141,7 +141,7 @@ func buildAnthropicMessageSSEBody(text string) string { "type": "message_start", "message": map[string]any{ "id": "msg_stub_1", "type": "message", "role": "assistant", - "model": "claude-sonnet-4.5", "content": []any{}, + "model": "claude-sonnet-5", "content": []any{}, "stop_reason": nil, "stop_sequence": nil, "usage": map[string]any{"input_tokens": 5, "output_tokens": 1}, }, @@ -188,7 +188,7 @@ func buildInferenceResponse(url string, bodyText string) *http.Response { base := func() map[string]any { return map[string]any{ "id": "chatcmpl-stub-1", "object": "chat.completion.chunk", - "created": 1, "model": "claude-sonnet-4.5", + "created": 1, "model": "claude-sonnet-5", } } c1 := base() @@ -215,7 +215,7 @@ func buildInferenceResponse(url string, bodyText string) *http.Response { "id": "msg_stub_1", "type": "message", "role": "assistant", - "model": "claude-sonnet-4.5", + "model": "claude-sonnet-5", "content": []any{map[string]any{"type": "text", "text": syntheticResponseText}}, "stop_reason": "end_turn", "stop_sequence": nil, @@ -225,7 +225,7 @@ func buildInferenceResponse(url string, bodyText string) *http.Response { } raw, _ := json.Marshal(map[string]any{ - "id": "chatcmpl-stub-1", "object": "chat.completion", "created": 1, "model": "claude-sonnet-4.5", + "id": "chatcmpl-stub-1", "object": "chat.completion", "created": 1, "model": "claude-sonnet-5", "choices": []any{map[string]any{"index": 0, "message": map[string]any{"role": "assistant", "content": syntheticResponseText}, "finish_reason": "stop"}}, "usage": map[string]any{"prompt_tokens": 5, "completion_tokens": 7, "total_tokens": 12}, }) diff --git a/go/internal/e2e/copilot_request_session_id_e2e_test.go b/go/internal/e2e/copilot_request_session_id_e2e_test.go index f7673bd457..46a62db1ab 100644 --- a/go/internal/e2e/copilot_request_session_id_e2e_test.go +++ b/go/internal/e2e/copilot_request_session_id_e2e_test.go @@ -139,14 +139,14 @@ func TestCopilotRequestSessionID(t *testing.T) { before := len(transport.inferenceRecords()) session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ OnPermissionRequest: copilot.PermissionHandler.ApproveAll, - Model: "claude-sonnet-4.5", + Model: "claude-sonnet-5", Provider: &copilot.ProviderConfig{ Type: "openai", WireAPI: "responses", BaseURL: "https://byok.invalid/v1", APIKey: "byok-secret", - ModelID: "claude-sonnet-4.5", - WireModel: "claude-sonnet-4.5", + ModelID: "claude-sonnet-5", + WireModel: "claude-sonnet-5", }, }) if err != nil { diff --git a/go/internal/e2e/event_fidelity_e2e_test.go b/go/internal/e2e/event_fidelity_e2e_test.go index e7cc4bfb37..8606da22d9 100644 --- a/go/internal/e2e/event_fidelity_e2e_test.go +++ b/go/internal/e2e/event_fidelity_e2e_test.go @@ -333,8 +333,8 @@ func TestEventFidelityE2E(t *testing.T) { } idleIdx := lastEventFidelityTypeIndex(types, copilot.SessionEventTypeSessionIdle) - if idleIdx != len(types)-1 { - t.Fatalf("Expected session.idle to be last event; idleIdx=%d len=%d types=%v", idleIdx, len(types), types) + if idleIdx < 0 || assistantIdx >= idleIdx { + t.Fatalf("Expected last assistant.message before session.idle; assistantIdx=%d idleIdx=%d types=%v", assistantIdx, idleIdx, types) } }) diff --git a/go/internal/e2e/github_telemetry_e2e_test.go b/go/internal/e2e/github_telemetry_e2e_test.go index aa26ba31f2..6e1a36383f 100644 --- a/go/internal/e2e/github_telemetry_e2e_test.go +++ b/go/internal/e2e/github_telemetry_e2e_test.go @@ -12,6 +12,11 @@ import ( func TestGitHubTelemetryE2E(t *testing.T) { t.Run("should forward github telemetry for a live session", func(t *testing.T) { + // TODO(cli-1.0.81-2): CLI 1.0.81-2 does not forward GitHub telemetry notifications + // over the in-process (FFI) host, mirroring the existing telemetry-configuration + // limitation. Still covered by the default (stdio) transport. + testharness.SkipIfInProcess(t, "GitHub telemetry forwarding is not honored in-process") + ctx := testharness.NewTestContext(t) ctx.ConfigureForTest(t) diff --git a/go/internal/e2e/inprocess_ffi_e2e_test.go b/go/internal/e2e/inprocess_ffi_e2e_test.go index 6923384a28..7f7dcc3f20 100644 --- a/go/internal/e2e/inprocess_ffi_e2e_test.go +++ b/go/internal/e2e/inprocess_ffi_e2e_test.go @@ -8,8 +8,8 @@ import ( ) // TestInProcessFfiE2E is a smoke test for the in-process (FFI) transport. It -// starts a client that loads the native runtime cdylib next to the resolved CLI -// entrypoint, lets the native host spawn the worker, performs a purely local +// starts a client that loads the native runtime cdylib directly, lets the native +// host construct the server, performs a purely local // "ping" round-trip through the runtime, and stops cleanly. No auth or replay // proxy is involved, so it needs no snapshot. // diff --git a/go/internal/e2e/mcp_and_agents_e2e_test.go b/go/internal/e2e/mcp_and_agents_e2e_test.go index 71a152ecaf..0cc57810ed 100644 --- a/go/internal/e2e/mcp_and_agents_e2e_test.go +++ b/go/internal/e2e/mcp_and_agents_e2e_test.go @@ -88,11 +88,6 @@ func TestMCPServersE2E(t *testing.T) { } sessionID := session1.SessionID - _, err = session1.SendAndWait(t.Context(), copilot.MessageOptions{Prompt: "What is 1+1?"}) - if err != nil { - t.Fatalf("Failed to send message: %v", err) - } - // Resume with MCP servers mcpServers := testMCPServers(t, "test-server") diff --git a/go/internal/e2e/pending_work_resume_e2e_test.go b/go/internal/e2e/pending_work_resume_e2e_test.go index 00419aec56..58de8644ac 100644 --- a/go/internal/e2e/pending_work_resume_e2e_test.go +++ b/go/internal/e2e/pending_work_resume_e2e_test.go @@ -468,7 +468,32 @@ func TestPendingWorkResumeE2E(t *testing.T) { } if scenario.disconnectOriginalClient { + lockObserver := ctx.NewClient(func(opts *copilot.ClientOptions) { + stdio := opts.Connection.(copilot.StdioConnection) + opts.Connection = copilot.TCPConnection{Path: stdio.Path} + }) + t.Cleanup(func() { lockObserver.ForceStop() }) + if err := lockObserver.Start(t.Context()); err != nil { + t.Fatalf("Failed to start session lock observer: %v", err) + } + + waitForRPCCondition(t, pendingWorkTimeout, "session lock to be held before disconnect", func() (bool, error) { + result, err := lockObserver.RPC.Sessions.CheckInUse( + t.Context(), + &rpc.SessionsCheckInUseRequest{SessionIDs: []string{sessionID}}, + ) + return err == nil && containsString(result.InUse, sessionID), err + }) + suspendedClient.ForceStop() + + waitForRPCCondition(t, pendingWorkTimeout, "session lock to be released after disconnect", func() (bool, error) { + result, err := lockObserver.RPC.Sessions.CheckInUse( + t.Context(), + &rpc.SessionsCheckInUseRequest{SessionIDs: []string{sessionID}}, + ) + return err == nil && !containsString(result.InUse, sessionID), err + }) } resumedClient := ctx.NewClient(func(opts *copilot.ClientOptions) { diff --git a/go/internal/e2e/rewind_e2e_test.go b/go/internal/e2e/rewind_e2e_test.go new file mode 100644 index 0000000000..701e610775 --- /dev/null +++ b/go/internal/e2e/rewind_e2e_test.go @@ -0,0 +1,189 @@ +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" + rewindFileOriginalContent = "Original rewind content" + rewindFilePreparedContent = "Prepared rewind content" + 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) + if err := os.WriteFile(filePath, []byte(rewindFileOriginalContent), 0o600); err != nil { + t.Fatalf("Failed to create original file: %v", err) + } + session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ + Model: "claude-sonnet-5", + EnableFileChangeTracking: copilot.Bool(true), + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + }) + if err != nil { + t.Fatalf("CreateSession failed: %v", err) + } + defer session.Disconnect() + + ready, err := session.SendAndWait(t.Context(), copilot.MessageOptions{ + Prompt: "Use the edit tool to replace the exact contents of " + rewindFileName + " from " + + rewindFileOriginalContent + " to " + rewindFilePreparedContent + + ". After the tool succeeds, reply with exactly SDK_REWIND_READY.", + }) + if err != nil { + t.Fatalf("SendAndWait readiness turn failed: %v", err) + } + readyData, ok := ready.Data.(*copilot.AssistantMessageData) + if !ok || readyData.Content != "SDK_REWIND_READY" { + t.Fatalf("Expected SDK_REWIND_READY response, got %+v", ready) + } + content, err := os.ReadFile(filePath) + if err != nil { + t.Fatalf("Failed to read prepared file: %v", err) + } + if string(content) != rewindFilePreparedContent { + t.Fatalf("Expected file content %q, got %q", rewindFilePreparedContent, content) + } + + response, err := session.SendAndWait(t.Context(), copilot.MessageOptions{ + Prompt: "Use the edit tool to replace the exact contents of " + rewindFileName + " from " + + rewindFilePreparedContent + " to " + 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 updated 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) != 2 { + t.Fatalf("Expected two rewind points, got %+v", rewindPoints.Points) + } + rewindPoint := rewindPoints.Points[1] + if !rewindPoint.TurnChangedFiles { + t.Fatalf("Expected the edit turn to report changed files, got %+v", rewindPoint) + } + 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]) + content, err = os.ReadFile(filePath) + if err != nil { + t.Fatalf("Failed to read restored file: %v", err) + } + if string(content) != rewindFilePreparedContent { + t.Fatalf("Expected restored file content %q, got %q", rewindFilePreparedContent, content) + } + + 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(30 * time.Second) + for { + result, err := session.RPC.History.ListRewindPoints(t.Context()) + if err != nil { + t.Fatalf("ListRewindPoints failed: %v", err) + } + if result.UnavailableReason == nil && + len(result.Points) == 2 && + result.Points[1].TurnChangedFiles && + result.Points[1].CanRestoreFiles && + result.Points[1].FileCount == 1 { + return result + } + if time.Now().After(deadline) { + t.Fatalf("Timed out waiting for a restorable rewind point: %+v", result) + } + 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_e2e_test.go b/go/internal/e2e/rpc_e2e_test.go index fcf843814e..4380415701 100644 --- a/go/internal/e2e/rpc_e2e_test.go +++ b/go/internal/e2e/rpc_e2e_test.go @@ -128,7 +128,7 @@ func TestSessionRPCE2E(t *testing.T) { session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ OnPermissionRequest: copilot.PermissionHandler.ApproveAll, - Model: "claude-sonnet-4.5", + Model: "claude-sonnet-5", }) if err != nil { t.Fatalf("Failed to create session: %v", err) @@ -150,7 +150,7 @@ func TestSessionRPCE2E(t *testing.T) { session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ OnPermissionRequest: copilot.PermissionHandler.ApproveAll, - Model: "claude-sonnet-4.5", + Model: "claude-sonnet-5", }) if err != nil { t.Fatalf("Failed to create session: %v", err) @@ -194,7 +194,7 @@ func TestSessionRPCE2E(t *testing.T) { session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ OnPermissionRequest: copilot.PermissionHandler.ApproveAll, - Model: "claude-sonnet-4.5", + Model: "claude-sonnet-5", }) if err != nil { t.Fatalf("Failed to create session: %v", err) diff --git a/go/internal/e2e/rpc_server_e2e_test.go b/go/internal/e2e/rpc_server_e2e_test.go index 6ea9ad6851..f1aa5a19c7 100644 --- a/go/internal/e2e/rpc_server_e2e_test.go +++ b/go/internal/e2e/rpc_server_e2e_test.go @@ -64,12 +64,12 @@ func TestRPCServerE2E(t *testing.T) { if strings.TrimSpace(model.Name) == "" { t.Errorf("Model %q has empty Name", model.ID) } - if model.ID == "claude-sonnet-4.5" { + if model.ID == "claude-sonnet-5" { hasClaude = true } } if !hasClaude { - t.Errorf("Expected models list to contain 'claude-sonnet-4.5'") + t.Errorf("Expected models list to contain 'claude-sonnet-5'") } }) @@ -532,6 +532,7 @@ func TestRPCServerE2E(t *testing.T) { t.Run("should report implemented error when connecting unknown remote session", func(t *testing.T) { ctx := testharness.NewTestContext(t) + ctx.ConfigureWithoutSnapshot(t) client := ctx.NewClient() t.Cleanup(func() { client.ForceStop() }) if err := client.Start(t.Context()); err != nil { diff --git a/go/internal/e2e/rpc_server_misc_e2e_test.go b/go/internal/e2e/rpc_server_misc_e2e_test.go index 37ec57e1ba..9607994676 100644 --- a/go/internal/e2e/rpc_server_misc_e2e_test.go +++ b/go/internal/e2e/rpc_server_misc_e2e_test.go @@ -131,7 +131,7 @@ func TestRpcServerMisc(t *testing.T) { login, err := client.RPC.Account.Login(t.Context(), &rpc.AccountLoginRequest{ Host: "https://github.com", - Login: "go-account-user", + Login: rpcPtr("go-account-user"), Token: "go-account-token", }) if err != nil { diff --git a/go/internal/e2e/rpc_session_state_e2e_test.go b/go/internal/e2e/rpc_session_state_e2e_test.go index 4046ab97fe..f4870dbac6 100644 --- a/go/internal/e2e/rpc_session_state_e2e_test.go +++ b/go/internal/e2e/rpc_session_state_e2e_test.go @@ -26,7 +26,7 @@ func TestRPCSessionStateE2E(t *testing.T) { t.Run("should call session rpc model getCurrent", func(t *testing.T) { session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ - Model: "claude-sonnet-4.5", + Model: "claude-sonnet-5", OnPermissionRequest: copilot.PermissionHandler.ApproveAll, }) if err != nil { @@ -37,8 +37,8 @@ func TestRPCSessionStateE2E(t *testing.T) { if err != nil { t.Fatalf("Model.GetCurrent failed: %v", err) } - if result.ModelID == nil || *result.ModelID != "claude-sonnet-4.5" { - t.Fatalf("Expected current model claude-sonnet-4.5, got %+v", result) + if result.ModelID == nil || *result.ModelID != "claude-sonnet-5" { + t.Fatalf("Expected current model claude-sonnet-5, got %+v", result) } }) @@ -58,7 +58,7 @@ func TestRPCSessionStateE2E(t *testing.T) { switchCtx.ConfigureForTest(t) session, err := switchClient.CreateSession(t.Context(), &copilot.SessionConfig{ - Model: "claude-sonnet-4.5", + Model: "claude-sonnet-5", OnPermissionRequest: copilot.PermissionHandler.ApproveAll, }) if err != nil { @@ -485,7 +485,7 @@ func TestRPCSessionStateE2E(t *testing.T) { branch := "rpc-context-" + randomHex(t) session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ - Model: "claude-sonnet-4.5", + Model: "claude-sonnet-5", WorkingDirectory: firstDirectory, OnPermissionRequest: copilot.PermissionHandler.ApproveAll, }) @@ -498,7 +498,7 @@ func TestRPCSessionStateE2E(t *testing.T) { t.Fatalf("Metadata.Snapshot failed: %v", err) } if initial.SessionID != session.SessionID || initial.CurrentMode != rpc.MetadataSnapshotCurrentModeInteractive || - initial.SelectedModel == nil || *initial.SelectedModel != "claude-sonnet-4.5" || + initial.SelectedModel == nil || *initial.SelectedModel != "claude-sonnet-5" || initial.IsRemote || initial.AlreadyInUse || initial.StartTime.IsZero() || initial.ModifiedTime.IsZero() || initial.Workspace == nil || initial.WorkspacePath == nil || *initial.WorkspacePath == "" { t.Fatalf("Unexpected initial metadata snapshot: %+v", initial) @@ -566,6 +566,10 @@ func TestRPCSessionStateE2E(t *testing.T) { }) t.Run("should update options and initialize session services", func(t *testing.T) { + // TODO(cli-1.0.81-2): under CLI 1.0.81-2 this subtest issues model traffic before the + // replaying proxy is configured, so the proxy rejects it with "not yet initialized". + // Re-enable once the runtime restores the previous option-update ordering. + t.Skip("blocked on CLI 1.0.81-2 session option/service initialization ordering") initialDirectory := createUniqueRPCWorkDirectory(t, ctx, "rpc-session-state-initial") optionsDirectory := createUniqueRPCWorkDirectory(t, ctx, "rpc-session-state-options") featureName := "rpc-session-state-" + randomHex(t) @@ -626,7 +630,7 @@ func TestRPCSessionStateE2E(t *testing.T) { t.Run("should set reasoning effort and auto name", func(t *testing.T) { session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ - Model: "claude-sonnet-4.5", + Model: "claude-sonnet-5", OnPermissionRequest: copilot.PermissionHandler.ApproveAll, }) if err != nil { @@ -644,9 +648,9 @@ func TestRPCSessionStateE2E(t *testing.T) { if err != nil { t.Fatalf("Model.GetCurrent failed: %v", err) } - if current.ModelID == nil || *current.ModelID != "claude-sonnet-4.5" || + if current.ModelID == nil || *current.ModelID != "claude-sonnet-5" || current.ReasoningEffort == nil || *current.ReasoningEffort != "high" { - t.Fatalf("Expected current model claude-sonnet-4.5/high, got %+v", current) + t.Fatalf("Expected current model claude-sonnet-5/high, got %+v", current) } autoName := "Auto Session " + randomHex(t) @@ -756,7 +760,7 @@ func TestRPCSessionStateE2E(t *testing.T) { t.Fatal("Expected fresh session to be idle") } - model := "claude-sonnet-4.5" + model := "claude-sonnet-5" contextInfo, err := session.RPC.Metadata.ContextInfo(t.Context(), &rpc.MetadataContextInfoRequest{ PromptTokenLimit: 128000, OutputTokenLimit: 4096, diff --git a/go/internal/e2e/rpc_session_state_extras_e2e_test.go b/go/internal/e2e/rpc_session_state_extras_e2e_test.go index 1e33e8a8bc..c74ed45cc8 100644 --- a/go/internal/e2e/rpc_session_state_extras_e2e_test.go +++ b/go/internal/e2e/rpc_session_state_extras_e2e_test.go @@ -22,7 +22,7 @@ func TestRpcSessionStateExtras(t *testing.T) { authClient := newAuthenticatedClient(ctx, token) defer authClient.ForceStop() - session := createPortedSession(t, authClient, &copilot.SessionConfig{Model: "claude-sonnet-4.5"}) + session := createPortedSession(t, authClient, &copilot.SessionConfig{Model: "claude-sonnet-5"}) defer session.Disconnect() result, err := session.RPC.Model.List(t.Context()) @@ -38,13 +38,13 @@ func TestRpcSessionStateExtras(t *testing.T) { found := false for _, model := range result.List { data, err := json.Marshal(model) - if err == nil && strings.Contains(string(data), "claude-sonnet-4.5") { + if err == nil && strings.Contains(string(data), "claude-sonnet-5") { found = true break } } if !found { - t.Fatalf("Expected model list to include claude-sonnet-4.5, got %+v", result.List) + t.Fatalf("Expected model list to include claude-sonnet-5, got %+v", result.List) } }) @@ -70,45 +70,45 @@ func TestRpcSessionStateExtras(t *testing.T) { session := createPortedSession(t, client, nil) defer session.Disconnect() defer func() { - _, _ = session.RPC.Permissions.SetAllowAll(t.Context(), &rpc.PermissionsSetAllowAllRequest{Enabled: copilot.Bool(false)}) + _, _ = session.RPC.Permissions.SetMode(t.Context(), &rpc.PermissionsSetModeRequest{Mode: rpc.PermissionModeManual}) }() - initial, err := session.RPC.Permissions.GetAllowAll(t.Context()) + initial, err := session.RPC.Permissions.GetMode(t.Context()) if err != nil { - t.Fatalf("Permissions.GetAllowAll initial failed: %v", err) + t.Fatalf("Permissions.GetMode initial failed: %v", err) } - if initial.Enabled { - t.Fatal("Allow-all should be disabled on a fresh session") + if initial.Mode != rpc.PermissionModeManual { + t.Fatalf("Expected manual mode on a fresh session, got %q", initial.Mode) } - enable, err := session.RPC.Permissions.SetAllowAll(t.Context(), &rpc.PermissionsSetAllowAllRequest{Enabled: copilot.Bool(true)}) + enable, err := session.RPC.Permissions.SetMode(t.Context(), &rpc.PermissionsSetModeRequest{Mode: rpc.PermissionModeAllowAll}) if err != nil { - t.Fatalf("Permissions.SetAllowAll(true) failed: %v", err) + t.Fatalf("Permissions.SetMode(allow-all) failed: %v", err) } - if !enable.Success || !enable.Enabled { - t.Fatalf("Expected successful enable, got %+v", enable) + if !enable.Success || enable.Mode != rpc.PermissionModeAllowAll { + t.Fatalf("Expected successful allow-all mode change, got %+v", enable) } - afterEnable, err := session.RPC.Permissions.GetAllowAll(t.Context()) + afterEnable, err := session.RPC.Permissions.GetMode(t.Context()) if err != nil { - t.Fatalf("Permissions.GetAllowAll after enable failed: %v", err) + t.Fatalf("Permissions.GetMode after allow-all failed: %v", err) } - if !afterEnable.Enabled { - t.Fatal("Expected allow-all to be enabled") + if afterEnable.Mode != rpc.PermissionModeAllowAll { + t.Fatalf("Expected allow-all mode, got %q", afterEnable.Mode) } - disable, err := session.RPC.Permissions.SetAllowAll(t.Context(), &rpc.PermissionsSetAllowAllRequest{Enabled: copilot.Bool(false)}) + disable, err := session.RPC.Permissions.SetMode(t.Context(), &rpc.PermissionsSetModeRequest{Mode: rpc.PermissionModeManual}) if err != nil { - t.Fatalf("Permissions.SetAllowAll(false) failed: %v", err) + t.Fatalf("Permissions.SetMode(manual) failed: %v", err) } - if !disable.Success || disable.Enabled { - t.Fatalf("Expected successful disable, got %+v", disable) + if !disable.Success || disable.Mode != rpc.PermissionModeManual { + t.Fatalf("Expected successful manual mode change, got %+v", disable) } - afterDisable, err := session.RPC.Permissions.GetAllowAll(t.Context()) + afterDisable, err := session.RPC.Permissions.GetMode(t.Context()) if err != nil { - t.Fatalf("Permissions.GetAllowAll after disable failed: %v", err) + t.Fatalf("Permissions.GetMode after manual failed: %v", err) } - if afterDisable.Enabled { - t.Fatal("Expected allow-all to be disabled") + if afterDisable.Mode != rpc.PermissionModeManual { + t.Fatalf("Expected manual mode, got %q", afterDisable.Mode) } }) diff --git a/go/internal/e2e/rpc_tasks_and_handlers_e2e_test.go b/go/internal/e2e/rpc_tasks_and_handlers_e2e_test.go index 0267f8d042..648855e5a3 100644 --- a/go/internal/e2e/rpc_tasks_and_handlers_e2e_test.go +++ b/go/internal/e2e/rpc_tasks_and_handlers_e2e_test.go @@ -127,6 +127,7 @@ func TestRPCTasksAndHandlersE2E(t *testing.T) { }) t.Run("should report implemented error for invalid task agent model", func(t *testing.T) { + ctx.ConfigureForTest(t) session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ OnPermissionRequest: copilot.PermissionHandler.ApproveAll, }) diff --git a/go/internal/e2e/rpc_ui_ephemeral_query_e2e_test.go b/go/internal/e2e/rpc_ui_ephemeral_query_e2e_test.go index 2669faea9a..a431aaee04 100644 --- a/go/internal/e2e/rpc_ui_ephemeral_query_e2e_test.go +++ b/go/internal/e2e/rpc_ui_ephemeral_query_e2e_test.go @@ -14,6 +14,11 @@ func TestRpcUiEphemeralQuery(t *testing.T) { t.Cleanup(func() { client.ForceStop() }) t.Run("should_answer_ephemeral_query", func(t *testing.T) { + // TODO(cli-1.0.81-2): CLI 1.0.81-5 still fails session.ui.ephemeralQuery against the + // recorded snapshot on macOS ("Failed to get response from the AI model"). Re-enable + // once the runtime fix ships. + t.Skip("blocked on CLI 1.0.81-5 session.ui.ephemeralQuery regression on macOS") + ctx.ConfigureForTest(t) session := createPortedSession(t, client, nil) defer session.Disconnect() diff --git a/go/internal/e2e/session_config_e2e_test.go b/go/internal/e2e/session_config_e2e_test.go index 2ce48e3b33..f1c267e230 100644 --- a/go/internal/e2e/session_config_e2e_test.go +++ b/go/internal/e2e/session_config_e2e_test.go @@ -119,8 +119,8 @@ func createAnthropicProvider() *copilot.ProviderConfig { Type: "anthropic", BaseURL: "https://anthropic-citations.invalid/v1", APIKey: "test-provider-key", - ModelID: "claude-sonnet-4.5", - WireModel: "claude-sonnet-4.5", + ModelID: "claude-sonnet-5", + WireModel: "claude-sonnet-5", } } @@ -184,6 +184,7 @@ func TestSessionConfigE2E(t *testing.T) { session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + Model: "claude-sonnet-5", ModelCapabilities: &copilot.ModelCapabilitiesOverride{ Supports: &copilot.ModelCapabilitiesOverrideSupports{ Vision: copilot.Bool(false), @@ -208,7 +209,7 @@ func TestSessionConfigE2E(t *testing.T) { } // Switch vision on - if err := session.SetModel(t.Context(), "claude-sonnet-4.5", &copilot.SetModelOptions{ + if err := session.SetModel(t.Context(), "claude-sonnet-5", &copilot.SetModelOptions{ ModelCapabilities: &copilot.ModelCapabilitiesOverride{ Supports: &copilot.ModelCapabilitiesOverrideSupports{ Vision: copilot.Bool(true), @@ -238,6 +239,7 @@ func TestSessionConfigE2E(t *testing.T) { session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + Model: "claude-sonnet-5", ModelCapabilities: &copilot.ModelCapabilitiesOverride{ Supports: &copilot.ModelCapabilitiesOverrideSupports{ Vision: copilot.Bool(true), @@ -262,7 +264,7 @@ func TestSessionConfigE2E(t *testing.T) { } // Switch vision off - if err := session.SetModel(t.Context(), "claude-sonnet-4.5", &copilot.SetModelOptions{ + if err := session.SetModel(t.Context(), "claude-sonnet-5", &copilot.SetModelOptions{ ModelCapabilities: &copilot.ModelCapabilitiesOverride{ Supports: &copilot.ModelCapabilitiesOverrideSupports{ Vision: copilot.Bool(false), @@ -420,7 +422,7 @@ func TestSessionConfigNewOptionsCopilotRequestE2E(t *testing.T) { session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ OnPermissionRequest: copilot.PermissionHandler.ApproveAll, - Model: "claude-sonnet-4.5", + Model: "claude-sonnet-5", EnableCitations: copilot.Bool(true), Provider: createAnthropicProvider(), }) @@ -481,7 +483,7 @@ func TestSessionConfigNewOptionsCopilotRequestE2E(t *testing.T) { session2, err := resumeClient.ResumeSessionWithOptions(t.Context(), session1.SessionID, &copilot.ResumeSessionConfig{ OnPermissionRequest: copilot.PermissionHandler.ApproveAll, - Model: "claude-sonnet-4.5", + Model: "claude-sonnet-5", EnableCitations: copilot.Bool(true), Provider: createAnthropicProvider(), }) @@ -589,7 +591,7 @@ func TestSessionConfigExtrasE2E(t *testing.T) { session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ OnPermissionRequest: copilot.PermissionHandler.ApproveAll, - Model: "claude-sonnet-4.5", + Model: "claude-sonnet-5", Provider: createProxyProvider(ctx, providerHeaderName, "create-provider-header"), }) if err != nil { @@ -633,7 +635,7 @@ func TestSessionConfigExtrasE2E(t *testing.T) { session2, err := client.ResumeSession(t.Context(), sessionID, &copilot.ResumeSessionConfig{ OnPermissionRequest: copilot.PermissionHandler.ApproveAll, - Model: "claude-sonnet-4.5", + Model: "claude-sonnet-5", Provider: createProxyProvider(ctx, providerHeaderName, "resume-provider-header"), }) if err != nil { @@ -677,7 +679,7 @@ func TestSessionConfigExtrasE2E(t *testing.T) { maxOutputTokens := 1024 session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ OnPermissionRequest: copilot.PermissionHandler.ApproveAll, - Model: "claude-sonnet-4.5", + Model: "claude-sonnet-5", Provider: &copilot.ProviderConfig{ Type: "openai", BaseURL: ctx.ProxyURL, @@ -719,7 +721,7 @@ func TestSessionConfigExtrasE2E(t *testing.T) { Type: "openai", BaseURL: ctx.ProxyURL, APIKey: "test-provider-key", - ModelID: "claude-sonnet-4.5", + ModelID: "claude-sonnet-5", }, }) if err != nil { @@ -738,8 +740,8 @@ func TestSessionConfigExtrasE2E(t *testing.T) { if len(exchanges) != 1 { t.Fatalf("Expected exactly 1 exchange, got %d", len(exchanges)) } - if exchanges[0].Request.Model != "claude-sonnet-4.5" { - t.Errorf("Expected request model to be 'claude-sonnet-4.5', got %q", exchanges[0].Request.Model) + if exchanges[0].Request.Model != "claude-sonnet-5" { + t.Errorf("Expected request model to be 'claude-sonnet-5', got %q", exchanges[0].Request.Model) } }) diff --git a/go/internal/e2e/session_e2e_test.go b/go/internal/e2e/session_e2e_test.go index 440a303483..efc20250b1 100644 --- a/go/internal/e2e/session_e2e_test.go +++ b/go/internal/e2e/session_e2e_test.go @@ -23,7 +23,7 @@ func TestSessionE2E(t *testing.T) { t.Run("should create and disconnect sessions", func(t *testing.T) { ctx.ConfigureForTest(t) - session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{OnPermissionRequest: copilot.PermissionHandler.ApproveAll, Model: "claude-sonnet-4.5"}) + session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{OnPermissionRequest: copilot.PermissionHandler.ApproveAll, Model: "claude-sonnet-5"}) if err != nil { t.Fatalf("Failed to create session: %v", err) } @@ -47,8 +47,8 @@ func TestSessionE2E(t *testing.T) { t.Errorf("Expected session.start sessionId to match") } - if !startOk || startData.SelectedModel == nil || *startData.SelectedModel != "claude-sonnet-4.5" { - t.Errorf("Expected selectedModel to be 'claude-sonnet-4.5', got %v", startData) + if !startOk || startData.SelectedModel == nil || *startData.SelectedModel != "claude-sonnet-5" { + t.Errorf("Expected selectedModel to be 'claude-sonnet-5', got %v", startData) } if err := session.Disconnect(); err != nil { @@ -448,6 +448,10 @@ func TestSessionE2E(t *testing.T) { }) t.Run("should resume a session using a new client", func(t *testing.T) { + // TODO(cli-1.0.81-2): resuming a session over the in-process transport no longer + // routes model traffic through COPILOT_API_URL, so the CLI reaches the real + // api.githubcopilot.com and macOS rejects the proxy certificate with an EKU error. + testharness.SkipIfInProcessOnMacOS(t, "session resume ignores COPILOT_API_URL") ctx.ConfigureForTest(t) // Create initial session diff --git a/go/internal/e2e/session_event_loop_leak_e2e_test.go b/go/internal/e2e/session_event_loop_leak_e2e_test.go new file mode 100644 index 0000000000..c6974f74d8 --- /dev/null +++ b/go/internal/e2e/session_event_loop_leak_e2e_test.go @@ -0,0 +1,102 @@ +package e2e + +import ( + "runtime" + "testing" + "time" + + copilot "github.com/github/copilot-sdk/go" + "github.com/github/copilot-sdk/go/internal/e2e/testharness" +) + +// TestSessionEventLoopLeakE2E is a regression test for the goroutine leak fixed +// alongside PR #2360: CreateSession/ResumeSession construct a *Session and start +// its event-dispatch goroutine eagerly, before the server confirms the session, +// so the CLI can route session-scoped requests to it while session.create (or +// session.resume) is still being processed. Every failure path must stop that +// goroutine — otherwise each failed call leaks one goroutine forever, since no +// caller ever receives the failed session to Disconnect() it. This test fails +// without the fix and passes with it. +func TestSessionEventLoopLeakE2E(t *testing.T) { + ctx := testharness.NewTestContext(t) + + t.Run("CreateSession failure does not leak the event loop goroutine", func(t *testing.T) { + ctx.ConfigureForTest(t) + + // Redirect the CLI's GitHub API calls at the replaying proxy so an + // invalid per-session token makes the real CLI reject session.create + // with a genuine RPC error (401 Unauthorized), exercising the same + // failure path a real user would hit — not a mocked transport. + client := ctx.NewClient(func(opts *copilot.ClientOptions) { + opts.Env = append(opts.Env, "COPILOT_DEBUG_GITHUB_API_URL="+ctx.ProxyURL) + }) + t.Cleanup(func() { client.ForceStop() }) + + createFailing := func() { + if _, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + GitHubToken: "invalid-token", + }); err == nil { + t.Fatal("expected CreateSession to fail with an invalid token") + } + } + + // Warm up: the first call spawns the CLI subprocess and its steady-state + // goroutines (read loop, etc.), which must not be counted as leaks. + createFailing() + + assertNoGoroutineLeak(t, 20, createFailing) + }) + + t.Run("ResumeSession failure does not leak the event loop goroutine", func(t *testing.T) { + ctx.ConfigureForTest(t) + + client := ctx.NewClient() + t.Cleanup(func() { client.ForceStop() }) + + resumeNonExistent := func() { + if _, err := client.ResumeSession(t.Context(), "non-existent-leak-check-session", &copilot.ResumeSessionConfig{ + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + }); err == nil { + t.Fatal("expected ResumeSession for a non-existent session to fail") + } + } + + // Warm up: the first call spawns the CLI subprocess and its steady-state + // goroutines (read loop, etc.), which must not be counted as leaks. + resumeNonExistent() + + assertNoGoroutineLeak(t, 20, resumeNonExistent) + }) +} + +// assertNoGoroutineLeak runs fn n times and fails if the goroutine count grows +// roughly in proportion to n afterward, which would indicate one goroutine +// leaked per call rather than transient goroutines that already exited. +func assertNoGoroutineLeak(t *testing.T, n int, fn func()) { + t.Helper() + runtime.GC() + before := runtime.NumGoroutine() + + for i := 0; i < n; i++ { + fn() + } + + // Give any goroutines that exit promptly (but not synchronously with the + // call returning) a brief window to actually terminate before measuring. + deadline := time.Now().Add(2 * time.Second) + var after int + for { + runtime.GC() + after = runtime.NumGoroutine() + if after <= before+3 || time.Now().After(deadline) { + break + } + time.Sleep(50 * time.Millisecond) + } + + t.Logf("goroutines before=%d after=%d (n=%d)", before, after, n) + if after > before+3 { + t.Fatalf("goroutine count grew from %d to %d after %d failed calls; suspected event-loop leak", before, after, n) + } +} diff --git a/go/internal/e2e/session_fs_e2e_test.go b/go/internal/e2e/session_fs_e2e_test.go index 3ba91c7993..4236e8e529 100644 --- a/go/internal/e2e/session_fs_e2e_test.go +++ b/go/internal/e2e/session_fs_e2e_test.go @@ -196,6 +196,8 @@ func TestSessionFSE2E(t *testing.T) { t.Fatalf("Failed to get messages: %v", err) } toolResult := findToolCallResult(messages, "get_big_string") + // Windows reports the spilled file with backslash separators. + toolResult = strings.ReplaceAll(toolResult, "\\", "/") if !strings.Contains(toolResult, sessionStatePath+"/temp/") { t.Fatalf("Expected tool result to reference %s/temp/, got %q", sessionStatePath, toolResult) } diff --git a/go/internal/e2e/session_fs_sqlite_e2e_test.go b/go/internal/e2e/session_fs_sqlite_e2e_test.go index 20cd777834..8042614496 100644 --- a/go/internal/e2e/session_fs_sqlite_e2e_test.go +++ b/go/internal/e2e/session_fs_sqlite_e2e_test.go @@ -3,6 +3,7 @@ package e2e import ( "encoding/json" "fmt" + "os" "sort" "strings" "sync" @@ -54,7 +55,7 @@ func (p *inMemorySqliteProvider) ReadFile(path string) (string, error) { defer p.mu.Unlock() content, ok := p.files[path] if !ok { - return "", fmt.Errorf("file not found: %s", path) + return "", fmt.Errorf("%s: %w", path, os.ErrNotExist) } return content, nil } @@ -97,7 +98,7 @@ func (p *inMemorySqliteProvider) Stat(path string) (*copilot.SessionFSFileInfo, IsFile: true, IsDirectory: false, Size: int64(len(content)), Mtime: now, Birthtime: now, }, nil } - return nil, fmt.Errorf("not found: %s", path) + return nil, fmt.Errorf("%s: %w", path, os.ErrNotExist) } func (p *inMemorySqliteProvider) MakeDirectory(path string, recursive bool, mode *int) error { diff --git a/go/internal/e2e/streaming_fidelity_e2e_test.go b/go/internal/e2e/streaming_fidelity_e2e_test.go index 7f6d4fba8e..23b1d91a46 100644 --- a/go/internal/e2e/streaming_fidelity_e2e_test.go +++ b/go/internal/e2e/streaming_fidelity_e2e_test.go @@ -142,6 +142,10 @@ func TestStreamingFidelityE2E(t *testing.T) { }) t.Run("should produce deltas after session resume", func(t *testing.T) { + // TODO(cli-1.0.81-2): resuming a session over the in-process transport no longer + // routes model traffic through COPILOT_API_URL, so the CLI reaches the real + // api.githubcopilot.com and macOS rejects the proxy certificate with an EKU error. + testharness.SkipIfInProcessOnMacOS(t, "session resume ignores COPILOT_API_URL") ctx.ConfigureForTest(t) session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ @@ -212,6 +216,10 @@ func TestStreamingFidelityE2E(t *testing.T) { }) t.Run("should not produce deltas after session resume with streaming disabled", func(t *testing.T) { + // TODO(cli-1.0.81-2): resuming a session over the in-process transport no longer + // routes model traffic through COPILOT_API_URL, so the CLI reaches the real + // api.githubcopilot.com and macOS rejects the proxy certificate with an EKU error. + testharness.SkipIfInProcessOnMacOS(t, "session resume ignores COPILOT_API_URL") ctx.ConfigureForTest(t) session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ diff --git a/go/internal/e2e/telemetry_e2e_test.go b/go/internal/e2e/telemetry_e2e_test.go index 4567817fd9..77f8bec8ea 100644 --- a/go/internal/e2e/telemetry_e2e_test.go +++ b/go/internal/e2e/telemetry_e2e_test.go @@ -96,17 +96,6 @@ func TestTelemetryE2E(t *testing.T) { } } - traceIDs := map[string]struct{}{} - for _, span := range spans { - id := stringProp(span, "traceId") - if id != "" { - traceIDs[id] = struct{}{} - } - } - if len(traceIDs) != 1 { - t.Errorf("Expected exactly 1 trace id across spans, got %d (%v)", len(traceIDs), traceIDs) - } - invokeAgent := findSpanWithOperation(spans, "invoke_agent") if invokeAgent == nil { t.Fatal("Expected an invoke_agent span") @@ -121,6 +110,10 @@ func TestTelemetryE2E(t *testing.T) { if invokeAgentSpanID == "" { t.Fatal("invoke_agent span has empty spanId") } + invokeAgentTraceID := stringProp(invokeAgent, "traceId") + if invokeAgentTraceID == "" { + t.Fatal("invoke_agent span has empty traceId") + } var chatSpans []map[string]any for _, span := range spans { @@ -135,6 +128,9 @@ func TestTelemetryE2E(t *testing.T) { if got := stringProp(chat, "parentSpanId"); got != invokeAgentSpanID { t.Errorf("Expected chat span parentSpanId=%q, got %q", invokeAgentSpanID, got) } + if got := stringProp(chat, "traceId"); got != invokeAgentTraceID { + t.Errorf("Expected chat span traceId=%q, got %q", invokeAgentTraceID, got) + } } var sawPromptInput, sawDoneOutput bool for _, chat := range chatSpans { @@ -159,6 +155,9 @@ func TestTelemetryE2E(t *testing.T) { if got := stringProp(toolSpan, "parentSpanId"); got != invokeAgentSpanID { t.Errorf("Expected execute_tool parentSpanId=%q, got %q", invokeAgentSpanID, got) } + if got := stringProp(toolSpan, "traceId"); got != invokeAgentTraceID { + t.Errorf("Expected execute_tool traceId=%q, got %q", invokeAgentTraceID, got) + } if got := stringAttr(toolSpan, "gen_ai.tool.name"); got != toolName { t.Errorf("Expected gen_ai.tool.name=%q, got %q", toolName, got) } diff --git a/go/internal/e2e/testharness/context.go b/go/internal/e2e/testharness/context.go index 03ebf24cbf..0374cc5130 100644 --- a/go/internal/e2e/testharness/context.go +++ b/go/internal/e2e/testharness/context.go @@ -2,6 +2,7 @@ package testharness import ( "os" + "os/exec" "path/filepath" "regexp" "runtime" @@ -29,15 +30,18 @@ func CLIPath() string { return } - // 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). - base := RepoPath("nodejs", "node_modules", "@github") - matches, _ := filepath.Glob(filepath.Join(base, "copilot-*", "index.js")) - if len(matches) > 0 { - cliPath = matches[0] - return + npm := "npm" + if runtime.GOOS == "windows" { + npm = "npm.cmd" + } + command := exec.Command(npm, "run", "--silent", "prepare:runtime", "--", "--print-path") + command.Dir = RepoPath("nodejs") + output, err := command.Output() + if err == nil { + candidate := strings.TrimSpace(string(output)) + if info, statErr := os.Stat(candidate); statErr == nil && !info.IsDir() { + cliPath = candidate + } } }) return cliPath @@ -114,6 +118,16 @@ func SkipIfInProcess(t *testing.T, reason string) { } } +// SkipIfInProcessOnMacOS skips the test when E2E tests run under the in-process +// (FFI) transport on macOS. Coverage is retained over stdio on every OS and +// in-process on Linux and Windows. +func SkipIfInProcessOnMacOS(t *testing.T, reason string) { + t.Helper() + if isInProcessTransport() && runtime.GOOS == "darwin" { + t.Skipf("unsupported over the in-process (FFI) transport on macOS: %s", reason) + } +} + // NewTestContext creates a new test context with isolated directories and a replaying proxy. func NewTestContext(t *testing.T) *TestContext { t.Helper() diff --git a/go/internal/embeddedcli/embeddedcli.go b/go/internal/embeddedcli/embeddedcli.go index cd0be21895..2535cf5f20 100644 --- a/go/internal/embeddedcli/embeddedcli.go +++ b/go/internal/embeddedcli/embeddedcli.go @@ -1,7 +1,9 @@ package embeddedcli import ( + "archive/tar" "bytes" + "compress/gzip" "crypto/sha256" "fmt" "io" @@ -23,10 +25,10 @@ import ( // version-specific child directory so multiple versions can coexist. License, // when provided, is written next to the installed binary. // -// RuntimeLib and RuntimeLibHash are optional: when set, the native in-process -// runtime library (cdylib) is installed next to the CLI binary so the in-process -// (FFI) transport can load it. They are omitted for CLI packages that do not -// ship the native runtime. +// RuntimeExecutable and RuntimeNode form the adjacent out-of-process runtime +// pair. RuntimeAssets is a filtered npm package archive containing auxiliary +// binaries and resources. RuntimeLib is the same cdylib bytes installed under +// the natural platform name for the optional in-process transport. type Config struct { Cli io.Reader CliHash []byte @@ -36,12 +38,25 @@ type Config struct { RuntimeLib io.Reader RuntimeLibHash []byte + RuntimeExecutable io.Reader + RuntimeExecutableHash []byte + RuntimeNode io.Reader + RuntimeNodeHash []byte + RuntimeAssets io.Reader + RuntimeAssetsHash []byte + // LinuxMuslCli and LinuxMuslRuntimeLib are optional alternatives selected // automatically when the application runs on a musl-based Linux system. - LinuxMuslCli io.Reader - LinuxMuslCliHash []byte - LinuxMuslRuntimeLib io.Reader - LinuxMuslRuntimeLibHash []byte + LinuxMuslCli io.Reader + LinuxMuslCliHash []byte + LinuxMuslRuntimeLib io.Reader + LinuxMuslRuntimeLibHash []byte + LinuxMuslRuntimeExecutable io.Reader + LinuxMuslRuntimeExecutableHash []byte + LinuxMuslRuntimeNode io.Reader + LinuxMuslRuntimeNodeHash []byte + LinuxMuslRuntimeAssets io.Reader + LinuxMuslRuntimeAssetsHash []byte Dir string Version string @@ -60,6 +75,10 @@ func Setup(cfg Config) { if cfg.LinuxMuslRuntimeLib != nil && len(cfg.LinuxMuslRuntimeLibHash) != sha256.Size { panic(fmt.Sprintf("LinuxMuslRuntimeLibHash must be a SHA-256 hash (%d bytes), got %d bytes", sha256.Size, len(cfg.LinuxMuslRuntimeLibHash))) } + validateRuntimePairConfig(cfg.RuntimeExecutable, cfg.RuntimeExecutableHash, cfg.RuntimeNode, cfg.RuntimeNodeHash, "") + validateRuntimePairConfig(cfg.LinuxMuslRuntimeExecutable, cfg.LinuxMuslRuntimeExecutableHash, cfg.LinuxMuslRuntimeNode, cfg.LinuxMuslRuntimeNodeHash, "LinuxMusl") + validateOptionalHash(cfg.RuntimeAssets, cfg.RuntimeAssetsHash, "RuntimeAssetsHash") + validateOptionalHash(cfg.LinuxMuslRuntimeAssets, cfg.LinuxMuslRuntimeAssetsHash, "LinuxMuslRuntimeAssetsHash") setupMu.Lock() defer setupMu.Unlock() if setupDone { @@ -93,13 +112,34 @@ func RuntimeLibPath() string { return runtimeLibPath } +// RuntimePath returns the installed copilot-runtime executable, or "" when the +// application bundle predates the out-of-process runtime pair. +func RuntimePath() string { + setupMu.Lock() + defer setupMu.Unlock() + if !setupDone { + return "" + } + pathInitialized = true + selectLinuxMuslBundle() + if config.RuntimeExecutable == nil { + return "" + } + if runtimePath == "" { + runtimePath = installRuntime() + } + return runtimePath +} + var ( - config Config - setupMu sync.Mutex - setupDone bool - pathInitialized bool - runtimeLibPath string - linuxMuslBundle bool + config Config + setupMu sync.Mutex + setupDone bool + pathInitialized bool + runtimeLibPath string + runtimePath string + runtimeAssetsInstalled bool + linuxMuslBundle bool ) func install() (path string) { @@ -118,6 +158,38 @@ func install() (path string) { fmt.Printf("installing embedded CLI at %s installation took %s\n", path, duration) }() } + installDir := configuredInstallDir() + path, err := installAt(installDir) + if err != nil { + logError("installing in configured directory", err) + return "" + } + return path +} + +func installRuntime() (path string) { + verbose := os.Getenv("COPILOT_CLI_INSTALL_VERBOSE") == "1" + logError := func(msg string, err error) { + if verbose { + fmt.Printf("embedded runtime installation error: %s: %v\n", msg, err) + } + } + if verbose { + start := time.Now() + defer func() { + fmt.Printf("installing embedded runtime at %s took %s\n", path, time.Since(start)) + }() + } + + path, err := installRuntimeAt(configuredInstallDir()) + if err != nil { + logError("installing in configured directory", err) + return "" + } + return path +} + +func configuredInstallDir() string { installDir := config.Dir if installDir == "" { if copilotHome := os.Getenv("COPILOT_HOME"); copilotHome != "" { @@ -131,12 +203,7 @@ func install() (path string) { installDir = filepath.Join(installDir, "copilot-sdk") } } - path, err := installAt(installDir) - if err != nil { - logError("installing in configured directory", err) - return "" - } - return path + return installDir } func selectLinuxMuslBundle() { @@ -152,6 +219,12 @@ func linuxMuslConfig(cfg Config) Config { cfg.CliHash = cfg.LinuxMuslCliHash cfg.RuntimeLib = cfg.LinuxMuslRuntimeLib cfg.RuntimeLibHash = cfg.LinuxMuslRuntimeLibHash + cfg.RuntimeExecutable = cfg.LinuxMuslRuntimeExecutable + cfg.RuntimeExecutableHash = cfg.LinuxMuslRuntimeExecutableHash + cfg.RuntimeNode = cfg.LinuxMuslRuntimeNode + cfg.RuntimeNodeHash = cfg.LinuxMuslRuntimeNodeHash + cfg.RuntimeAssets = cfg.LinuxMuslRuntimeAssets + cfg.RuntimeAssetsHash = cfg.LinuxMuslRuntimeAssetsHash return cfg } @@ -198,6 +271,9 @@ func installAt(installDir string) (string, error) { } runtimeLibPath = libPath } + if err := installRuntimeAssets(installDir); err != nil { + return "", err + } return finalPath, nil } @@ -229,12 +305,188 @@ func installAt(installDir string) (string, error) { if err != nil { return "", err } + if err := installRuntimeAssets(installDir); err != nil { + return "", err + } runtimeLibPath = libPath } return finalPath, nil } +func installRuntimeAt(installDir string) (string, error) { + version := sanitizeVersion(config.Version) + if version != "" { + installDir = filepath.Join(installDir, version) + } + if linuxMuslBundle { + installDir = filepath.Join(installDir, "linuxmusl") + } + if err := os.MkdirAll(installDir, 0755); err != nil { + return "", fmt.Errorf("creating install directory: %w", err) + } + + if release, _ := flock.Acquire(filepath.Join(installDir, ".copilot-cli.lock")); release != nil { + defer release() + } + path, err := installRuntimePair(installDir) + if err != nil { + return "", err + } + if err := installRuntimeAssets(installDir); err != nil { + return "", err + } + return path, nil +} + +func validateOptionalHash(reader io.Reader, hash []byte, name string) { + if reader != nil && len(hash) != sha256.Size { + panic(fmt.Sprintf("%s must be a SHA-256 hash (%d bytes), got %d bytes", name, sha256.Size, len(hash))) + } +} + +func installRuntimeAssets(installDir string) error { + if config.RuntimeAssets == nil || runtimeAssetsInstalled { + return nil + } + archiveBytes, err := io.ReadAll(config.RuntimeAssets) + if closer, ok := config.RuntimeAssets.(io.Closer); ok { + closer.Close() + } + if err != nil { + return fmt.Errorf("reading runtime assets: %w", err) + } + actual := sha256.Sum256(archiveBytes) + if !bytes.Equal(actual[:], config.RuntimeAssetsHash) { + return fmt.Errorf("runtime assets hash mismatch") + } + gzipReader, err := gzip.NewReader(bytes.NewReader(archiveBytes)) + if err != nil { + return fmt.Errorf("opening runtime assets: %w", err) + } + defer gzipReader.Close() + tarReader := tar.NewReader(gzipReader) + for { + header, err := tarReader.Next() + if err == io.EOF { + break + } + if err != nil { + return fmt.Errorf("reading runtime assets: %w", err) + } + if header.Typeflag != tar.TypeReg { + continue + } + clean := filepath.Clean(filepath.FromSlash(header.Name)) + if !filepath.IsLocal(clean) { + return fmt.Errorf("unsafe runtime asset path %q", header.Name) + } + content, err := io.ReadAll(tarReader) + if err != nil { + return fmt.Errorf("reading runtime asset %q: %w", header.Name, err) + } + path := filepath.Join(installDir, clean) + if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { + return fmt.Errorf("creating runtime asset directory: %w", err) + } + hash := sha256.Sum256(content) + mode := os.FileMode(header.Mode & 0777) + if err := installVerifiedFile(path, bytes.NewReader(content), hash[:], mode, "runtime asset"); err != nil { + return err + } + } + runtimeAssetsInstalled = true + return nil +} + +func validateRuntimePairConfig(wrapper io.Reader, wrapperHash []byte, node io.Reader, nodeHash []byte, prefix string) { + if (wrapper == nil) != (node == nil) { + panic(prefix + "RuntimeExecutable and " + prefix + "RuntimeNode must be provided together") + } + if wrapper == nil { + return + } + if len(wrapperHash) != sha256.Size { + panic(fmt.Sprintf("%sRuntimeExecutableHash must be a SHA-256 hash (%d bytes), got %d bytes", prefix, sha256.Size, len(wrapperHash))) + } + if len(nodeHash) != sha256.Size { + panic(fmt.Sprintf("%sRuntimeNodeHash must be a SHA-256 hash (%d bytes), got %d bytes", prefix, sha256.Size, len(nodeHash))) + } +} + +func installRuntimePair(installDir string) (string, error) { + nodePath := filepath.Join(installDir, "runtime.node") + if err := installVerifiedFile(nodePath, config.RuntimeNode, config.RuntimeNodeHash, 0644, "runtime.node"); err != nil { + return "", err + } + wrapperPath := filepath.Join(installDir, runtimeExecutableName()) + if err := installVerifiedFile(wrapperPath, config.RuntimeExecutable, config.RuntimeExecutableHash, 0755, "runtime wrapper"); err != nil { + return "", err + } + return wrapperPath, nil +} + +func installVerifiedFile(path string, reader io.Reader, expectedHash []byte, mode os.FileMode, label string) error { + if _, err := os.Stat(path); err == nil { + existingHash, err := hashFile(path) + if err != nil { + return fmt.Errorf("hashing existing %s: %w", label, err) + } + if !bytes.Equal(existingHash, expectedHash) { + return fmt.Errorf("existing %s hash mismatch", label) + } + if runtime.GOOS != "windows" && mode.Perm()&0111 != 0 { + info, err := os.Stat(path) + if err != nil { + return fmt.Errorf("checking existing %s permissions: %w", label, err) + } + if info.Mode().Perm()&0111 == 0 { + if err := os.Chmod(path, info.Mode().Perm()|mode.Perm()&0111); err != nil { + return fmt.Errorf("restoring existing %s permissions: %w", label, err) + } + } + } + return nil + } + + tmp, err := os.CreateTemp(filepath.Dir(path), ".copilot-runtime-pair-*.tmp") + if err != nil { + return fmt.Errorf("creating temporary %s: %w", label, err) + } + tmpPath := tmp.Name() + h := sha256.New() + _, err = io.Copy(io.MultiWriter(tmp, h), reader) + if err1 := tmp.Chmod(mode); err1 != nil && err == nil { + err = err1 + } + if err1 := tmp.Close(); err1 != nil && err == nil { + err = err1 + } + if closer, ok := reader.(io.Closer); ok { + closer.Close() + } + if err != nil { + os.Remove(tmpPath) + return fmt.Errorf("writing %s: %w", label, err) + } + if !bytes.Equal(h.Sum(nil), expectedHash) { + os.Remove(tmpPath) + return fmt.Errorf("%s hash mismatch", label) + } + if err := os.Rename(tmpPath, path); err != nil { + os.Remove(tmpPath) + return fmt.Errorf("installing %s: %w", label, err) + } + return nil +} + +func runtimeExecutableName() string { + if runtime.GOOS == "windows" { + return "copilot-runtime.exe" + } + return "copilot-runtime" +} + // installRuntimeLib writes the embedded runtime cdylib into installDir under its // natural platform file name, verifying its SHA-256. It is idempotent: an // existing file with a matching hash is reused; a mismatch is a hard error. diff --git a/go/internal/embeddedcli/embeddedcli_test.go b/go/internal/embeddedcli/embeddedcli_test.go index b0394e0f69..159b6e1505 100644 --- a/go/internal/embeddedcli/embeddedcli_test.go +++ b/go/internal/embeddedcli/embeddedcli_test.go @@ -1,7 +1,9 @@ package embeddedcli import ( + "archive/tar" "bytes" + "compress/gzip" "crypto/sha256" "os" "path/filepath" @@ -17,9 +19,143 @@ func resetGlobals() { setupDone = false pathInitialized = false runtimeLibPath = "" + runtimePath = "" + runtimeAssetsInstalled = false linuxMuslBundle = false } +func TestInstallRuntimeWritesRetainedAssets(t *testing.T) { + resetGlobals() + tempDir := t.TempDir() + cli := []byte("cli") + wrapper := []byte("wrapper") + node := []byte("runtime") + assets := runtimeAssetsArchive(t, map[string]assetFixture{ + "ripgrep/bin/test-platform/rg": {content: []byte("ripgrep"), mode: 0755}, + "definitions/future.json": {content: []byte("{}"), mode: 0644}, + }) + cliHash := sha256.Sum256(cli) + wrapperHash := sha256.Sum256(wrapper) + nodeHash := sha256.Sum256(node) + assetsHash := sha256.Sum256(assets) + Setup(Config{ + Cli: bytes.NewReader(cli), + CliHash: cliHash[:], + RuntimeExecutable: bytes.NewReader(wrapper), + RuntimeExecutableHash: wrapperHash[:], + RuntimeNode: bytes.NewReader(node), + RuntimeNodeHash: nodeHash[:], + RuntimeAssets: bytes.NewReader(assets), + RuntimeAssetsHash: assetsHash[:], + Version: "1.2.3", + Dir: tempDir, + }) + + gotWrapper, err := installRuntimeAt(tempDir) + if err != nil { + t.Fatal(err) + } + installDir := filepath.Dir(gotWrapper) + if got, err := os.ReadFile(filepath.Join(installDir, "ripgrep", "bin", "test-platform", "rg")); err != nil || string(got) != "ripgrep" { + t.Fatalf("ripgrep content=%q err=%v", got, err) + } + if got, err := os.ReadFile(filepath.Join(installDir, "definitions", "future.json")); err != nil || string(got) != "{}" { + t.Fatalf("definition content=%q err=%v", got, err) + } +} + +type assetFixture struct { + content []byte + mode int64 +} + +func runtimeAssetsArchive(t *testing.T, files map[string]assetFixture) []byte { + t.Helper() + var buffer bytes.Buffer + gzipWriter := gzip.NewWriter(&buffer) + tarWriter := tar.NewWriter(gzipWriter) + for name, fixture := range files { + header := &tar.Header{Name: name, Mode: fixture.mode, Size: int64(len(fixture.content)), Typeflag: tar.TypeReg} + if err := tarWriter.WriteHeader(header); err != nil { + t.Fatal(err) + } + if _, err := tarWriter.Write(fixture.content); err != nil { + t.Fatal(err) + } + } + if err := tarWriter.Close(); err != nil { + t.Fatal(err) + } + if err := gzipWriter.Close(); err != nil { + t.Fatal(err) + } + return buffer.Bytes() +} + +func TestInstallRuntimeWritesAdjacentPairWithoutCLI(t *testing.T) { + resetGlobals() + tempDir := t.TempDir() + cli := []byte("cli") + wrapper := []byte("wrapper") + node := []byte("runtime") + cliHash := sha256.Sum256(cli) + wrapperHash := sha256.Sum256(wrapper) + nodeHash := sha256.Sum256(node) + Setup(Config{ + Cli: bytes.NewReader(cli), + CliHash: cliHash[:], + RuntimeExecutable: bytes.NewReader(wrapper), + RuntimeExecutableHash: wrapperHash[:], + RuntimeNode: bytes.NewReader(node), + RuntimeNodeHash: nodeHash[:], + Version: "1.2.3", + Dir: tempDir, + }) + + gotWrapper, err := installRuntimeAt(tempDir) + if err != nil { + t.Fatal(err) + } + if gotWrapper != filepath.Join(tempDir, "1.2.3", runtimeExecutableName()) { + t.Fatalf("RuntimePath() = %q", gotWrapper) + } + if got, err := os.ReadFile(filepath.Join(filepath.Dir(gotWrapper), "runtime.node")); err != nil || !bytes.Equal(got, node) { + t.Fatalf("runtime.node content=%q err=%v", got, err) + } + if cliPath := filepath.Join(filepath.Dir(gotWrapper), binaryNameForOS()); fileExists(cliPath) { + t.Fatalf("managed runtime installation unexpectedly materialized CLI host at %q", cliPath) + } +} + +func fileExists(path string) bool { + _, err := os.Stat(path) + return err == nil +} + +func TestInstallVerifiedFileRestoresExecutablePermission(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Windows does not use Unix execute bits") + } + path := filepath.Join(t.TempDir(), "copilot-runtime") + content := []byte("wrapper") + hash := sha256.Sum256(content) + if err := os.WriteFile(path, content, 0644); err != nil { + t.Fatal(err) + } + + if err := installVerifiedFile(path, bytes.NewReader(content), hash[:], 0755, "runtime wrapper"); err != nil { + t.Fatal(err) + } + + info, err := os.Stat(path) + if err != nil { + t.Fatal(err) + } + if info.Mode().Perm()&0111 == 0 { + t.Fatal("existing runtime wrapper was not made executable") + } +} + func mustPanic(t *testing.T, fn func()) { t.Helper() defer func() { diff --git a/go/internal/ffihost/ffihost.go b/go/internal/ffihost/ffihost.go index 30cd831281..9a824881de 100644 --- a/go/internal/ffihost/ffihost.go +++ b/go/internal/ffihost/ffihost.go @@ -159,8 +159,8 @@ type Host struct { // Create resolves the native library and prepares the host. environment and // args contain SDK-managed runtime options. -func Create(cliEntrypoint string, environment map[string]string, args []string) (*Host, error) { - libraryPath, err := ResolveLibraryPath(cliEntrypoint) +func Create(runtimeEntrypoint, cliEntrypoint string, environment map[string]string, args []string) (*Host, error) { + libraryPath, err := ResolveLibraryPath(runtimeEntrypoint) if err != nil { return nil, err } @@ -207,18 +207,13 @@ func (h *Host) Start() error { runtime.KeepAlive(argv) runtime.KeepAlive(env) if h.serverID == 0 { - return fmt.Errorf("copilot_runtime_host_start failed (library %q, entrypoint %q)", h.libraryPath, h.cliEntrypoint) + return fmt.Errorf("copilot_runtime_host_start failed (library %q)", h.libraryPath) } - // host_start spawned the worker child via libuv's uv_spawn, which installs a - // SIGCHLD handler without SA_ONSTACK on its first call. The Go runtime aborts - // ("non-Go code set up signal handler without SA_ONSTACK flag") when it later - // reaps one of its own os/exec children (e.g. a test-spawned MCP server) and - // the delivered SIGCHLD lands on a non-signal stack. Re-add SA_ONSTACK to that - // foreign handler now that it exists (implemented on darwin+linux; a no-op on - // other platforms, and before the first spawn there is nothing to fix — hence - // here rather than at library load). - rearmForeignSignalHandlers(h.lib.handle) + if h.cliEntrypoint != "" { + // A legacy embedded host may install a SIGCHLD handler without SA_ONSTACK. + rearmForeignSignalHandlers(h.lib.handle) + } callbackHandle := sharedOutboundCallback() callbackToken := uintptr(nextOutboundToken.Add(1)) @@ -229,7 +224,9 @@ func (h *Host) Start() error { outboundTargets.Delete(callbackToken) h.callbackToken = 0 h.lib.hostShutdown(h.serverID) - rearmForeignSignalHandlers(h.lib.handle) + if h.cliEntrypoint != "" { + rearmForeignSignalHandlers(h.lib.handle) + } h.serverID = 0 return fmt.Errorf("copilot_runtime_connection_open failed") } @@ -243,14 +240,12 @@ func (h *Host) Writer() io.WriteCloser { return hostWriter{h} } func (h *Host) Reader() io.ReadCloser { return h.recv } func (h *Host) buildArgv() []byte { - // A `.js` entrypoint (dev) is launched via node; the packaged single-file CLI - // embeds its own Node and is invoked directly. `--no-auto-update` pins the - // worker to the runtime package matching the loaded cdylib (avoids ABI skew). - var argv []string - if strings.HasSuffix(strings.ToLower(h.cliEntrypoint), ".js") { - argv = []string{"node", h.cliEntrypoint, "--embedded-host", "--no-auto-update"} - } else { - argv = []string{h.cliEntrypoint, "--embedded-host", "--no-auto-update"} + argv := make([]string, 0, len(h.args)+4) + if h.cliEntrypoint != "" { + if strings.HasSuffix(strings.ToLower(h.cliEntrypoint), ".js") { + argv = append(argv, "node") + } + argv = append(argv, h.cliEntrypoint, "--embedded-host", "--no-auto-update") } argv = append(argv, h.args...) b, _ := json.Marshal(argv) @@ -365,10 +360,10 @@ func (h *Host) Dispose() { } if serverID != 0 { h.lib.hostShutdown(serverID) - // libuv may restore a previously saved SIGCHLD action while tearing down - // its final child watcher, so repair the process-wide handler again after - // shutdown before Go reaps another os/exec child. - rearmForeignSignalHandlers(h.lib.handle) + if h.cliEntrypoint != "" { + // A legacy host may restore its saved SIGCHLD action during shutdown. + rearmForeignSignalHandlers(h.lib.handle) + } } h.recv.Close() } diff --git a/go/internal/ffihost/ffihost_test.go b/go/internal/ffihost/ffihost_test.go index bc588fa6a9..ccb48af419 100644 --- a/go/internal/ffihost/ffihost_test.go +++ b/go/internal/ffihost/ffihost_test.go @@ -25,10 +25,9 @@ func TestDisposeUnregistersOutboundTarget(t *testing.T) { } } -func TestBuildArgvAppendsManagedOptions(t *testing.T) { +func TestBuildArgvWithoutEntrypointContainsOnlyManagedOptions(t *testing.T) { host := &Host{ - cliEntrypoint: "copilot", - args: []string{"--log-level", "debug", "--remote"}, + args: []string{"--log-level", "debug", "--remote"}, } var argv []string @@ -36,7 +35,45 @@ func TestBuildArgvAppendsManagedOptions(t *testing.T) { t.Fatal(err) } - expected := []string{"copilot", "--embedded-host", "--no-auto-update", "--log-level", "debug", "--remote"} + expected := []string{"--log-level", "debug", "--remote"} + if len(argv) != len(expected) { + t.Fatalf("Expected %d arguments, got %d: %v", len(expected), len(argv), argv) + } + for i := range expected { + if argv[i] != expected[i] { + t.Fatalf("Expected argument %d to be %q, got %q", i, expected[i], argv[i]) + } + } +} + +func TestBuildArgvPreservesExplicitEntrypoint(t *testing.T) { + host := &Host{cliEntrypoint: "copilot", args: []string{"--remote"}} + + var argv []string + if err := json.Unmarshal(host.buildArgv(), &argv); err != nil { + t.Fatal(err) + } + + expected := []string{"copilot", "--embedded-host", "--no-auto-update", "--remote"} + if len(argv) != len(expected) { + t.Fatalf("Expected %d arguments, got %d: %v", len(expected), len(argv), argv) + } + for i := range expected { + if argv[i] != expected[i] { + t.Fatalf("Expected argument %d to be %q, got %q", i, expected[i], argv[i]) + } + } +} + +func TestBuildArgvUsesNodeForExplicitJavaScriptEntrypoint(t *testing.T) { + host := &Host{cliEntrypoint: "copilot.js"} + + var argv []string + if err := json.Unmarshal(host.buildArgv(), &argv); err != nil { + t.Fatal(err) + } + + expected := []string{"node", "copilot.js", "--embedded-host", "--no-auto-update"} if len(argv) != len(expected) { t.Fatalf("Expected %d arguments, got %d: %v", len(expected), len(argv), argv) } diff --git a/go/internal/ffihost/resolve.go b/go/internal/ffihost/resolve.go index c8d4052322..5205a01c19 100644 --- a/go/internal/ffihost/resolve.go +++ b/go/internal/ffihost/resolve.go @@ -63,7 +63,8 @@ func PrebuildsFolder() string { // entrypoint. It checks, in order: // // 1. The natural platform library name next to the CLI (bundled/flat layout). -// 2. prebuilds//runtime.node next to the CLI (dev/package layout). +// 2. runtime.node next to the CLI (out-of-process wrapper layout). +// 3. prebuilds//runtime.node next to the CLI (dev/package layout). // // It returns an error when neither exists. func ResolveLibraryPath(cliEntrypoint string) (string, error) { @@ -78,6 +79,11 @@ func ResolveLibraryPath(cliEntrypoint string) (string, error) { return flat, nil } + adjacent := filepath.Join(dir, "runtime.node") + if fileExists(adjacent) { + return adjacent, nil + } + if folder := PrebuildsFolder(); folder != "" { prebuilt := filepath.Join(dir, "prebuilds", folder, "runtime.node") if fileExists(prebuilt) { @@ -86,7 +92,7 @@ func ResolveLibraryPath(cliEntrypoint string) (string, error) { } return "", fmt.Errorf( - "in-process FFI runtime library not found next to %q (looked for %q and prebuilds/%s/runtime.node); "+ + "in-process FFI runtime library not found next to %q (looked for %q, runtime.node, and prebuilds/%s/runtime.node); "+ "use a runtime package that ships the native library", abs, NaturalLibraryName(), PrebuildsFolder()) } diff --git a/go/mode_empty.go b/go/mode_empty.go index 6057b2661f..6e238c58c4 100644 --- a/go/mode_empty.go +++ b/go/mode_empty.go @@ -225,7 +225,8 @@ func (c *Client) applyResumeDefaultsForMode(config *ResumeSessionConfig) { // updateSessionOptionsForMode applies the per-mode safe-defaults patch via // session.options.update after create/resume succeeds. In empty mode the // four overridable feature flags default to safe values; caller values win. -// installedPlugins=[] is unconditional in empty mode. +// installedPlugins=[] is unconditional in empty mode. IncludedBuiltinSkills +// defaults to [] but callers can explicitly allow selected runtime-bundled skills. func (c *Client) updateSessionOptionsForMode(ctx context.Context, session *Session, base optBackInFields) error { patch := &rpc.SessionUpdateOptionsParams{} hasAny := false @@ -255,6 +256,11 @@ func (c *Client) updateSessionOptionsForMode(ctx context.Context, session *Sessi patch.ManageScheduleEnabled = &f } patch.InstalledPlugins = []rpc.SessionInstalledPlugin{} + if base.IncludedBuiltinSkills != nil { + patch.IncludedBuiltinSkills = base.IncludedBuiltinSkills + } else { + patch.IncludedBuiltinSkills = []string{} + } hasAny = true } else { if base.SkipCustomInstructions != nil { @@ -273,6 +279,10 @@ func (c *Client) updateSessionOptionsForMode(ctx context.Context, session *Sessi patch.ManageScheduleEnabled = base.ManageScheduleEnabled hasAny = true } + if base.IncludedBuiltinSkills != nil { + patch.IncludedBuiltinSkills = base.IncludedBuiltinSkills + hasAny = true + } } if !hasAny { return nil @@ -297,4 +307,5 @@ type optBackInFields struct { CustomAgentsLocalOnly *bool CoauthorEnabled *bool ManageScheduleEnabled *bool + IncludedBuiltinSkills []string } diff --git a/go/permission_context_test.go b/go/permission_context_test.go new file mode 100644 index 0000000000..16c6d2d592 --- /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 24b9cc7f13..f27f9b6e62 100644 --- a/go/permissions.go +++ b/go/permissions.go @@ -6,6 +6,62 @@ import ( "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 permission requests when managed settings are disabled. diff --git a/go/rpc/generated_rpc_api_shape_test.go b/go/rpc/generated_rpc_api_shape_test.go index a6b357d54f..05774f4847 100644 --- a/go/rpc/generated_rpc_api_shape_test.go +++ b/go/rpc/generated_rpc_api_shape_test.go @@ -35,9 +35,9 @@ func TestGeneratedRPCAPIShape(t *testing.T) { assertTypeExpr(t, fileSet, findTypeSpec(t, file, "FilterMappingEnumMap").Type, "map[string]ContentFilterMode") assertInterfaceType(t, file, "MCPServerConfig") - assertStructFieldType(t, file, fileSet, "MCPConfigAddRequest", "Config", "MCPServerConfig") - assertStructFieldType(t, file, fileSet, "MCPConfigList", "Servers", "map[string]MCPServerConfig") - assertStructFieldType(t, file, fileSet, "MCPConfigUpdateRequest", "Config", "MCPServerConfig") + assertStructFieldType(t, file, fileSet, "MCPConfigAddRequest", "Config", "MCPSerializableServerConfig") + assertStructFieldType(t, file, fileSet, "MCPConfigList", "Servers", "map[string]MCPSerializableServerConfig") + assertStructFieldType(t, file, fileSet, "MCPConfigUpdateRequest", "Config", "MCPSerializableServerConfig") assertStructFieldType(t, file, fileSet, "MCPServerConfigHTTP", "FilterMapping", "FilterMapping") assertStructFieldType(t, file, fileSet, "MCPServerConfigStdio", "FilterMapping", "FilterMapping") diff --git a/go/rpc/permission_request_managed_approval.go b/go/rpc/permission_request_managed_approval.go index 0206268931..d1bfa37356 100644 --- a/go/rpc/permission_request_managed_approval.go +++ b/go/rpc/permission_request_managed_approval.go @@ -20,6 +20,12 @@ func (r PermissionRequestExtensionManagement) RequiresManagedApproval() bool { return managedApprovalRequired(r.ManagedApprovalRequired) } +// RequiresManagedApproval reports whether managed policy requires an explicit +// human decision for this request. +func (r PermissionRequestExtensionEnvAccess) RequiresManagedApproval() bool { + return managedApprovalRequired(r.ManagedApprovalRequired) +} + // RequiresManagedApproval reports whether managed policy requires an explicit // human decision for this request. func (r PermissionRequestExtensionPermissionAccess) RequiresManagedApproval() bool { diff --git a/go/rpc/zrpc.go b/go/rpc/zrpc.go index 5ecee9f6cb..5b4cdab32d 100644 --- a/go/rpc/zrpc.go +++ b/go/rpc/zrpc.go @@ -28,12 +28,13 @@ type AbortResult struct { Success bool `json:"success"` } -// Authenticated account entry returned by `account.getAllUsers`, with auth info and an -// optional associated token. +// Authenticated account entry returned by `account.getAllUsers`. // Experimental: AccountAllUsers is part of an experimental API and may change or be removed. type AccountAllUsers struct { // Authentication information for this user AuthInfo AuthInfo `json:"authInfo"` + // Opaque identifier accepted by account and model selection APIs + SelectionID *string `json:"selectionId,omitempty"` // Associated token, if available Token *string `json:"token,omitempty"` } @@ -56,9 +57,12 @@ type AccountGetCurrentAuthResult struct { // Experimental: AccountGetQuotaRequest is part of an experimental API and may change or be // removed. type AccountGetQuotaRequest struct { - // GitHub token for per-user quota lookup. When provided, resolves this token to determine - // the user's quota instead of using the global auth. + // GitHub token accepted for compatibility with existing SDK clients. When provided, + // resolves this token instead of using the current account. GitHubToken *string `json:"gitHubToken,omitempty"` + // Opaque account identifier returned by `account.getAllUsers`. When omitted, the current + // account is used. + SelectionID *string `json:"selectionId,omitempty"` } // Quota usage snapshots for the resolved user, keyed by quota type. @@ -69,14 +73,16 @@ type AccountGetQuotaResult struct { QuotaSnapshots map[string]AccountQuotaSnapshot `json:"quotaSnapshots"` } -// Credentials to store after successful authentication +// Credentials to validate and store. Omit login to resolve the authenticated user from the +// token. // Experimental: AccountLoginRequest is part of an experimental API and may change or be // removed. type AccountLoginRequest struct { // GitHub host URL Host string `json:"host"` - // User login/username - Login string `json:"login"` + // User login/username. When omitted, the runtime validates the token and resolves the login + // from GitHub. + Login *string `json:"login,omitempty"` // GitHub authentication token Token string `json:"token"` } @@ -96,7 +102,9 @@ type AccountLoginResult struct { // removed. type AccountLogoutRequest struct { // Authentication information for the user to log out - AuthInfo AuthInfo `json:"authInfo"` + AuthInfo AuthInfo `json:"authInfo,omitempty"` + // Opaque account identifier returned by `account.getAllUsers` + SelectionID *string `json:"selectionId,omitempty"` } // Logout result indicating if more users remain @@ -162,7 +170,7 @@ type AgentGetCurrentResult struct { Agent *AgentInfo `json:"agent,omitempty"` } -// Agent metadata, including identifiers, display details, source, tools, model, MCP +// Agent metadata, including identifiers, display details, source, tools, model, models, MCP // servers, skills, and file path. // Experimental: AgentInfo is part of an experimental API and may change or be removed. type AgentInfo struct { @@ -181,6 +189,11 @@ type AgentInfo struct { // 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"` + // Whether authored models are preferences or required constraints. + ModelPolicy *AgentModelPolicy `json:"modelPolicy,omitempty"` + // Authored preferred model ids for this agent, in priority order. Runtime model selection + // chooses the first available model; omitted means no authored preference. + Models []string `json:"models,omitzero"` // 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 @@ -455,28 +468,6 @@ type AgentsGetDiscoveryPathsRequest struct { ProjectPaths []string `json:"projectPaths,omitzero"` } -// Indicates whether the operation succeeded and reports the post-mutation state. -// Experimental: AllowAllPermissionSetResult is part of an experimental API and may change -// or be removed. -type AllowAllPermissionSetResult struct { - // Authoritative full allow-all state after the mutation - Enabled bool `json:"enabled"` - // Authoritative allow-all mode after the mutation - Mode *PermissionsAllowAllMode `json:"mode,omitempty"` - // Whether the operation succeeded - Success bool `json:"success"` -} - -// Current allow-all permission mode. -// Experimental: AllowAllPermissionState is part of an experimental API and may change or be -// removed. -type AllowAllPermissionState struct { - // Whether full allow-all permissions are currently active - Enabled bool `json:"enabled"` - // Current allow-all mode - Mode *PermissionsAllowAllMode `json:"mode,omitempty"` -} - // A user message attachment — a file, directory, code selection, blob, GitHub-anchored // pointer, or extension-supplied context payload // Experimental: Attachment is part of an experimental API and may change or be removed. @@ -869,7 +860,26 @@ type AttachmentSelectionDetailsStart struct { Line int64 `json:"line"` } -// Initial authentication info for the session. +// Credential-free authentication identity safe to expose to hosts and user interfaces. +// Experimental: AuthIdentity is part of an experimental API and may change or be removed. +type AuthIdentity struct { + // Snapshot of the authenticated user's Copilot subscription info, if known + CopilotUser *CopilotUserResponse `json:"copilotUser,omitempty"` + // Name of the environment variable that supplied the credential, when applicable + EnvVar *string `json:"envVar,omitempty"` + // Authentication host + Host string `json:"host"` + // Authenticated login, when available + Login *string `json:"login,omitempty"` + // Opaque SDK GitHub credential registration backing this identity. Routing metadata only; + // never a credential. + RegistrationID *string `json:"registrationId,omitempty"` + // Authentication type + Type AuthInfoType `json:"type"` +} + +// Authentication credentials accepted only at native protocol ingress. Runtime outputs use +// credential-free `AuthIdentity` metadata. // Experimental: AuthInfo is part of an experimental API and may change or be removed. type AuthInfo interface { authInfo() @@ -886,8 +896,8 @@ func (r RawAuthInfoData) Type() AuthInfoType { return r.Discriminator } -// Authentication-info variant for API-key authentication to a non-GitHub LLM provider, -// carrying the secret `apiKey` and host. +// Authentication-info input variant for API-key authentication to a non-GitHub LLM +// provider, carrying the secret `apiKey` and host. // Experimental: APIKeyAuthInfo is part of an experimental API and may change or be removed. type APIKeyAuthInfo struct { // The API key. Treat as a secret. @@ -923,8 +933,8 @@ func (CopilotAPITokenAuthInfo) Type() AuthInfoType { return AuthInfoTypeCopilotAPIToken } -// Authentication-info variant for a token sourced from an environment variable, with host, -// optional login, token, and env var name. +// Authentication-info input variant for a token sourced from an environment variable, with +// host, optional login, token, and env var name. // Experimental: EnvAuthInfo is part of an experimental API and may change or be removed. type EnvAuthInfo struct { // Snapshot of the authenticated user's Copilot subscription info, if known. Mirrors the @@ -947,8 +957,8 @@ func (EnvAuthInfo) Type() AuthInfoType { return AuthInfoTypeEnv } -// Authentication-info variant for GitHub CLI credentials, carrying host, login, and the `gh -// auth token` value. +// Authentication-info input variant for GitHub CLI credentials, carrying host, login, and +// the `gh auth token` value. // Experimental: GhCLIAuthInfo is part of an experimental API and may change or be removed. type GhCLIAuthInfo struct { // Snapshot of the authenticated user's Copilot subscription info, if known. Mirrors the @@ -968,8 +978,8 @@ func (GhCLIAuthInfo) Type() AuthInfoType { return AuthInfoTypeGhCLI } -// Authentication-info variant for GitHub-internal HMAC auth, carrying the public GitHub -// host and HMAC secret. +// Authentication-info input variant for GitHub-internal HMAC auth, carrying the public +// GitHub host and HMAC secret. // Experimental: HMACAuthInfo is part of an experimental API and may change or be removed. type HMACAuthInfo struct { // Snapshot of the authenticated user's Copilot subscription info, if known. Mirrors the @@ -987,8 +997,8 @@ func (HMACAuthInfo) Type() AuthInfoType { return AuthInfoTypeHMAC } -// Authentication-info variant for SDK-configured token authentication, carrying host and -// the secret token value. +// Authentication-info input variant for SDK-configured token authentication, carrying host +// and the secret token value. // Experimental: TokenAuthInfo is part of an experimental API and may change or be removed. type TokenAuthInfo struct { // Snapshot of the authenticated user's Copilot subscription info, if known. Mirrors the @@ -997,6 +1007,8 @@ type TokenAuthInfo struct { CopilotUser *CopilotUserResponse `json:"copilotUser,omitempty"` // Authentication host. Host string `json:"host"` + // Opaque native GitHub credential registration backing this token identity, when applicable. + RegistrationID *string `json:"registrationId,omitempty"` // The token value itself. Treat as a secret. Token string `json:"token"` } @@ -1006,6 +1018,24 @@ func (TokenAuthInfo) Type() AuthInfoType { return AuthInfoTypeToken } +// Authentication-info variant backed by an SDK GitHub token callback. It carries routing +// metadata but never a plaintext token. +// Experimental: TokenProviderAuthInfo is part of an experimental API and may change or be +// removed. +type TokenProviderAuthInfo struct { + // Snapshot of the authenticated user's Copilot subscription info, if known. + CopilotUser *CopilotUserResponse `json:"copilotUser,omitempty"` + // Authentication host. + Host string `json:"host"` + // Opaque SDK callback registration identifier. + RegistrationID string `json:"registrationId"` +} + +func (TokenProviderAuthInfo) authInfo() {} +func (TokenProviderAuthInfo) Type() AuthInfoType { + return AuthInfoTypeTokenProvider +} + // Authentication-info variant for OAuth user auth, with host and login; the token remains // in the runtime secret store. // Experimental: UserAuthInfo is part of an experimental API and may change or be removed. @@ -1025,6 +1055,21 @@ func (UserAuthInfo) Type() AuthInfoType { return AuthInfoTypeUser } +// Validation error from an authentication attempt. +// Experimental: AuthValidationError is part of an experimental API and may change or be +// removed. +type AuthValidationError struct { + // Optional message returned by GitHub + GitHubMessage *string `json:"githubMessage,omitempty"` + // Authentication validation error message + Message string `json:"message"` +} + +// Validation errors from the most recent authentication attempt. +// Experimental: AuthValidationErrors is part of an experimental API and may change or be +// removed. +type AuthValidationErrors []AuthValidationError + // 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 @@ -1038,12 +1083,81 @@ type BuiltInModelCatalog struct { // 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. + // Well-known runtime model ID suitable for provider or provider-model metadata. This is not + // necessarily the provider-facing deployment or model name and does not indicate CAPI + // entitlement or provider availability. ID string `json:"id"` } +// Rust-owned metadata and input schema for a built-in tool. +// Experimental: BuiltinToolDescriptor is part of an experimental API and may change or be +// removed. +type BuiltinToolDescriptor struct { + // Model-facing description of the tool's behavior. + Description string `json:"description"` + // Optional custom input format used instead of a JSON Schema. + Format *BuiltinToolFormat `json:"format"` + // Whether the tool provides a specialized intention summary. + HasSummariseIntention bool `json:"hasSummariseIntention"` + // JSON Schema for the tool input, or null when the tool uses a custom format. + InputSchema *BuiltinToolInputSchema `json:"inputSchema"` + // Optional supplemental usage instructions for the tool. + Instructions *string `json:"instructions"` + // Whether the tool executes commands in a terminal. + IsTerminal bool `json:"isTerminal"` + // Stable name used to invoke the built-in tool. + Name string `json:"name"` + // Policy describing which tool metadata may be recorded without obfuscation. + SafeForTelemetry BuiltinToolSafeForTelemetry `json:"safeForTelemetry"` + // Optional human-readable title for the tool. + Title *string `json:"title"` + // Optional tool category discriminator. + Type *string `json:"type"` +} + +// Custom grammar input format accepted by a built-in tool. +// Experimental: BuiltinToolFormat is part of an experimental API and may change or be +// removed. +type BuiltinToolFormat struct { + // Grammar definition accepted by the tool. + Definition string `json:"definition"` + // Grammar syntax used by the format definition. + Syntax string `json:"syntax"` + // Custom input-format discriminator. + Type BuiltinToolFormatType `json:"type"` +} + +// JSON Schema object accepted by a built-in tool. +// Experimental: BuiltinToolInputSchema is part of an experimental API and may change or be +// removed. +type BuiltinToolInputSchema struct { + // Root type of the tool input schema. + Type BuiltinToolInputSchemaType `json:"type"` +} + +// Telemetry-safety policy for a built-in tool. +// Experimental: BuiltinToolSafeForTelemetry is part of an experimental API and may change +// or be removed. +type BuiltinToolSafeForTelemetry interface { + builtinToolSafeForTelemetry() +} + +type BuiltinToolSafeForTelemetryBoolean bool + +func (BuiltinToolSafeForTelemetryBoolean) builtinToolSafeForTelemetry() {} + +func (BuiltinToolSafeTelemetryFields) builtinToolSafeForTelemetry() {} + +// Per-field telemetry-safety policy for a built-in tool. +// Experimental: BuiltinToolSafeTelemetryFields is part of an experimental API and may +// change or be removed. +type BuiltinToolSafeTelemetryFields struct { + // Whether tool input names may be included in telemetry without obfuscation. + InputsNames *bool `json:"inputsNames,omitempty"` + // Whether the tool name may be included in telemetry without obfuscation. + Name *bool `json:"name,omitempty"` +} + // Cancellation result for a user-requested shell command. // Experimental: CancelUserRequestedShellCommandResult is part of an experimental API and // may change or be removed. @@ -1220,6 +1334,26 @@ type CanvasProviderOpenResult struct { URL *string `json:"url,omitempty"` } +// Internal canvas provider registration parameters. +// Experimental: CanvasProviderRegisterRequest is part of an experimental API and may change +// or be removed. +type CanvasProviderRegisterRequest struct { + // Canvas contributions supplied by the provider + Canvases []any `json:"canvases"` + // Connection identifier for callback routing + ConnectionID string `json:"connectionId"` + // Provider metadata supplied by the host + Info any `json:"info"` +} + +// Internal canvas provider unregistration parameters. +// Experimental: CanvasProviderUnregisterRequest is part of an experimental API and may +// change or be removed. +type CanvasProviderUnregisterRequest struct { + // Connection identifier to unregister + ConnectionID string `json:"connectionId"` +} + // Session context supplied by the runtime. // Experimental: CanvasSessionContext is part of an experimental API and may change or be // removed. @@ -1232,6 +1366,10 @@ type CanvasSessionContext struct { // Experimental: CapiSessionOptions is part of an experimental API and may change or be // removed. type CapiSessionOptions struct { + // Routing preference used when the session model is `auto`. The runtime persists the + // preference across cold resume. When omitted, the default routing behavior is used. + // Resuming an already-resident session cannot change its preference. + AutoTier *AutoTier `json:"autoTier,omitempty"` // 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 @@ -1240,6 +1378,478 @@ type CapiSessionOptions struct { EnableWebSocketResponses *bool `json:"enableWebSocketResponses,omitempty"` } +// Semantic digest of a strictly parsed and schema-validated JSON MCP card. Both URL-backed +// and embedded cards are canonicalised with RFC 8785 JSON Canonicalization Scheme, encoded +// as UTF-8, and hashed with SHA-256. +// Experimental: CardDigest is part of an experimental API and may change or be removed. +type CardDigest struct { + // Digest algorithm and canonical representation + Algorithm CardDigestAlgorithm `json:"algorithm"` + // SHA-256 digest of the RFC 8785 canonical UTF-8 bytes, encoded as exactly 64 lowercase + // hexadecimal characters. + Value string `json:"value"` +} + +// SHA-256 digest encoded as exactly 64 lowercase hexadecimal characters. +// Experimental: CardDigestValue is part of an experimental API and may change or be removed. +type CardDigestValue string + +// Where and when an AI skill catalog reference was observed. Discovery provenance +// deliberately carries no content digest because search does not establish the exact +// validated content a later plan will bind. +// Experimental: CatalogAiSkillCandidateProvenance is part of an experimental API and may +// change or be removed. +type CatalogAiSkillCandidateProvenance struct { + // Host of the catalog authority that advertised the reference, without path, query, or + // credentials. Inert untrusted data. + Authority string `json:"authority"` + // Media type advertised for the referenced AI skill card + MediaType CatalogAiSkillCandidateProvenanceMediaType `json:"mediaType"` + // ISO 8601 timestamp at which the runtime observed the catalog reference. This is not a + // retrieval or validation timestamp. + ObservedAt string `json:"observedAt"` +} + +// One inert catalog result, represented as an MCP server or discovery-only AI skill variant +// so kind, media type, provenance, and installability cannot contradict each other. +// Experimental: CatalogCandidate is part of an experimental API and may change or be +// removed. +type CatalogCandidate interface { + catalogCandidate() + Kind() CatalogCandidateKind +} + +type RawCatalogCandidateData struct { + Discriminator CatalogCandidateKind + Raw json.RawMessage +} + +func (RawCatalogCandidateData) catalogCandidate() {} +func (r RawCatalogCandidateData) Kind() CatalogCandidateKind { + return r.Discriminator +} + +// An inert AI skill catalog result. AI skills are discovery-only and cannot be represented +// as installable through this surface. +// Experimental: CatalogAiSkillCandidate is part of an experimental API and may change or be +// removed. +type CatalogAiSkillCandidate struct { + // Description taken verbatim from the card. Inert untrusted text. + Description *string `json:"description,omitempty"` + // Display name taken verbatim from the card. Inert untrusted text. + DisplayName string `json:"displayName"` + // Opaque, runtime-instance scoped, TTL-bound, single-use handle for this candidate. Carries + // no readable information and is rejected when stale, replayed, or presented to a different + // runtime instance. Never logged. + Handle string `json:"handle"` + // ISO 8601 timestamp after which the handle is stale and will be rejected. + HandleExpiresAt string `json:"handleExpiresAt"` + // AI skills are discovery-only and cannot be installed through this surface + Installability CatalogAiSkillCandidateInstallability `json:"installability"` + // Media type of the underlying AI skill card + MediaType CatalogAiSkillCandidateMediaType `json:"mediaType"` + // Where the catalog reference was observed, without the card itself or any content digest. + Provenance CatalogAiSkillCandidateProvenance `json:"provenance"` + // Publisher taken verbatim from the card. Inert untrusted text. + Publisher *string `json:"publisher,omitempty"` + // Where the card came from: exactly one of a URL or embedded data, encoded as a tagged + // union so neither both nor neither can be represented. + Source CatalogCandidateSource `json:"source"` +} + +func (CatalogAiSkillCandidate) catalogCandidate() {} +func (CatalogAiSkillCandidate) Kind() CatalogCandidateKind { + return CatalogCandidateKindAiSkill +} + +// An inert MCP server catalog result. Every free-text field is untrusted external data and +// must never be treated as an instruction, and the handle is the only way to refer to the +// candidate in a later operation. +// Experimental: CatalogMCPServerCandidate is part of an experimental API and may change or +// be removed. +type CatalogMCPServerCandidate struct { + // Description taken verbatim from the card. Inert untrusted text. + Description *string `json:"description,omitempty"` + // Display name taken verbatim from the card. Inert untrusted text. + DisplayName string `json:"displayName"` + // Opaque, runtime-instance scoped, TTL-bound, single-use handle for this candidate. Carries + // no readable information and is rejected when stale, replayed, or presented to a different + // runtime instance. Never logged. + Handle string `json:"handle"` + // ISO 8601 timestamp after which the handle is stale and will be rejected. + HandleExpiresAt string `json:"handleExpiresAt"` + // Whether this MCP server can be planned for installation, and if policy prevents it. + Installability CatalogMCPServerInstallability `json:"installability"` + // JSON MCP media type of the underlying card. + MediaType MCPServerCardMediaType `json:"mediaType"` + // Where the catalog reference was observed, without the card itself or any content digest. + Provenance CatalogMCPServerCandidateProvenance `json:"provenance"` + // Publisher taken verbatim from the card. Inert untrusted text. + Publisher *string `json:"publisher,omitempty"` + // Where the card came from: exactly one of a URL or embedded data, encoded as a tagged + // union so neither both nor neither can be represented. + Source CatalogCandidateSource `json:"source"` +} + +func (CatalogMCPServerCandidate) catalogCandidate() {} +func (CatalogMCPServerCandidate) Kind() CatalogCandidateKind { + return CatalogCandidateKindMCPServer +} + +// Where a candidate's card came from. Exactly one of a URL or embedded data: the union has +// no variant carrying both, and no variant carrying neither, so the rule holds structurally +// rather than by validation. +// Experimental: CatalogCandidateSource is part of an experimental API and may change or be +// removed. +type CatalogCandidateSource interface { + catalogCandidateSource() + Kind() CatalogCandidateSourceKind +} + +type RawCatalogCandidateSourceData struct { + Discriminator CatalogCandidateSourceKind + Raw json.RawMessage +} + +func (RawCatalogCandidateSourceData) catalogCandidateSource() {} +func (r RawCatalogCandidateSourceData) Kind() CatalogCandidateSourceKind { + return r.Discriminator +} + +// Candidate whose card reference arrived inline. The document and its content-derived +// properties stay behind the runtime boundary. +// Experimental: CatalogCandidateSourceEmbedded is part of an experimental API and may +// change or be removed. +type CatalogCandidateSourceEmbedded struct { +} + +func (CatalogCandidateSourceEmbedded) catalogCandidateSource() {} +func (CatalogCandidateSourceEmbedded) Kind() CatalogCandidateSourceKind { + return CatalogCandidateSourceKindEmbedded +} + +// Candidate whose card is retrieved from a URL through the runtime's hardened fetch +// boundary. +// Experimental: CatalogCandidateSourceURL is part of an experimental API and may change or +// be removed. +type CatalogCandidateSourceURL struct { + // Card URL as advertised. Inert untrusted data: the runtime retrieves it only through its + // own hardened boundary, and it is never logged. + URL string `json:"url"` +} + +func (CatalogCandidateSourceURL) catalogCandidateSource() {} +func (CatalogCandidateSourceURL) Kind() CatalogCandidateSourceKind { + return CatalogCandidateSourceKindURL +} + +// Bounded extensible wire-feature identifier. Known values are described by +// `CatalogCapability`; newer callers may send future identifiers so an older runtime can +// return a typed negotiation refusal instead of failing schema validation. Capability +// negotiation establishes contract understanding, while each operation's result separately +// reports runtime availability. +// Experimental: CatalogCapabilityID is part of an experimental API and may change or be +// removed. +type CatalogCapabilityID string + +// The protocol version and capability set a caller requires, supplied on every catalog +// request so negotiation cannot be skipped by omission. +// Experimental: CatalogClientContract is part of an experimental API and may change or be +// removed. +type CatalogClientContract struct { + // SDK protocol version the caller was generated against. A caller below the runtime's + // minimum supported version is refused rather than served a partial result. + ProtocolVersion int64 `json:"protocolVersion"` + // Wire features the caller requires the runtime to understand. Identifiers are bounded but + // extensible so a newer caller can negotiate with an older runtime. Requiring an unknown + // feature yields a typed refusal listing what is understood, never a partial grant. A grant + // does not promise that a deployment has enabled the operation; typed unavailable results + // report that separately. + RequiredCapabilities []string `json:"requiredCapabilities"` +} + +// Where and when an MCP server catalog reference was observed. Discovery provenance +// deliberately carries no content digest because search does not establish the exact +// validated content a later plan will bind. +// Experimental: CatalogMCPServerCandidateProvenance is part of an experimental API and may +// change or be removed. +type CatalogMCPServerCandidateProvenance struct { + // Host of the catalog authority that advertised the reference, without path, query, or + // credentials. Inert untrusted data. + Authority string `json:"authority"` + // JSON MCP media type advertised for the referenced card. + MediaType MCPServerCardMediaType `json:"mediaType"` + // ISO 8601 timestamp at which the runtime observed the catalog reference. This is not a + // retrieval or validation timestamp. + ObservedAt string `json:"observedAt"` +} + +// The protocol version and capability set the runtime actually honoured for a successful +// catalog operation. +// Experimental: CatalogNegotiatedContract is part of an experimental API and may change or +// be removed. +type CatalogNegotiatedContract struct { + // Wire features the runtime understood for this operation. Always a superset of the + // caller's required features, because any shortfall is a refusal instead. Operation + // availability remains a separate typed result. + GrantedCapabilities []CatalogCapability `json:"grantedCapabilities"` + // Protocol version of the runtime that served the request. + RuntimeProtocolVersion int64 `json:"runtimeProtocolVersion"` +} + +// A bounded catalog search. Both the query length and the result count are capped by the +// schema so a caller cannot request an unbounded scan. +// Experimental: CatalogSearchRequest is part of an experimental API and may change or be +// removed. +type CatalogSearchRequest struct { + // Protocol version and capabilities the caller requires. + Contract CatalogClientContract `json:"contract"` + // Restrict results to these candidate kinds. When omitted, every kind the runtime supports + // is searched. + Kinds []CatalogCandidateKind `json:"kinds,omitzero"` + // Maximum number of candidates to return. Defaults to 10 when omitted. + Limit *int32 `json:"limit,omitempty"` + // Free-text search query. Persisted as tool input for session continuity, but omitted from + // telemetry. + Query string `json:"query"` +} + +// Outcome of a catalog.search call: either bounded inert candidates, or one typed refusal. +// Never a partial success. +// Experimental: CatalogSearchResult is part of an experimental API and may change or be +// removed. +type CatalogSearchResult interface { + catalogSearchResult() + Kind() CatalogSearchResultKind +} + +type RawCatalogSearchResultData struct { + Discriminator CatalogSearchResultKind + Raw json.RawMessage +} + +func (RawCatalogSearchResultData) catalogSearchResult() {} +func (r RawCatalogSearchResultData) Kind() CatalogSearchResultKind { + return r.Discriminator +} + +// An optional catalog authentication exchange did not establish the caller's identity. +// Anonymous search remains supported; this refusal is reserved for an operation that cannot +// continue after the attempted exchange. It is distinct from `policy-rejected` and from a +// network failure, and the reason identifies the recovery action. +// Experimental: CatalogAuthenticationRequiredError is part of an experimental API and may +// change or be removed. +type CatalogAuthenticationRequiredError struct { + // Human-readable explanation, safe to surface. Never contains a credential or token, nor a + // query, URL, handle, or secret. + Message string `json:"message"` + // Why authentication failed. Only an expired credential justifies attempting a silent + // refresh; an absent or rejected credential requires sign-in. + Reason CatalogAuthenticationRequiredReason `json:"reason"` +} + +func (CatalogAuthenticationRequiredError) catalogSearchResult() {} +func (CatalogAuthenticationRequiredError) Kind() CatalogSearchResultKind { + return CatalogSearchResultKindAuthenticationRequired +} + +// An upstream catalog response broke the wire contract. Most importantly, every result must +// carry exactly one of a URL or embedded data: a result carrying both, or neither, is +// refused here rather than being guessed at. +// Experimental: CatalogContractViolationError is part of an experimental API and may change +// or be removed. +type CatalogContractViolationError struct { + // Human-readable explanation, safe to surface. Never echoes response content, nor a query, + // URL, handle, or secret. + Message string `json:"message"` + // Which rule the response broke. + Reason CatalogContractViolationReason `json:"reason"` +} + +func (CatalogContractViolationError) catalogSearchResult() {} +func (CatalogContractViolationError) Kind() CatalogSearchResultKind { + return CatalogSearchResultKindContractViolation +} + +// The request was rejected before any work was done, because a bounded field fell outside +// its permitted range or a required field was unusable. +// Experimental: CatalogInvalidRequestError is part of an experimental API and may change or +// be removed. +type CatalogInvalidRequestError struct { + // Which request field was rejected. + Field CatalogInvalidRequestField `json:"field"` + // Human-readable explanation, safe to surface. Never echoes the offending value, nor a + // query, URL, handle, or secret. + Message string `json:"message"` +} + +func (CatalogInvalidRequestError) catalogSearchResult() {} +func (CatalogInvalidRequestError) Kind() CatalogSearchResultKind { + return CatalogSearchResultKindInvalidRequest +} + +// A card could not be parsed or did not satisfy its declared media type's schema. +// Experimental: CatalogMalformedCardError is part of an experimental API and may change or +// be removed. +type CatalogMalformedCardError struct { + // Media type the card was interpreted as, when it declared one this runtime recognises. + MediaType *CatalogMediaType `json:"mediaType,omitempty"` + // Human-readable explanation, safe to surface. Never echoes card content, nor a query, URL, + // handle, or secret. + Message string `json:"message"` + // How the card failed validation. + Reason CatalogMalformedCardReason `json:"reason"` +} + +func (CatalogMalformedCardError) catalogSearchResult() {} +func (CatalogMalformedCardError) Kind() CatalogSearchResultKind { + return CatalogSearchResultKindMalformedCard +} + +// The caller's protocol version or required capabilities cannot be honoured. Returned +// instead of a partial or ambiguous success. +// Experimental: CatalogNegotiationRefusedError is part of an experimental API and may +// change or be removed. +type CatalogNegotiationRefusedError struct { + // Human-readable explanation, safe to surface. Never contains a query, URL, handle, or + // secret. + Message string `json:"message"` + // Lowest caller protocol version this runtime will serve. + MinimumSupportedProtocolVersion int64 `json:"minimumSupportedProtocolVersion"` + // Whether the version or the capability set was the problem. + Reason CatalogNegotiationRefusedReason `json:"reason"` + // Protocol version of the runtime that refused the request. + RuntimeProtocolVersion int64 `json:"runtimeProtocolVersion"` + // Every wire feature this runtime understands, so the caller can retry within that + // contract. This list does not imply that every deployment has enabled every operation. + SupportedCapabilities []CatalogCapability `json:"supportedCapabilities"` + // The subset of the caller's bounded extensible capability identifiers this runtime cannot + // honour. + UnsupportedCapabilities []string `json:"unsupportedCapabilities"` +} + +func (CatalogNegotiationRefusedError) catalogSearchResult() {} +func (CatalogNegotiationRefusedError) Kind() CatalogSearchResultKind { + return CatalogSearchResultKindNegotiationRefused +} + +// The runtime could not reach the catalog authority or retrieve a card. Covers being +// offline as well as transport-level failure. +// Experimental: CatalogNetworkFailureError is part of an experimental API and may change or +// be removed. +type CatalogNetworkFailureError struct { + // Human-readable explanation, safe to surface. Never contains a query, URL, handle, or + // secret. + Message string `json:"message"` + // Categorised failure, low cardinality so it can be aggregated without carrying a URL. + Reason CatalogNetworkFailureReason `json:"reason"` + // Bounded cooldown in seconds before another catalog request should be attempted, when the + // authority supplied a numeric Retry-After value or the runtime applied its documented + // fallback. + RetryAfterSeconds *int32 `json:"retryAfterSeconds,omitempty"` + // HTTP status code, when the failure was a rejected response. + StatusCode *int32 `json:"statusCode,omitempty"` +} + +func (CatalogNetworkFailureError) catalogSearchResult() {} +func (CatalogNetworkFailureError) Kind() CatalogSearchResultKind { + return CatalogSearchResultKindNetworkFailure +} + +// Registry or enterprise policy refused the operation. +// Experimental: CatalogPolicyRejectedError is part of an experimental API and may change or +// be removed. +type CatalogPolicyRejectedError struct { + // Human-readable explanation, safe to surface. Never contains a query, URL, handle, or + // secret. + Message string `json:"message"` + // Which authority produced the decision. + Source MCPPlanPolicySource `json:"source"` +} + +func (CatalogPolicyRejectedError) catalogSearchResult() {} +func (CatalogPolicyRejectedError) Kind() CatalogSearchResultKind { + return CatalogSearchResultKindPolicyRejected +} + +// A completed catalog search: inert candidate summaries, each carrying a single-use handle. +// Experimental: CatalogSearchSucceeded is part of an experimental API and may change or be +// removed. +type CatalogSearchSucceeded struct { + // Matching candidates, never more than the requested limit. All text is inert untrusted + // data. + Candidates []CatalogCandidate `json:"candidates"` + // Protocol version and capabilities the runtime honoured. + Negotiated CatalogNegotiatedContract `json:"negotiated"` + // Pseudonymous identifier for this search, issued by the runtime or by the catalog + // authority it queried and never by the caller, so it cannot be forged or replayed to + // attribute an install to a search that never happened. Always present on a success, so a + // result set can be tied to the installs it leads to. It identifies a search rather than a + // person: it is derived from no user, account, device, or query data, and must never be + // joined with user identity to re-identify anyone. + SearchID string `json:"searchId"` + // Whether further matches existed beyond the requested limit. + Truncated bool `json:"truncated"` +} + +func (CatalogSearchSucceeded) catalogSearchResult() {} +func (CatalogSearchSucceeded) Kind() CatalogSearchResultKind { + return CatalogSearchResultKindSucceeded +} + +// The operation is not available on this runtime. Distinct from a network failure: nothing +// was attempted. +// Experimental: CatalogUnavailableError is part of an experimental API and may change or be +// removed. +type CatalogUnavailableError struct { + // Human-readable explanation, safe to surface. Never contains a query, URL, handle, or + // secret. + Message string `json:"message"` + // Why the operation is unavailable. + Reason CatalogUnavailableReason `json:"reason"` +} + +func (CatalogUnavailableError) catalogSearchResult() {} +func (CatalogUnavailableError) Kind() CatalogSearchResultKind { + return CatalogSearchResultKindUnavailable +} + +// Retrieval was refused by the runtime's hardened fetch boundary before any request left +// the process, or before a redirect was followed. +// Experimental: CatalogUnsafeRetrievalError is part of an experimental API and may change +// or be removed. +type CatalogUnsafeRetrievalError struct { + // Human-readable explanation, safe to surface. Never contains the refused URL, nor a query, + // handle, or secret. + Message string `json:"message"` + // Which control refused the retrieval, low cardinality so it can be aggregated without + // carrying a URL. + Reason CatalogUnsafeRetrievalReason `json:"reason"` +} + +func (CatalogUnsafeRetrievalError) catalogSearchResult() {} +func (CatalogUnsafeRetrievalError) Kind() CatalogSearchResultKind { + return CatalogSearchResultKindUnsafeRetrieval +} + +// The request asked for a candidate kind this runtime does not serve. +// Experimental: CatalogUnsupportedKindError is part of an experimental API and may change +// or be removed. +type CatalogUnsupportedKindError struct { + // Human-readable explanation, safe to surface. Never contains a query, URL, handle, or + // secret. + Message string `json:"message"` + // The kinds from the request that are not supported. + RequestedKinds []CatalogCandidateKind `json:"requestedKinds"` + // Every candidate kind this runtime can serve. + SupportedKinds []CatalogCandidateKind `json:"supportedKinds"` +} + +func (CatalogUnsupportedKindError) catalogSearchResult() {} +func (CatalogUnsupportedKindError) Kind() CatalogSearchResultKind { + return CatalogSearchResultKindUnsupportedKind +} + // Slash commands available in the session, after applying any include/exclude filters. // Experimental: CommandList is part of an experimental API and may change or be removed. type CommandList struct { @@ -1247,6 +1857,28 @@ type CommandList struct { Commands []SlashCommandInfo `json:"commands"` } +// The pending slash-command invocation effect to finalize, plus whether the host applied or +// cancelled it. +// Experimental: CommandsFinalizeInvocationEffectRequest is part of an experimental API and +// may change or be removed. +type CommandsFinalizeInvocationEffectRequest struct { + // The slash-command result object that produced the pending effect, echoed back unchanged. + Effect any `json:"effect"` + // Whether the host applied or cancelled the pending invocation effect. + Outcome CommandsInvocationEffectOutcome `json:"outcome"` +} + +// Whether finalizing the invocation effect succeeded, and the failure reason when it did +// not. +// Experimental: CommandsFinalizeInvocationEffectResult is part of an experimental API and +// may change or be removed. +type CommandsFinalizeInvocationEffectResult struct { + // Failure reason when the invocation effect could not be finalized. + Error *string `json:"error,omitempty"` + // Whether the pending invocation effect was finalized successfully. + Success bool `json:"success"` +} + // Pending command request ID and an optional error if the client handler failed. // Experimental: CommandsHandlePendingCommandRequest is part of an experimental API and may // change or be removed. @@ -1273,6 +1905,8 @@ type CommandsInvokeRequest struct { Input *string `json:"input,omitempty"` // Command name. Leading slashes are stripped and the name is matched case-insensitively. Name string `json:"name"` + // Optional client surface that initiated the invocation + Origin *CommandsInvocationOrigin `json:"origin,omitempty"` } // Experimental: CommandsListRequest is part of an experimental API and may change or be @@ -1352,6 +1986,25 @@ type ConfigureSessionExtensionsParams struct { SessionID string `json:"sessionId"` } +// Identity of the integrating host, declared once on the `server.connect` handshake so +// telemetry from this connection is attributed to a single, consistent surface. All fields +// are optional; omit them to keep the default attribution. +// Experimental: ConnectClientInfo is part of an experimental API and may change or be +// removed. +// Internal: ConnectClientInfo is an internal SDK API and is not part of the public surface. +type ConnectClientInfo struct { + // Name of the host editor, e.g. `"vscode"`. + EditorName *string `json:"editorName,omitempty"` + // Version of the host editor, e.g. `"1.124.2"`. Ignored unless it looks like a version + // string. + EditorVersion *string `json:"editorVersion,omitempty"` + // Name of the Copilot extension within the host, e.g. `"copilot-chat"`. + ExtensionName *string `json:"extensionName,omitempty"` + // Version of the Copilot extension within the host, e.g. `"0.54.0"`. Ignored unless it + // looks like a version string. + ExtensionVersion *string `json:"extensionVersion,omitempty"` +} + // Metadata for a connected remote session. // Experimental: ConnectedRemoteSessionMetadata is part of an experimental API and may // change or be removed. @@ -1400,11 +2053,15 @@ type ConnectRemoteSessionParams struct { SessionID string `json:"sessionId"` } -// Parameters for the `server.connect` handshake: an optional connection token and optional -// connection-level opt-ins (e.g. GitHub telemetry forwarding). +// Connection-level opt-ins for the `server.connect` handshake. Transport authentication is +// consumed by the native protocol boundary before dispatch. // Experimental: ConnectRequest is part of an experimental API and may change or be removed. // Internal: ConnectRequest is an internal SDK API and is not part of the public surface. type ConnectRequest struct { + // Identity of the integrating host. Optional; omit it to keep the default attribution. + // Internal: ClientInfo is part of the SDK's internal API surface and is not intended for + // external use. + ClientInfo *ConnectClientInfo `json:"clientInfo,omitempty"` // 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. @@ -1547,16 +2204,23 @@ type CopilotUserResponse struct { // Experimental: CopilotUserResponseEndpoints is part of an experimental API and may change // or be removed. type CopilotUserResponseEndpoints struct { - API *string `json:"api,omitempty"` - Exp *string `json:"exp,omitempty"` + // Copilot API endpoint URL. + API *string `json:"api,omitempty"` + // Experimental-service endpoint URL. + Exp *string `json:"exp,omitempty"` + // Origin-tracker endpoint URL. OriginTracker *string `json:"origin-tracker,omitempty"` - Proxy *string `json:"proxy,omitempty"` - Telemetry *string `json:"telemetry,omitempty"` + // Copilot proxy endpoint URL. + Proxy *string `json:"proxy,omitempty"` + // Copilot telemetry endpoint URL. + Telemetry *string `json:"telemetry,omitempty"` } type CopilotUserResponseOrganizationListItem struct { + // GitHub login of the organization. Login *string `json:"login,omitempty"` - Name *string `json:"name,omitempty"` + // Display name of the organization. + Name *string `json:"name,omitempty"` } // Quota snapshot map from the raw Copilot user-response passthrough, with chat, @@ -1923,6 +2587,36 @@ type DiscoveredExtensionsEnableRequest struct { IDs []string `json:"ids"` } +// One server-discovered hook action from user, repository, plugin, or managed-policy +// configuration. +// Experimental: DiscoveredHook is part of an experimental API and may change or be removed. +type DiscoveredHook struct { + // Durable content hash used by hook enablement. Identical actions may intentionally share + // this key. Omitted when changing the user's disabled-hooks setting cannot change the + // action's current server-discovered state, including managed-policy hooks, session-start + // prompt actions, actions suppressed by disable-all settings, and projectless plugin + // actions that require project-directory expansion. + DisableKey *string `json:"disableKey,omitempty"` + // Whether this action is enabled under the server-side discovery settings. Concrete + // sessions may differ because they can add session-specific directories, plugins, or trust. + // False when its disable key is present in the user's disabled-hooks setting or disable-all + // settings suppress the action. + Enabled bool `json:"enabled"` + // Hook event that invokes this action. + HookType HookType `json:"hookType"` + // Deterministic identifier for this server-discovered action row. It remains stable while + // the project, origin, source, event, action content, and duplicate ordinal are unchanged. + // This is row identity, not the key persisted in disabledHooks. + ID string `json:"id"` + // Configuration tier that contributed this hook action. + Origin HookOrigin `json:"origin"` + // Input project path for which this server-side action was resolved. Set on every row + // returned for project-scoped discovery, including repeated user and policy actions. + ProjectPath *string `json:"projectPath,omitempty"` + // Human-readable source label, such as a hook file path, settings source, or plugin name. + Source *string `json:"source,omitempty"` +} + // 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 @@ -1949,6 +2643,8 @@ type EnqueueCommandParams struct { // 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. Command string `json:"command"` + // Optional user-facing text for the queue row. The command string is shown when omitted. + DisplayText *string `json:"displayText,omitempty"` } // Indicates whether the command was accepted into the local execution queue. @@ -2109,6 +2805,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 { @@ -2292,6 +3026,9 @@ type ExternalToolTextResultForLlmContentShellExit struct { Cwd *string `json:"cwd,omitempty"` // Exit code from the completed shell command ExitCode int64 `json:"exitCode"` + // Path reported in the shell session's filesystem namespace when shell output exceeded the + // configured large-output threshold. + OutputFilePath *string `json:"outputFilePath,omitempty"` // Output associated with this shell command, if available. May be partial, truncated, or a // preview; not guaranteed to be full output. OutputPreview *string `json:"outputPreview,omitempty"` @@ -2414,10 +3151,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"` } @@ -2448,19 +3191,34 @@ type FactoryAgentResult struct { // 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"` + // Accumulated active agent time in milliseconds. + ActiveMs int64 `json:"activeMs"` + // Prompt-safe live activity text. + Activity *string `json:"activity,omitempty"` + // Stable direct-agent identifier. + AgentID string `json:"agentId"` + // Registered agent type. + AgentType string `json:"agentType"` + // Epoch milliseconds when the agent completed. + CompletedAt *int64 `json:"completedAt,omitempty"` + // Friendly, non-unique name intended for display + DisplayName *string `json:"displayName,omitempty"` + // Friendly, non-unique name intended for display + Label string `json:"label"` + // Phase identifier active when the agent was launched, or null. + PhaseID *string `json:"phaseId"` + // Model requested when the agent was launched. 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"` + // Concrete model resolved for the agent. + ResolvedModel *string `json:"resolvedModel,omitempty"` + // Owning factory run identifier. + RunID string `json:"runId"` + // Epoch milliseconds when the agent started. + StartedAt *int64 `json:"startedAt,omitempty"` + // Current durable or live agent status. + Status string `json:"status"` + // Tool-call identifier that launched the agent. + ToolCallID string `json:"toolCallId"` } // Parameters for cancelling a factory run. @@ -2475,7 +3233,9 @@ type FactoryCancelRequest struct { // Experimental: FactoryCurrentPhase is part of an experimental API and may change or be // removed. type FactoryCurrentPhase struct { - ID string `json:"id"` + // Current phase identifier. + ID string `json:"id"` + // Zero-based declared phase ordinal, or null for an undeclared phase. Ordinal *int64 `json:"ordinal"` } @@ -2483,10 +3243,14 @@ type FactoryCurrentPhase struct { // 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"` + // Maximum AI credits consumed by subagents and descendants. + MaxAiCredits *float64 `json:"maxAiCredits,omitempty"` + // Maximum concurrently active subagents. + MaxConcurrentSubagents *int64 `json:"maxConcurrentSubagents,omitempty"` + // Maximum total subagents spawned by the run. + MaxTotalSubagents *int64 `json:"maxTotalSubagents,omitempty"` + // Maximum accumulated active execution time in seconds. + TimeoutSeconds *float64 `json:"timeoutSeconds,omitempty"` } // Parameters sent to the owning extension to execute a factory closure. @@ -2573,16 +3337,31 @@ type FactoryJournalPutRequest struct { RunID string `json:"runId"` } -// Empty parameters for listing factory runs. +// 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"` } -// Factory runs in durable creation order. +// 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"` + // Factory run summaries in durable creation order. Runs []FactoryRunSummary `json:"runs"` } @@ -2613,19 +3392,35 @@ type FactoryLogRequest struct { // 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"` + // Completed active time accumulated by this phase in milliseconds. + AccumulatedActiveMs int64 `json:"accumulatedActiveMs"` + // Epoch milliseconds when this phase completed; for a skipped phase, the synthetic skip + // timestamp (equal to `startedAt`). + CompletedAt *int64 `json:"completedAt,omitempty"` + // Current live active time for this phase in milliseconds. + CurrentActiveMs int64 `json:"currentActiveMs"` + // Optional human-readable phase detail. + Detail *string `json:"detail,omitempty"` + // Number of times execution entered this phase. + EntryCount int64 `json:"entryCount"` + // Phase identifier. + ID string `json:"id"` + // Most recent run attempt that entered this phase, or `0` if the phase has never been + // entered. + LastEnteredRunAttempt int64 `json:"lastEnteredRunAttempt"` + // Direct agents in this phase that are currently live. + LiveAgentCount int64 `json:"liveAgentCount"` + // Zero-based declared phase ordinal, or null for an undeclared phase. + Ordinal *int64 `json:"ordinal"` + // Epoch milliseconds when this phase first started; for a skipped phase, the synthetic skip + // timestamp (equal to `completedAt`). + StartedAt *int64 `json:"startedAt,omitempty"` + // Derived lifecycle state of the phase. + Status FactoryPhaseStatus `json:"status"` + // Human-readable phase title. + Title string `json:"title"` + // Total direct agents associated with this phase. + TotalAgentCount int64 `json:"totalAgentCount"` } // One durable factory progress record. @@ -2650,11 +3445,16 @@ type FactoryProgressLine struct { // 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"` + // Whether progress records newer than this page exist. + HasMoreNewer bool `json:"hasMoreNewer"` + // Whether progress records older than this page exist. + HasMoreOlder bool `json:"hasMoreOlder"` + // Newest sequence number in this page, or null when empty. + NewestSeq *int64 `json:"newestSeq"` + // Oldest sequence number in this page, or null when empty. + OldestSeq *int64 `json:"oldestSeq"` + // Progress records in sequence order. + Records []FactoryProgressLine `json:"records"` // Run revision reflected by this page. Revision int64 `json:"revision"` } @@ -2665,6 +3465,10 @@ type FactoryProgressPage struct { type FactoryResumeRequest struct { // Optional per-invocation resource ceiling overrides. Limits *FactoryRunLimits `json:"limits,omitempty"` + // Whether to emit factory phase names to the session transcript. + LogPhaseNames *bool `json:"logPhaseNames,omitempty"` + // Whether to notify the originating session when the factory completes. + NotifyOnComplete *bool `json:"notifyOnComplete,omitempty"` // Factory run identifier. RunID string `json:"runId"` } @@ -2683,8 +3487,11 @@ type FactoryResumeResult struct { // 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"` + // Accumulated active execution time in milliseconds. + ActiveMs int64 `json:"activeMs"` + // AI usage consumed by the run in nano-AIU. + NanoAiu int64 `json:"nanoAiu"` + // Total subagents spawned by the run. Subagents int64 `json:"subagents"` } @@ -2692,28 +3499,50 @@ type FactoryRunConsumed struct { // 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"` + // Epoch milliseconds when the current active segment started, or null while inactive. + ActiveSegmentStartedAt *int64 `json:"activeSegmentStartedAt"` + // Durable identities and live statuses for direct factory agents. + Agents []FactoryAgentSummary `json:"agents"` + // Approved effective resource ceilings, or null until approved. + Approved *FactoryDeclaredLimits `json:"approved"` + // Epoch milliseconds when the run completed, or null while nonterminal. + CompletedAt *int64 `json:"completedAt"` + // Durable resource consumption. + Consumed FactoryRunConsumed `json:"consumed"` + // Epoch milliseconds when the run was created. + CreatedAt int64 `json:"createdAt"` + // Current phase identity, or null before any phase is entered. + CurrentPhase *FactoryCurrentPhase `json:"currentPhase"` + // Resource ceilings declared by the factory. + DeclaredLimits FactoryDeclaredLimits `json:"declaredLimits"` + // Number of phases declared by the factory. + DeclaredPhaseCount int64 `json:"declaredPhaseCount"` + // Human-readable factory description. + Description string `json:"description"` + // Registered factory name. + FactoryName string `json:"factoryName"` + // Number of direct factory agents currently live. + LiveAgentCount int64 `json:"liveAgentCount"` + // Epoch milliseconds when this live-overlay snapshot was observed. + ObservedAt int64 `json:"observedAt"` + // Lifecycle and timing observations for each factory phase. + Phases []FactoryPhaseObservation `json:"phases"` + // Bidirectional page of durable factory progress. + Progress FactoryProgressPage `json:"progress"` + // Monotonic durable run revision. + Revision int64 `json:"revision"` + // Factory run identifier. + RunID string `json:"runId"` + // Epoch milliseconds when execution first started, or null before start. + StartedAt *int64 `json:"startedAt"` + // Current factory run status. + Status FactoryRunStatus `json:"status"` + // Terminal run outcome, or null while nonterminal. + Terminal *FactoryRunTerminal `json:"terminal"` + // Total direct factory agents spawned across all attempts. + TotalSpawnedAgentCount int64 `json:"totalSpawnedAgentCount"` + // Epoch milliseconds when the durable run was last updated. + UpdatedAt int64 `json:"updatedAt"` } // Machine-readable factory run failure. @@ -2734,6 +3563,19 @@ 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"` @@ -2762,6 +3604,18 @@ func (FactoryRunFailureFactoryLimitReached) Type() FactoryRunFailureType { return FactoryRunFailureTypeFactoryLimitReached } +// The extension that owns the factory disconnected while the run was executing, so the host +// halted it. The run's journaled subagent results are preserved so a resume can reuse them. +type FactoryRunFailureFactoryProviderDisconnected struct { + // Factory run identifier. + RunID string `json:"runId"` +} + +func (FactoryRunFailureFactoryProviderDisconnected) factoryRunFailure() {} +func (FactoryRunFailureFactoryProviderDisconnected) Type() FactoryRunFailureType { + return FactoryRunFailureTypeFactoryProviderDisconnected +} + type FactoryRunFailureFactoryResumeDeclined struct { // Human-readable reason the resume did not proceed. Reason string `json:"reason"` @@ -2807,9 +3661,12 @@ type FactoryRunRequest struct { // Experimental: FactoryRunResult is part of an experimental API and may change or be // removed. type FactoryRunResult struct { + // One-based execution attempt represented by this envelope. Absent before the first attempt + // starts or when returned by an older runtime. + Attempt *int64 `json:"attempt,omitempty"` // Error message for an errored run. Error *string `json:"error,omitempty"` - // Machine-readable failure details for an errored run. + // Machine-readable failure details for a halted or errored run. Failure FactoryRunFailure `json:"failure,omitempty"` // Reason for a halted or cancelled run. Reason *string `json:"reason,omitempty"` @@ -2827,35 +3684,100 @@ type FactoryRunResult struct { // 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"` + // Epoch milliseconds when the current active segment started, or null while inactive. + ActiveSegmentStartedAt *int64 `json:"activeSegmentStartedAt"` + // Approved effective resource ceilings, or null until approved. + Approved *FactoryDeclaredLimits `json:"approved"` + // Epoch milliseconds when the run completed, or null while nonterminal. + CompletedAt *int64 `json:"completedAt"` + // Durable resource consumption. + Consumed FactoryRunConsumed `json:"consumed"` + // Epoch milliseconds when the run was created. + CreatedAt int64 `json:"createdAt"` + // Current phase identity, or null before any phase is entered. + CurrentPhase *FactoryCurrentPhase `json:"currentPhase"` + // Resource ceilings declared by the factory. + DeclaredLimits FactoryDeclaredLimits `json:"declaredLimits"` + // Number of phases declared by the factory. + DeclaredPhaseCount int64 `json:"declaredPhaseCount"` + // Human-readable factory description. + Description string `json:"description"` + // Registered factory name. + FactoryName string `json:"factoryName"` + // Number of direct factory agents currently live. + LiveAgentCount int64 `json:"liveAgentCount"` + // Epoch milliseconds when this live-overlay snapshot was observed. + ObservedAt int64 `json:"observedAt"` + // Monotonic durable run revision. + Revision int64 `json:"revision"` + // Factory run identifier. + RunID string `json:"runId"` + // Epoch milliseconds when execution first started, or null before start. + StartedAt *int64 `json:"startedAt"` + // Current factory run status. + Status FactoryRunStatus `json:"status"` + // Terminal run outcome, or null while nonterminal. + Terminal *FactoryRunTerminal `json:"terminal"` + // Total direct factory agents spawned across all attempts. + TotalSpawnedAgentCount int64 `json:"totalSpawnedAgentCount"` + // Epoch milliseconds when the durable run was last updated. + 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"` + // Human-readable terminal error. + Error *string `json:"error,omitempty"` + // Machine-readable terminal failure. + Failure FactoryRunFailure `json:"failure,omitempty"` + // Human-readable terminal reason. + Reason *string `json:"reason,omitempty"` + // Prompt-safe preview of the completed result. + ResultPreview *string `json:"resultPreview,omitempty"` +} + +// Internal parameters for resuming a factory run from a tool. +// Experimental: FactoryToolResumeRequest is part of an experimental API and may change or +// be removed. +// Internal: FactoryToolResumeRequest is an internal SDK API and is not part of the public +// surface. +type FactoryToolResumeRequest struct { + // Optional per-invocation resource ceiling overrides. + Limits *FactoryRunLimits `json:"limits,omitempty"` + // Factory run identifier. + RunID string `json:"runId"` + // Opaque identifier of the originating tool call. + ToolCallID *string `json:"toolCallId,omitempty"` +} + +// Options for an internal tool-originated factory invocation. +// Experimental: FactoryToolRunOptions is part of an experimental API and may change or be +// removed. +// Internal: FactoryToolRunOptions is an internal SDK API and is not part of the public +// surface. +type FactoryToolRunOptions struct { + // Per-invocation resource ceiling overrides. + Limits *FactoryRunLimits `json:"limits,omitempty"` + // Run identifier whose journal and progress should seed this resumed run. + ResumeFromRunID *string `json:"resumeFromRunId,omitempty"` +} + +// Internal parameters for invoking a registered factory from a tool. +// Experimental: FactoryToolRunRequest is part of an experimental API and may change or be +// removed. +// Internal: FactoryToolRunRequest is an internal SDK API and is not part of the public +// surface. +type FactoryToolRunRequest struct { + // Factory input value. + Args any `json:"args"` + // Registered factory name. + Name string `json:"name"` + // Tool-originated factory invocation options. + Options *FactoryToolRunOptions `json:"options,omitempty"` + // Opaque identifier of the originating tool call. + ToolCallID *string `json:"toolCallId,omitempty"` } // Content filtering mode to apply to all tools, or a map of tool name to content filtering @@ -2934,6 +3856,10 @@ type GitHubTelemetryClientInfo struct { CLIVersion string `json:"cli_version"` // Copilot subscription plan, when known. CopilotPlan *string `json:"copilot_plan,omitempty"` + // Number of logical CPU cores on the host. + CpuCount *int64 `json:"cpu_count,omitempty"` + // Distinct CPU model names for the host, comma-separated. + CpuModel *string `json:"cpu_model,omitempty"` // Stable machine identifier for the device. DevDeviceID *string `json:"dev_device_id,omitempty"` // Whether the user is a GitHub/Microsoft staff member. @@ -2999,6 +3925,61 @@ type GitHubTelemetryNotification struct { SessionID *string `json:"sessionId,omitempty"` } +// Asks the SDK client to acquire a GitHub access token from an opaque callback registration. +// Experimental: GitHubTokenAcquireRequest is part of an experimental API and may change or +// be removed. +type GitHubTokenAcquireRequest struct { + // Effective GitHub host for which the callback must return a token. + Host string `json:"host"` + // Why the runtime is requesting a GitHub credential. + Reason GitHubTokenAcquireReason `json:"reason"` + // Opaque identifier generated by the SDK for this callback registration. + RegistrationID string `json:"registrationId"` + // Session receiving the token. Absent only before a cloud session has been assigned its id. + SessionID *string `json:"sessionId,omitempty"` +} + +// SDK host response to a GitHub credential request. +// Experimental: GitHubTokenAcquireResult is part of an experimental API and may change or +// be removed. +type GitHubTokenAcquireResult interface { + githubTokenAcquireResult() + Kind() GitHubTokenAcquireResultKind +} + +type RawGitHubTokenAcquireResultData struct { + Discriminator GitHubTokenAcquireResultKind + Raw json.RawMessage +} + +func (RawGitHubTokenAcquireResultData) githubTokenAcquireResult() {} +func (r RawGitHubTokenAcquireResultData) Kind() GitHubTokenAcquireResultKind { + return r.Discriminator +} + +type GitHubTokenAcquireResultCancelled struct { +} + +func (GitHubTokenAcquireResultCancelled) githubTokenAcquireResult() {} +func (GitHubTokenAcquireResultCancelled) Kind() GitHubTokenAcquireResultKind { + return GitHubTokenAcquireResultKindCancelled +} + +type GitHubTokenAcquireResultToken struct { + // GitHub access token acquired by the SDK host. + AccessToken string `json:"accessToken"` + // Remaining token lifetime in seconds when callback execution completes. It must exceed the + // one-hour preflight refresh threshold. + ExpiresIn int64 `json:"expiresIn"` + // OAuth token type. Defaults to bearer when omitted. + TokenType *string `json:"tokenType,omitempty"` +} + +func (GitHubTokenAcquireResultToken) githubTokenAcquireResult() {} +func (GitHubTokenAcquireResultToken) Kind() GitHubTokenAcquireResultKind { + return GitHubTokenAcquireResultKindToken +} + // Pending external tool call request ID, with the tool result or an error describing why it // failed. // Experimental: HandlePendingToolCallRequest is part of an experimental API and may change @@ -3283,8 +4264,6 @@ type HistoryTruncateResult struct { // removed. // Internal: HookInvokeRequest is an internal SDK API and is not part of the public surface. type HookInvokeRequest struct { - // Internal: HookType is part of the SDK's internal API surface and is not intended for - // external use. HookType HookType `json:"hookType"` Input any `json:"input"` SessionID string `json:"sessionId"` @@ -3298,6 +4277,40 @@ type HookInvokeResponse struct { Output any `json:"output,omitempty"` } +// Optional project paths and host-exclusion behavior for server-scoped hook discovery. +// Experimental: HooksDiscoverRequest is part of an experimental API and may change or be +// removed. +type HooksDiscoverRequest struct { + // When true, omit host-owned user and plugin hook rows and their diagnostics. + // Managed-policy hooks and trusted repository hooks remain visible, and host disabledHooks + // still contribute to each remaining row's effective enabled state. This filters sources + // rather than simulating a host with no settings. + ExcludeHostHooks *bool `json:"excludeHostHooks,omitempty"` + // Optional project directory paths whose trusted repository and project-expanded plugin + // hooks should be discovered. When omitted or empty, user, managed-policy, and globally + // enabled installed or explicit plugin hooks are returned without project expansion. + ProjectPaths []string `json:"projectPaths,omitzero"` +} + +// Server-discovered hook actions and partial-load diagnostics from user, repository, +// plugin, and managed-policy sources. Concrete sessions may include additional +// session-specific hook sources. +// Experimental: HooksDiscoverResult is part of an experimental API and may change or be +// removed. +type HooksDiscoverResult struct { + // Errors for hook sources or actions that could not be loaded, making the result partially + // incomplete. Other valid actions are still returned. Project-resolution and + // repository-settings errors are prefixed with their project path. + Errors []string `json:"errors"` + // All discovered hook actions. Byte-identical actions remain separate rows even when they + // share a disable key. + Hooks []DiscoveredHook `json:"hooks"` + // Non-fatal source-loading warnings. Discovery remains complete for the affected source, + // although the source had a recoverable issue. Repository-settings warnings are prefixed + // with their project path when attribution is available. + Warnings []string `json:"warnings"` +} + // Installed plugin record from global state, with marketplace, version, install time, // enabled state, cache path, and source. // Experimental: InstalledPlugin is part of an experimental API and may change or be removed. @@ -3308,6 +4321,12 @@ type InstalledPlugin struct { Enabled bool `json:"enabled"` // Installation timestamp InstalledAt string `json:"installed_at"` + // Absolute path of the marketplace directory a live plugin was resolved from. Present only + // on live, never-persisted records — those synthesized at session start for a + // directory/local marketplace, whose cache_path points at the real plugin directory on disk + // rather than a copy under the installed-plugins cache. Its presence is what marks a record + // as live, and no record carrying it is ever written to the persisted installedPlugins key. + InstalledFrom *string `json:"installed_from,omitempty"` // Marketplace the plugin came from (empty string for direct repo installs) Marketplace string `json:"marketplace"` // Plugin name @@ -3334,6 +4353,13 @@ type InstalledPluginInfo struct { DirectSourceID *string `json:"directSourceId,omitempty"` // Whether the plugin is currently enabled for new sessions Enabled bool `json:"enabled"` + // Absolute path of the marketplace directory a live plugin was resolved from. Present only + // on live, never-persisted records — a plugin belonging to a directory/local marketplace, + // which is loaded from its real directory on every pass instead of a copy under the + // installed-plugins cache. Its presence is what marks a listed plugin as live: such a + // plugin is always present on disk, so `enabled` is its only meaningful state and it is + // never "not installed". + InstalledFrom *string `json:"installedFrom,omitempty"` // Marketplace the plugin came from. Empty string ("") for direct repo / URL / local // installs. Marketplace string `json:"marketplace"` @@ -3358,9 +4384,12 @@ type InstalledPluginSource struct { // Experimental: InstalledPluginSourceGitHub is part of an experimental API and may change // or be removed. type InstalledPluginSourceGitHub struct { + // Optional repository-relative path to the plugin. Path *string `json:"path,omitempty"` - Ref *string `json:"ref,omitempty"` - Repo string `json:"repo"` + // Optional Git ref to resolve. + Ref *string `json:"ref,omitempty"` + // GitHub repository in `owner/repo` form. + Repo string `json:"repo"` // Optional full 40-character hexadecimal commit SHA. Sha *string `json:"sha,omitempty"` // Constant value. Always "github". @@ -3371,6 +4400,7 @@ type InstalledPluginSourceGitHub struct { // Experimental: InstalledPluginSourceLocal is part of an experimental API and may change or // be removed. type InstalledPluginSourceLocal struct { + // Local filesystem path to the plugin. Path string `json:"path"` // Constant value. Always "local". Source InstalledPluginSourceLocalSource `json:"source"` @@ -3381,13 +4411,16 @@ type InstalledPluginSourceLocal struct { // Experimental: InstalledPluginSourceURL is part of an experimental API and may change or // be removed. type InstalledPluginSourceURL struct { + // Optional source-relative path to the plugin. Path *string `json:"path,omitempty"` - Ref *string `json:"ref,omitempty"` + // Optional Git ref to resolve. + 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"` + // URL of the plugin source. + URL string `json:"url"` } // Canonical file or directory where custom instructions can be discovered or created, with @@ -3556,8 +4589,9 @@ type LlmInferenceHTTPRequestStartRequest struct { // 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. - AgentInvocationID *string `json:"agentInvocationId,omitempty"` - Headers map[string][]string `json:"headers"` + AgentInvocationID *string `json:"agentInvocationId,omitempty"` + // HTTP request headers, preserving multiple values per name. + Headers map[string][]string `json:"headers"` // 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`, @@ -3642,6 +4676,7 @@ type LlmInferenceHTTPResponseChunkResult struct { // Experimental: LlmInferenceHTTPResponseStartRequest is part of an experimental API and may // change or be removed. type LlmInferenceHTTPResponseStartRequest struct { + // HTTP response headers, preserving multiple values per name. Headers map[string][]string `json:"headers"` // Matches the requestId from the originating httpRequestStart frame. RequestID string `json:"requestId"` @@ -3737,6 +4772,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. @@ -4021,7 +5067,7 @@ type MCPCancelSamplingExecutionResult struct { // removed. type MCPConfigAddRequest struct { // MCP server configuration (stdio process or remote HTTP/SSE) - Config MCPServerConfig `json:"config"` + Config MCPSerializableServerConfig `json:"config"` // Unique name for the MCP server Name string `json:"name"` } @@ -4064,7 +5110,7 @@ type MCPConfigEnableResult struct { // Experimental: MCPConfigList is part of an experimental API and may change or be removed. type MCPConfigList struct { // All MCP servers from user config, keyed by name - Servers map[string]MCPServerConfig `json:"servers"` + Servers map[string]MCPSerializableServerConfig `json:"servers"` } // Experimental: MCPConfigReloadResult is part of an experimental API and may change or be @@ -4090,7 +5136,7 @@ type MCPConfigRemoveResult struct { // removed. type MCPConfigUpdateRequest struct { // MCP server configuration (stdio process or remote HTTP/SSE) - Config MCPServerConfig `json:"config"` + Config MCPSerializableServerConfig `json:"config"` // Name of the MCP server to update Name string `json:"name"` } @@ -4100,7 +5146,7 @@ type MCPConfigUpdateRequest struct { type MCPConfigUpdateResult struct { } -// Opaque auth info used to configure GitHub MCP. +// Credential-free authentication identity used to configure GitHub MCP. // Experimental: MCPConfigureGitHubRequest is part of an experimental API and may change or // be removed. type MCPConfigureGitHubRequest struct { @@ -4187,6 +5233,15 @@ type MCPExecuteSamplingRequest struct { type MCPExecuteSamplingResult struct { } +// MCP server whose connection attempt failed. +// Experimental: MCPFailedServer is part of an experimental API and may change or be removed. +type MCPFailedServer struct { + // The captured connection failure detail. + Error *string `json:"error,omitempty"` + // The config key of the server that failed to connect. + Name string `json:"name"` +} + // 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. @@ -4280,6 +5335,44 @@ type MCPHostState struct { PendingConnections []string `json:"pendingConnections"` } +// A normalised, inert description of what installing an MCP server would involve. Carries +// no raw card, no install specification, and no secret value. +// Experimental: MCPInstallPlan is part of an experimental API and may change or be removed. +type MCPInstallPlan struct { + // The configuration changes installing would make, described rather than serialised, so the + // mutable configuration payload stays behind the runtime boundary. + ConfigurationChanges []MCPPlanConfigurationChange `json:"configurationChanges"` + // Normalised identity of the server the plan would install. + Identity MCPPlanResourceIdentity `json:"identity"` + // Opaque, runtime-instance scoped, TTL-bound, single-use handle for this plan. Rejected + // when stale, replayed, or presented to a different runtime instance. Never logged. + PlanHandle string `json:"planHandle"` + // ISO 8601 timestamp after which the plan handle is stale and will be rejected. Abandoning + // a plan needs no call: an unused handle simply expires, so cancellation before commit is + // side-effect free. + PlanHandleExpiresAt string `json:"planHandleExpiresAt"` + // Outcome of evaluating the server against registry and enterprise policy. + Policy MCPPlanPolicyResult `json:"policy"` + // Origin and semantic digest of the exact validated JSON MCP card content bound to this + // plan. + Provenance MCPPlanProvenance `json:"provenance"` + // Identifier of the choice the runtime would pick by default. Omitted when there is no + // eligible transport, or when the runtime expresses no preference. + RecommendedTransportChoiceID *string `json:"recommendedTransportChoiceId,omitempty"` + // Whether applying this plan would require an MCP reload to take effect. Planning itself + // never reloads. + ReloadRequired bool `json:"reloadRequired"` + // Whether the plan cannot be applied without further input, because a required value has no + // default or a secret must be supplied. + RequiresInteractiveConfiguration bool `json:"requiresInteractiveConfiguration"` + // Configuration scope and key the plan would write to. + Target MCPPlanTarget `json:"target"` + // Every eligible transport, so a host can present an explicit choice. A completed plan + // always has at least one; when none is eligible, planning returns + // `CatalogUnavailableTransportError` instead. + TransportChoices []MCPPlanTransportChoice `json:"transportChoices"` +} + // Server name to check running status for. // Experimental: MCPIsServerRunningRequest is part of an experimental API and may change or // be removed. @@ -4424,7 +5517,7 @@ type MCPOauthPendingRequestResponseToken struct { AccessToken string `json:"accessToken"` // Token lifetime in seconds, if known. ExpiresIn *int64 `json:"expiresIn,omitempty"` - // OAuth token type. Defaults to Bearer when omitted. + // OAuth token type. Defaults to bearer when omitted. TokenType *string `json:"tokenType,omitempty"` } @@ -4433,6 +5526,83 @@ func (MCPOauthPendingRequestResponseToken) Kind() MCPOauthPendingRequestResponse return MCPOauthPendingRequestResponseKindToken } +// Remote MCP server name for a passive OAuth status probe. +// Experimental: MCPOauthProbeRequest is part of an experimental API and may change or be +// removed. +type MCPOauthProbeRequest struct { + // Name of the configured remote MCP server to probe. + ServerName string `json:"serverName"` +} + +// Passive MCP OAuth probe result. `authenticated` means the server accepted the probe +// request while an OAuth-origin access token was attached; it does not prove the server +// required or independently validated that token. The probe does not make a second +// unauthenticated request. Failed is an expected probe-domain outcome; JSON-RPC errors are +// reserved for API-call failures. +// Experimental: MCPOauthProbeResult is part of an experimental API and may change or be +// removed. +type MCPOauthProbeResult interface { + mcpOauthProbeResult() + Status() MCPOauthProbeResultStatus +} + +type RawMCPOauthProbeResultData struct { + Discriminator MCPOauthProbeResultStatus + Raw json.RawMessage +} + +func (RawMCPOauthProbeResultData) mcpOauthProbeResult() {} +func (r RawMCPOauthProbeResultData) Status() MCPOauthProbeResultStatus { + return r.Discriminator +} + +type MCPOauthProbeResultAuthenticated struct { + // HTTP response returned by the server. + HTTPResponse MCPOauthHTTPResponse `json:"httpResponse"` +} + +func (MCPOauthProbeResultAuthenticated) mcpOauthProbeResult() {} +func (MCPOauthProbeResultAuthenticated) Status() MCPOauthProbeResultStatus { + return MCPOauthProbeResultStatusAuthenticated +} + +type MCPOauthProbeResultFailed struct { + // Human-readable probe failure detail. + Error string `json:"error"` + // HTTP response returned by the server, when the probe reached the server and captured the + // complete response. + HTTPResponse *MCPOauthHTTPResponse `json:"httpResponse,omitempty"` +} + +func (MCPOauthProbeResultFailed) mcpOauthProbeResult() {} +func (MCPOauthProbeResultFailed) Status() MCPOauthProbeResultStatus { + return MCPOauthProbeResultStatusFailed +} + +type MCPOauthProbeResultNeedsAuth struct { + // HTTP 401 or 403 response returned by the server. + HTTPResponse MCPOauthHTTPResponse `json:"httpResponse"` + // Why authentication is needed. + Reason MCPOauthProbeNeedsAuthReason `json:"reason"` + // Parsed WWW-Authenticate challenge parameters, when present and parseable. + WwwAuthenticateParams *MCPOauthWwwAuthenticateParams `json:"wwwAuthenticateParams,omitempty"` +} + +func (MCPOauthProbeResultNeedsAuth) mcpOauthProbeResult() {} +func (MCPOauthProbeResultNeedsAuth) Status() MCPOauthProbeResultStatus { + return MCPOauthProbeResultStatusNeedsAuth +} + +type MCPOauthProbeResultNoAuthRequired struct { + // HTTP response returned by the server. + HTTPResponse MCPOauthHTTPResponse `json:"httpResponse"` +} + +func (MCPOauthProbeResultNoAuthRequired) mcpOauthProbeResult() {} +func (MCPOauthProbeResultNoAuthRequired) Status() MCPOauthProbeResultStatus { + return MCPOauthProbeResultStatusNoAuthRequired +} + // Pending MCP OAuth request id to respond to. // Experimental: MCPOauthRespondRequest is part of an experimental API and may change or be // removed. @@ -4450,6 +5620,439 @@ type MCPOauthRespondResult struct { Success bool `json:"success"` } +// One change applying the plan would make, described rather than serialised so the +// configuration payload stays behind the runtime boundary. +// Experimental: MCPPlanConfigurationChange is part of an experimental API and may change or +// be removed. +type MCPPlanConfigurationChange struct { + // Names of the configuration fields the change would set, without their values. + ChangedFields []string `json:"changedFields"` + // Configuration key the change applies to. + ConfigKey string `json:"configKey"` + // Whether the change would create a new entry or modify an existing one. + Operation MCPPlanConfigurationOperation `json:"operation"` + // Scope the change would be written to. + Scope MCPPlanScope `json:"scope"` + // Secret placeholders the written configuration would reference. The constrained + // placeholder type cannot carry a literal secret value. + SecretReferences []string `json:"secretReferences"` +} + +// A side-effect-free request for an MCP install plan. Computing a plan never writes +// configuration, stores a secret, or reloads MCP servers. +// Experimental: MCPPlanInstallRequest is part of an experimental API and may change or be +// removed. +type MCPPlanInstallRequest struct { + // Protocol version and capabilities the caller requires. + Contract CatalogClientContract `json:"contract"` + // Configuration scope the plan targets. Defaults to user scope when omitted. + Scope *MCPPlanScope `json:"scope,omitempty"` + // What to plan: either a candidate handle from a previous search, or a card supplied + // directly. + Source MCPPlanInstallSource `json:"source"` +} + +// Outcome of an mcp.planInstall call: either a normalised plan, or one typed refusal. +// Nothing is written in either case. +// Experimental: MCPPlanInstallResult is part of an experimental API and may change or be +// removed. +type MCPPlanInstallResult interface { + mcpPlanInstallResult() + mcpPlanInstallResultKind() MCPPlanInstallResultKind +} + +type RawMCPPlanInstallResultData struct { + Discriminator MCPPlanInstallResultKind + Raw json.RawMessage +} + +func (RawMCPPlanInstallResultData) mcpPlanInstallResult() {} +func (r RawMCPPlanInstallResultData) mcpPlanInstallResultKind() MCPPlanInstallResultKind { + return r.Discriminator +} +func (CatalogAuthenticationRequiredError) mcpPlanInstallResult() {} +func (CatalogAuthenticationRequiredError) mcpPlanInstallResultKind() MCPPlanInstallResultKind { + return MCPPlanInstallResultKindAuthenticationRequired +} +func (CatalogContractViolationError) mcpPlanInstallResult() {} +func (CatalogContractViolationError) mcpPlanInstallResultKind() MCPPlanInstallResultKind { + return MCPPlanInstallResultKindContractViolation +} + +// A presented handle was not accepted. Handles are runtime-instance scoped, TTL-bound, and +// single-use, so each way of failing is reported distinctly. +// Experimental: CatalogHandleRejectedError is part of an experimental API and may change or +// be removed. +type CatalogHandleRejectedError struct { + // Which kind of handle was presented. + HandleType CatalogHandleType `json:"handleType"` + // Human-readable explanation, safe to surface. Never contains the handle itself, nor a + // query, URL, or secret. + Message string `json:"message"` + // Why the handle was rejected. + Reason CatalogHandleRejectionReason `json:"reason"` +} + +func (CatalogHandleRejectedError) mcpPlanInstallResult() {} +func (CatalogHandleRejectedError) mcpPlanInstallResultKind() MCPPlanInstallResultKind { + return MCPPlanInstallResultKindHandleRejected +} +func (CatalogInvalidRequestError) mcpPlanInstallResult() {} +func (CatalogInvalidRequestError) mcpPlanInstallResultKind() MCPPlanInstallResultKind { + return MCPPlanInstallResultKindInvalidRequest +} +func (CatalogMalformedCardError) mcpPlanInstallResult() {} +func (CatalogMalformedCardError) mcpPlanInstallResultKind() MCPPlanInstallResultKind { + return MCPPlanInstallResultKindMalformedCard +} +func (CatalogNegotiationRefusedError) mcpPlanInstallResult() {} +func (CatalogNegotiationRefusedError) mcpPlanInstallResultKind() MCPPlanInstallResultKind { + return MCPPlanInstallResultKindNegotiationRefused +} +func (CatalogNetworkFailureError) mcpPlanInstallResult() {} +func (CatalogNetworkFailureError) mcpPlanInstallResultKind() MCPPlanInstallResultKind { + return MCPPlanInstallResultKindNetworkFailure +} + +// The candidate is discoverable but cannot be installed. `application/ai-skill` resolves +// here, because it stays searchable while remaining typed non-installable. +// Experimental: CatalogNotInstallableError is part of an experimental API and may change or +// be removed. +type CatalogNotInstallableError struct { + // Human-readable explanation, safe to surface. Never contains a query, URL, handle, or + // secret. + Message string `json:"message"` + // Why the candidate cannot be installed. + Reason CatalogNotInstallableReason `json:"reason"` +} + +func (CatalogNotInstallableError) mcpPlanInstallResult() {} +func (CatalogNotInstallableError) mcpPlanInstallResultKind() MCPPlanInstallResultKind { + return MCPPlanInstallResultKindNotInstallable +} +func (CatalogPolicyRejectedError) mcpPlanInstallResult() {} +func (CatalogPolicyRejectedError) mcpPlanInstallResultKind() MCPPlanInstallResultKind { + return MCPPlanInstallResultKindPolicyRejected +} +func (CatalogUnavailableError) mcpPlanInstallResult() {} +func (CatalogUnavailableError) mcpPlanInstallResultKind() MCPPlanInstallResultKind { + return MCPPlanInstallResultKindUnavailable +} + +// No transport this runtime can use is available for the requested server. +// Experimental: CatalogUnavailableTransportError is part of an experimental API and may +// change or be removed. +type CatalogUnavailableTransportError struct { + // Human-readable explanation, safe to surface. Never contains a query, URL, handle, or + // secret. + Message string `json:"message"` + // Why no transport could be offered. + Reason CatalogUnavailableTransportReason `json:"reason"` +} + +func (CatalogUnavailableTransportError) mcpPlanInstallResult() {} +func (CatalogUnavailableTransportError) mcpPlanInstallResultKind() MCPPlanInstallResultKind { + return MCPPlanInstallResultKindUnavailableTransport +} +func (CatalogUnsafeRetrievalError) mcpPlanInstallResult() {} +func (CatalogUnsafeRetrievalError) mcpPlanInstallResultKind() MCPPlanInstallResultKind { + return MCPPlanInstallResultKindUnsafeRetrieval +} + +// A computed MCP install plan. Nothing has been applied: the plan describes what installing +// would change, and the plan handle is what a later apply operation would consume. +// Experimental: MCPPlanInstallPlanned is part of an experimental API and may change or be +// removed. +type MCPPlanInstallPlanned struct { + // Protocol version and capabilities the runtime honoured. + Negotiated CatalogNegotiatedContract `json:"negotiated"` + // The normalised plan. + Plan MCPInstallPlan `json:"plan"` +} + +func (MCPPlanInstallPlanned) mcpPlanInstallResult() {} +func (MCPPlanInstallPlanned) mcpPlanInstallResultKind() MCPPlanInstallResultKind { + return MCPPlanInstallResultKindPlanned +} + +// What an install plan is computed from: a candidate handle from a previous search, or a +// card supplied directly. +// Experimental: MCPPlanInstallSource is part of an experimental API and may change or be +// removed. +type MCPPlanInstallSource interface { + mcpPlanInstallSource() + Kind() MCPPlanInstallSourceKind +} + +type RawMCPPlanInstallSourceData struct { + Discriminator MCPPlanInstallSourceKind + Raw json.RawMessage +} + +func (RawMCPPlanInstallSourceData) mcpPlanInstallSource() {} +func (r RawMCPPlanInstallSourceData) Kind() MCPPlanInstallSourceKind { + return r.Discriminator +} + +// Plan from a candidate returned by a previous catalog search. +// Experimental: MCPPlanInstallSourceCandidate is part of an experimental API and may change +// or be removed. +type MCPPlanInstallSourceCandidate struct { + // Single-use candidate handle. Consumed by this call, so a replay of the same handle is + // rejected. + CandidateHandle string `json:"candidateHandle"` + // The runtime- or authority-minted `searchId` returned with the search that produced this + // candidate. A search implementation binds it to private candidate-handle context; a + // planning implementation must verify that context before returning a plan. The unavailable + // planning implementation in this contract layer validates presence but does not claim the + // verification has occurred. It identifies a search rather than a person and must never be + // joined with user identity to re-identify anyone. + SearchID string `json:"searchId"` +} + +func (MCPPlanInstallSourceCandidate) mcpPlanInstallSource() {} +func (MCPPlanInstallSourceCandidate) Kind() MCPPlanInstallSourceKind { + return MCPPlanInstallSourceKindCandidate +} + +// Plan from a card supplied directly by the caller, without a preceding search. +// Experimental: MCPPlanInstallSourceCard is part of an experimental API and may change or +// be removed. +type MCPPlanInstallSourceCard struct { + // The card to plan from: exactly one of a URL or embedded data. + Card MCPServerCardReference `json:"card"` +} + +func (MCPPlanInstallSourceCard) mcpPlanInstallSource() {} +func (MCPPlanInstallSourceCard) Kind() MCPPlanInstallSourceKind { + return MCPPlanInstallSourceKindCard +} + +// Outcome of evaluating the planned server against registry and enterprise policy. +// Evaluation is read-only. +// Experimental: MCPPlanPolicyResult is part of an experimental API and may change or be +// removed. +type MCPPlanPolicyResult struct { + // What policy decided for this server. + Decision MCPPlanPolicyDecision `json:"decision"` + // Human-readable explanation, safe to surface. Never contains a query, URL, handle, or + // secret. + Reason *string `json:"reason,omitempty"` + // Which authority produced the decision. + Source MCPPlanPolicySource `json:"source"` +} + +// Provenance of the exact validated JSON MCP card content bound privately to a completed +// plan and its opaque handle. +// Experimental: MCPPlanProvenance is part of an experimental API and may change or be +// removed. +type MCPPlanProvenance struct { + // Authority associated with the validated card, without path, query, or credentials. Inert + // untrusted data. + Authority string `json:"authority"` + // Semantic digest of the exact validated JSON content bound to the plan handle. + CardDigest CardDigest `json:"cardDigest"` + // JSON MCP media type the validated card was interpreted as. + MediaType MCPServerCardMediaType `json:"mediaType"` + // ISO 8601 timestamp at which the runtime completed strict parsing and schema validation of + // the card content. + ValidatedAt string `json:"validatedAt"` +} + +// One non-secret value a transport choice needs, represented as a scalar or enumerated +// variant so enum values cannot be missing or attached to another type. +// Experimental: MCPPlanRequiredValue is part of an experimental API and may change or be +// removed. +type MCPPlanRequiredValue interface { + mcpPlanRequiredValue() + Kind() MCPPlanRequiredValueKind +} + +type RawMCPPlanRequiredValueData struct { + Discriminator MCPPlanRequiredValueKind + Raw json.RawMessage +} + +func (RawMCPPlanRequiredValueData) mcpPlanRequiredValue() {} +func (r RawMCPPlanRequiredValueData) Kind() MCPPlanRequiredValueKind { + return r.Discriminator +} + +// One enumerated non-secret value a transport choice needs before it can be applied. The +// permitted values are structurally required. +// Experimental: MCPPlanRequiredValueEnum is part of an experimental API and may change or +// be removed. +type MCPPlanRequiredValueEnum struct { + // Where the value is applied when the server is launched. + Category MCPPlanValueCategory `json:"category"` + // Default supplied by the card, when the value can be resolved without input. Presence is + // the authoritative indication that a default exists. Inert untrusted data. + DefaultValue *string `json:"defaultValue,omitempty"` + // Human-readable explanation from the card. Inert untrusted text. + Description *string `json:"description,omitempty"` + // Non-empty permitted value set. Inert untrusted data. + EnumValues []string `json:"enumValues"` + // Whether the value may be supplied more than once. + IsRepeated bool `json:"isRepeated"` + // Key the value is supplied under. Inert untrusted data. + Key string `json:"key"` + // Whether the value must be present for the plan to be applicable. + Required bool `json:"required"` + // Human-readable label from the card. Inert untrusted text. + Title *string `json:"title,omitempty"` + // Discriminator: the value must be one of `enumValues`. + ValueType MCPPlanEnumValueType `json:"valueType"` +} + +func (MCPPlanRequiredValueEnum) mcpPlanRequiredValue() {} +func (MCPPlanRequiredValueEnum) Kind() MCPPlanRequiredValueKind { + return MCPPlanRequiredValueKindEnum +} + +// One non-secret scalar value a transport choice needs before it can be applied. +// Experimental: MCPPlanRequiredValueScalar is part of an experimental API and may change or +// be removed. +type MCPPlanRequiredValueScalar struct { + // Where the value is applied when the server is launched. + Category MCPPlanValueCategory `json:"category"` + // Default supplied by the card, when the value can be resolved without input. Presence is + // the authoritative indication that a default exists. Inert untrusted data. + DefaultValue *string `json:"defaultValue,omitempty"` + // Human-readable explanation from the card. Inert untrusted text. + Description *string `json:"description,omitempty"` + // Whether the value may be supplied more than once. + IsRepeated bool `json:"isRepeated"` + // Key the value is supplied under. Inert untrusted data. + Key string `json:"key"` + // Whether the value must be present for the plan to be applicable. + Required bool `json:"required"` + // Human-readable label from the card. Inert untrusted text. + Title *string `json:"title,omitempty"` + // Scalar type the value must conform to. + ValueType MCPPlanScalarValueType `json:"valueType"` +} + +func (MCPPlanRequiredValueScalar) mcpPlanRequiredValue() {} +func (MCPPlanRequiredValueScalar) Kind() MCPPlanRequiredValueKind { + return MCPPlanRequiredValueKindScalar +} + +// Normalised identity of the MCP server a plan targets, independent of how the card spelled +// it. +// Experimental: MCPPlanResourceIdentity is part of an experimental API and may change or be +// removed. +type MCPPlanResourceIdentity struct { + // Canonical, normalised name of the server, for example `io.github.owner/server`. + CanonicalName string `json:"canonicalName"` + // Registry identifier of the server, when it came from a registry. + RegistryID *string `json:"registryId,omitempty"` + // Local configuration key the server would be recorded under. + ServerName string `json:"serverName"` + // Version advertised by the card, when it declares one. + Version *string `json:"version,omitempty"` +} + +// A secret a transport choice needs, referenced by placeholder. No secret value ever +// appears in a plan, and the placeholder resolves against the keychain only when a plan is +// applied. +// Experimental: MCPPlanSecretPlaceholder is part of an experimental API and may change or +// be removed. +type MCPPlanSecretPlaceholder struct { + // Key the secret is supplied under. Inert untrusted data. + Key string `json:"key"` + // The runtime-assigned `${secret:}` placeholder written into configuration in place of + // the value. + Placeholder string `json:"placeholder"` + // Human-readable label from the card. Inert untrusted text. + Title *string `json:"title,omitempty"` +} + +// A runtime-assigned secret placeholder. The identifier is carried once, inside the +// placeholder, so it cannot contradict a separate secret-id field. +// Experimental: MCPPlanSecretReference is part of an experimental API and may change or be +// removed. +type MCPPlanSecretReference string + +// Where a plan would be written. +// Experimental: MCPPlanTarget is part of an experimental API and may change or be removed. +type MCPPlanTarget struct { + // Configuration key the server would be recorded under within that scope. + ConfigKey string `json:"configKey"` + // Configuration scope the plan targets. + Scope MCPPlanScope `json:"scope"` +} + +// One eligible way to run the server, represented as a tagged package or remote variant so +// package identity and endpoint states cannot contradict the install method. +// Experimental: MCPPlanTransportChoice is part of an experimental API and may change or be +// removed. +type MCPPlanTransportChoice interface { + mcpPlanTransportChoice() + Transport() MCPPlanTransportChoiceTransport +} + +type RawMCPPlanTransportChoiceData struct { + Discriminator MCPPlanTransportChoiceTransport + Raw json.RawMessage +} + +func (RawMCPPlanTransportChoiceData) mcpPlanTransportChoice() {} +func (r RawMCPPlanTransportChoiceData) Transport() MCPPlanTransportChoiceTransport { + return r.Discriminator +} + +// An eligible local-package transport choice. Package identity is required and a remote +// endpoint cannot be represented. +// Experimental: MCPPlanTransportChoicePackage is part of an experimental API and may change +// or be removed. +type MCPPlanTransportChoicePackage struct { + // Stable identifier for this choice within the plan, used to select it when the plan is + // applied. + ChoiceID string `json:"choiceId"` + // Discriminator: this choice runs a local package + InstallMethod MCPPlanPackageInstallMethod `json:"installMethod"` + // Package identifier. Inert untrusted data. + PackageIdentifier string `json:"packageIdentifier"` + // Packaging ecosystem, for example `oci` or `npm`. + PackageType string `json:"packageType"` + // Typed values this choice requires, excluding secrets. + RequiredValues []MCPPlanRequiredValue `json:"requiredValues"` + // Secrets this choice requires, referenced by placeholder only. + SecretPlaceholders []MCPPlanSecretPlaceholder `json:"secretPlaceholders"` +} + +func (MCPPlanTransportChoicePackage) mcpPlanTransportChoice() {} +func (MCPPlanTransportChoicePackage) Transport() MCPPlanTransportChoiceTransport { + return MCPPlanTransportChoiceTransportStdio +} + +// An eligible remote-endpoint transport choice. The endpoint is required and package +// identity cannot be represented. +// Experimental: MCPPlanTransportChoiceRemote is part of an experimental API and may change +// or be removed. +type MCPPlanTransportChoiceRemote struct { + // Stable identifier for this choice within the plan, used to select it when the plan is + // applied. + ChoiceID string `json:"choiceId"` + // Endpoint URL. Inert untrusted data. + Endpoint string `json:"endpoint"` + // Discriminator: this choice connects to a remote endpoint + InstallMethod MCPPlanRemoteInstallMethod `json:"installMethod"` + // Typed values this choice requires, excluding secrets. + RequiredValues []MCPPlanRequiredValue `json:"requiredValues"` + // Secrets this choice requires, referenced by placeholder only. + SecretPlaceholders []MCPPlanSecretPlaceholder `json:"secretPlaceholders"` + Discriminator MCPPlanRemoteTransport `json:"transport,omitempty"` +} + +func (MCPPlanTransportChoiceRemote) mcpPlanTransportChoice() {} +func (r MCPPlanTransportChoiceRemote) Transport() MCPPlanTransportChoiceTransport { + if r.Discriminator == "" { + return MCPPlanTransportChoiceTransportHTTP + } + return MCPPlanTransportChoiceTransport(r.Discriminator) +} + // Registration parameters for an external MCP client. // Experimental: MCPRegisterExternalClientRequest is part of an experimental API and may // change or be removed. @@ -4473,6 +6076,25 @@ type MCPRegisterExternalClientRequest struct { Transport any `json:"transport"` } +// In-process MCP reload configuration. +// Experimental: MCPReloadConfig is part of an experimental API and may change or be removed. +type MCPReloadConfig struct { + ActiveGitHubToken *string `json:"activeGitHubToken,omitempty"` + // Server names the CLI enabled for this session via `--enable-mcp-server`. + CLIEnabledServers []string `json:"cliEnabledServers,omitzero"` + ConfigFilter any `json:"configFilter,omitempty"` + DisabledServers []string `json:"disabledServers,omitzero"` + EnabledServers []string `json:"enabledServers,omitzero"` + ForceRestart *bool `json:"forceRestart,omitempty"` + GitHubMCPToolOptions any `json:"githubMcpToolOptions,omitempty"` + GitHubMCPUserOverride *bool `json:"githubMcpUserOverride,omitempty"` + IncludeWorkspaceSources *bool `json:"includeWorkspaceSources,omitempty"` + Mcp3pEnabled *bool `json:"mcp3pEnabled,omitempty"` + MCPServers map[string]MCPServerConfig `json:"mcpServers"` + SecretStore any `json:"secretStore,omitempty"` + UseCachedToolSnapshots *bool `json:"useCachedToolSnapshots,omitempty"` +} + // Opaque MCP reload configuration. // Experimental: MCPReloadWithConfigRequest is part of an experimental API and may change or // be removed. @@ -4660,11 +6282,34 @@ type MCPResourceTemplate struct { type MCPRestartServerRequest struct { // 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). - Config MCPServerConfig `json:"config,omitempty"` + Config MCPSerializableServerConfig `json:"config,omitempty"` // Name of the MCP server to restart ServerName string `json:"serverName"` } +// Telemetry-obfuscation policy for an MCP server's tools. +// Experimental: MCPSafeForTelemetry is part of an experimental API and may change or be +// removed. +type MCPSafeForTelemetry interface { + mcpSafeForTelemetry() +} + +type MCPSafeForTelemetryBoolean bool + +func (MCPSafeForTelemetryBoolean) mcpSafeForTelemetry() {} + +func (MCPSafeForTelemetryFields) mcpSafeForTelemetry() {} + +// Per-field MCP telemetry-obfuscation policy. +// Experimental: MCPSafeForTelemetryFields is part of an experimental API and may change or +// be removed. +type MCPSafeForTelemetryFields struct { + // Whether MCP tool input names may be included in telemetry without obfuscation. + InputsNames bool `json:"inputsNames"` + // Whether the MCP tool name may be included in telemetry without obfuscation. + Name bool `json:"name"` +} + // Outcome of an MCP sampling execution: success result, failure error, or cancellation. // Experimental: MCPSamplingExecutionResult is part of an experimental API and may change or // be removed. @@ -4681,56 +6326,18 @@ type MCPSamplingExecutionResult struct { Result *MCPExecuteSamplingResult `json:"result,omitempty"` } -// MCP server status entry, including config source/plugin source and any connection error. -// Experimental: MCPServer is part of an experimental API and may change or be removed. -type MCPServer struct { - // Error message if the server failed to connect - Error *string `json:"error,omitempty"` - // Server name (config key) - Name string `json:"name"` - // Configuration source: user, workspace, plugin, or builtin - Source *MCPServerSource `json:"source,omitempty"` - // Plugin name that provided this server, when source is plugin. - 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, stopped, or - // not_configured - Status MCPServerStatus `json:"status"` -} - -// Set to `true` to use defaults, or provide an object with additional auth or OIDC settings. -// Experimental: MCPServerAuthConfig is part of an experimental API and may change or be -// removed. -type MCPServerAuthConfig interface { - mcpServerAuthConfig() -} - -type MCPServerAuthConfigBoolean bool - -func (MCPServerAuthConfigBoolean) mcpServerAuthConfig() {} - -func (MCPServerAuthConfigRedirectPort) mcpServerAuthConfig() {} - -// Authentication settings with optional redirect port configuration. -// Experimental: MCPServerAuthConfigRedirectPort is part of an experimental API and may -// change or be removed. -type MCPServerAuthConfigRedirectPort struct { - // Fixed port for the OAuth redirect callback server. - RedirectPort *int32 `json:"redirectPort,omitempty"` -} - -// MCP server configuration (stdio process or remote HTTP/SSE) -// Experimental: MCPServerConfig is part of an experimental API and may change or be removed. -type MCPServerConfig interface { - mcpServerConfig() +// Serializable MCP server configuration (stdio process or remote HTTP/SSE) +// Experimental: MCPSerializableServerConfig is part of an experimental API and may change +// or be removed. +type MCPSerializableServerConfig interface { + mcpSerializableServerConfig() } -type RawMCPServerConfigData struct { +type RawMCPSerializableServerConfigData struct { Raw json.RawMessage } -func (RawMCPServerConfigData) mcpServerConfig() {} +func (RawMCPSerializableServerConfigData) mcpSerializableServerConfig() {} // Remote MCP server configuration accessed over HTTP or SSE. // Experimental: MCPServerConfigHTTP is part of an experimental API and may change or be @@ -4738,20 +6345,34 @@ func (RawMCPServerConfigData) mcpServerConfig() {} type MCPServerConfigHTTP struct { // Set to `true` to use defaults, or provide an object with additional auth or OIDC settings. Auth MCPServerAuthConfig `json:"auth,omitempty"` + // Configuration warnings recorded while loading the server. + ConfigWarnings []string `json:"configWarnings,omitzero"` // 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"` + // Whether secret masking is disabled for calls to this server. + DisableSecretMasking *bool `json:"disableSecretMasking,omitempty"` // Set to true to disable persisted MCP tool snapshots for this server. Live tool discovery // is unaffected. DisableToolCache *bool `json:"disableToolCache,omitempty"` + // Optional human-readable server name. + DisplayName *string `json:"displayName,omitempty"` + // Event types this server receives as Copilot notifications. + Events []string `json:"events,omitzero"` + // Tool names excluded after the include filter is applied. + ExcludeTools []string `json:"excludeTools,omitzero"` // Content filtering mode to apply to all tools, or a map of tool name to content filtering // mode. FilterMapping FilterMapping `json:"filterMapping,omitempty"` // HTTP headers to include in requests to the remote MCP server. Headers map[string]string `json:"headers,omitzero"` + // Dynamic-header refresh cache lifetime in milliseconds. + HeadersRefreshTtlMs *int64 `json:"headersRefreshTtlMs,omitempty"` // Whether this server is a built-in fallback used when the user has not configured their // own server. IsDefaultServer *bool `json:"isDefaultServer,omitempty"` + // Copilot notification types this server may send to the host. + Notifications []string `json:"notifications,omitzero"` // OAuth client ID for a pre-registered remote MCP OAuth client. OauthClientID *string `json:"oauthClientId,omitempty"` // OAuth grant type to use when authenticating to the remote MCP server. @@ -4760,7 +6381,19 @@ type MCPServerConfigHTTP struct { OauthPublicClient *bool `json:"oauthPublicClient,omitempty"` // Set to `true` to use defaults, or provide an object with additional auth or OIDC settings. Oidc MCPServerAuthConfig `json:"oidc,omitempty"` - // Timeout in milliseconds for tool calls to this server. + // Telemetry-obfuscation policy for this server's tools. + SafeForTelemetry MCPSafeForTelemetry `json:"safeForTelemetry,omitempty"` + // The origin of this server configuration. + Source *MCPServerSource `json:"source,omitempty"` + // Source file path recorded while loading the config. + SourcePath *string `json:"sourcePath,omitempty"` + // Plugin that provided this server. + SourcePlugin *string `json:"sourcePlugin,omitempty"` + // Whether the providing plugin uses the Open Plugin Spec. + SourcePluginSpec *bool `json:"sourcePluginSpec,omitempty"` + // Version of the plugin that provided this server. + SourcePluginVersion *string `json:"sourcePluginVersion,omitempty"` + // Timeout in milliseconds for tool discovery and tool calls. Timeout *int64 `json:"timeout,omitempty"` // Tools to include. Defaults to all tools if not specified. Tools []string `json:"tools,omitzero"` @@ -4770,7 +6403,7 @@ type MCPServerConfigHTTP struct { URL string `json:"url"` } -func (MCPServerConfigHTTP) mcpServerConfig() {} +func (MCPServerConfigHTTP) mcpSerializableServerConfig() {} // Stdio MCP server configuration launched as a child process. // Experimental: MCPServerConfigStdio is part of an experimental API and may change or be @@ -4782,30 +6415,216 @@ type MCPServerConfigStdio struct { Auth MCPServerAuthConfig `json:"auth,omitempty"` // Executable command used to start the Stdio MCP server process. Command string `json:"command"` + // Configuration warnings recorded while loading the server. + ConfigWarnings []string `json:"configWarnings,omitzero"` // Working directory for the Stdio MCP server process. Cwd *string `json:"cwd,omitempty"` // 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"` + // Whether secret masking is disabled for calls to this server. + DisableSecretMasking *bool `json:"disableSecretMasking,omitempty"` // Set to true to disable persisted MCP tool snapshots for this server. Live tool discovery // is unaffected. DisableToolCache *bool `json:"disableToolCache,omitempty"` + // Optional human-readable server name. + DisplayName *string `json:"displayName,omitempty"` // Environment variables to pass to the Stdio MCP server process. Env map[string]string `json:"env,omitzero"` + // Event types this server receives as Copilot notifications. + Events []string `json:"events,omitzero"` + // Tool names excluded after the include filter is applied. + ExcludeTools []string `json:"excludeTools,omitzero"` // Content filtering mode to apply to all tools, or a map of tool name to content filtering // mode. FilterMapping FilterMapping `json:"filterMapping,omitempty"` // Whether this server is a built-in fallback used when the user has not configured their // own server. IsDefaultServer *bool `json:"isDefaultServer,omitempty"` + // Copilot notification types this server may send to the host. + Notifications []string `json:"notifications,omitzero"` // Set to `true` to use defaults, or provide an object with additional auth or OIDC settings. Oidc MCPServerAuthConfig `json:"oidc,omitempty"` - // Timeout in milliseconds for tool calls to this server. + // Telemetry-obfuscation policy for this server's tools. + SafeForTelemetry MCPSafeForTelemetry `json:"safeForTelemetry,omitempty"` + // The origin of this server configuration. + Source *MCPServerSource `json:"source,omitempty"` + // Source file path recorded while loading the config. + SourcePath *string `json:"sourcePath,omitempty"` + // Plugin that provided this server. + SourcePlugin *string `json:"sourcePlugin,omitempty"` + // Whether the providing plugin uses the Open Plugin Spec. + SourcePluginSpec *bool `json:"sourcePluginSpec,omitempty"` + // Version of the plugin that provided this server. + SourcePluginVersion *string `json:"sourcePluginVersion,omitempty"` + // Timeout in milliseconds for tool discovery and tool calls. + Timeout *int64 `json:"timeout,omitempty"` + // Tools to include. Defaults to all tools if not specified. + Tools []string `json:"tools,omitzero"` + // Local transport type. Defaults to stdio when omitted. + Type *MCPServerConfigStdioType `json:"type,omitempty"` +} + +func (MCPServerConfigStdio) mcpSerializableServerConfig() {} + +// MCP server status entry, including config source/plugin source and any connection error. +// Experimental: MCPServer is part of an experimental API and may change or be removed. +type MCPServer struct { + // Error message if the server failed to connect + Error *string `json:"error,omitempty"` + // Server name (config key) + Name string `json:"name"` + // Configuration source: user, workspace, plugin, or builtin + Source *MCPServerSource `json:"source,omitempty"` + // Plugin name that provided this server, when source is plugin. + 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, stopped, or + // not_configured + Status MCPServerStatus `json:"status"` +} + +// Set to `true` to use defaults, or provide an object with additional auth or OIDC settings. +// Experimental: MCPServerAuthConfig is part of an experimental API and may change or be +// removed. +type MCPServerAuthConfig interface { + mcpServerAuthConfig() +} + +type MCPServerAuthConfigBoolean bool + +func (MCPServerAuthConfigBoolean) mcpServerAuthConfig() {} + +func (MCPServerAuthConfigRedirectPort) mcpServerAuthConfig() {} + +// Authentication settings with optional redirect port configuration. +// Experimental: MCPServerAuthConfigRedirectPort is part of an experimental API and may +// change or be removed. +type MCPServerAuthConfigRedirectPort struct { + // Fixed port for the OAuth redirect callback server. + RedirectPort *int32 `json:"redirectPort,omitempty"` +} + +// A card supplied directly by the caller. Exactly one of a URL or embedded data, encoded +// structurally so neither both nor neither can be expressed. +// Experimental: MCPServerCardReference is part of an experimental API and may change or be +// removed. +type MCPServerCardReference interface { + mcpServerCardReference() + Kind() MCPServerCardReferenceKind +} + +type RawMCPServerCardReferenceData struct { + Discriminator MCPServerCardReferenceKind + Raw json.RawMessage +} + +func (RawMCPServerCardReferenceData) mcpServerCardReference() {} +func (r RawMCPServerCardReferenceData) Kind() MCPServerCardReferenceKind { + return r.Discriminator +} + +// An MCP server card supplied inline as an inert document. +// Experimental: MCPServerCardEmbedded is part of an experimental API and may change or be +// removed. +type MCPServerCardEmbedded struct { + // The card document verbatim, treated as inert untrusted bytes. The runtime parses and + // validates it; the host is not expected to interpret it. Never logged. + Data string `json:"data"` + // Media type the card is expected to conform to. + MediaType MCPServerCardMediaType `json:"mediaType"` +} + +func (MCPServerCardEmbedded) mcpServerCardReference() {} +func (MCPServerCardEmbedded) Kind() MCPServerCardReferenceKind { + return MCPServerCardReferenceKindEmbedded +} + +// An MCP server card to be retrieved from a URL through the runtime's hardened fetch +// boundary. +// Experimental: MCPServerCardURL is part of an experimental API and may change or be +// removed. +type MCPServerCardURL struct { + // Media type the card is expected to conform to. + MediaType MCPServerCardMediaType `json:"mediaType"` + // Card URL. Retrieved only through the runtime's hardened boundary, with scheme, + // credential, address-range, redirect, timeout, and response-size controls applied. Never + // logged. + URL string `json:"url"` +} + +func (MCPServerCardURL) mcpServerCardReference() {} +func (MCPServerCardURL) Kind() MCPServerCardReferenceKind { + return MCPServerCardReferenceKindURL +} + +// MCP server configuration (stdio, remote HTTP/SSE, or in-process) +// Experimental: MCPServerConfig is part of an experimental API and may change or be removed. +type MCPServerConfig interface { + mcpServerConfig() +} + +type RawMCPServerConfigData struct { + Raw json.RawMessage +} + +func (RawMCPServerConfigData) mcpServerConfig() {} +func (MCPServerConfigHTTP) mcpServerConfig() {} + +// In-process MCP server configuration used by embedded SDK clients. +// Experimental: MCPServerConfigMemory is part of an experimental API and may change or be +// removed. +type MCPServerConfigMemory struct { + // Configuration warnings recorded while loading the server. + ConfigWarnings []string `json:"configWarnings,omitzero"` + // Controls whether tools can be loaded on demand. + DeferTools *MCPServerConfigDeferTools `json:"deferTools,omitempty"` + // Whether secret masking is disabled for calls to this server. + DisableSecretMasking *bool `json:"disableSecretMasking,omitempty"` + // Whether persisted tool snapshots are disabled. + DisableToolCache *bool `json:"disableToolCache,omitempty"` + // Optional human-readable server name. + DisplayName *string `json:"displayName,omitempty"` + // Event types this server receives as Copilot notifications. + Events []string `json:"events,omitzero"` + // Tool names excluded after the include filter is applied. + ExcludeTools []string `json:"excludeTools,omitzero"` + // Content filtering mode to apply to this server's tools. + FilterMapping FilterMapping `json:"filterMapping,omitempty"` + // Whether this server is a built-in fallback. + IsDefaultServer *bool `json:"isDefaultServer,omitempty"` + // Copilot notification types this server may send to the host. + Notifications []string `json:"notifications,omitzero"` + // Set to `true` to use default OIDC settings. + Oidc MCPServerAuthConfig `json:"oidc,omitempty"` + // Telemetry-obfuscation policy for this server's tools. + SafeForTelemetry MCPSafeForTelemetry `json:"safeForTelemetry,omitempty"` + // In-process MCP server instance. This value cannot cross a JSON-RPC boundary. + // Internal: ServerInstance is part of the SDK's internal API surface and is not intended + // for external use. + ServerInstance any `json:"serverInstance"` + // The origin of this server configuration. + Source *MCPServerSource `json:"source,omitempty"` + // Source file path recorded while loading the config. + SourcePath *string `json:"sourcePath,omitempty"` + // Plugin that provided this server. + SourcePlugin *string `json:"sourcePlugin,omitempty"` + // Whether the providing plugin uses the Open Plugin Spec. + SourcePluginSpec *bool `json:"sourcePluginSpec,omitempty"` + // Version of the plugin that provided this server. + SourcePluginVersion *string `json:"sourcePluginVersion,omitempty"` + // Timeout in milliseconds for tool discovery and tool calls. Timeout *int64 `json:"timeout,omitempty"` // Tools to include. Defaults to all tools if not specified. Tools []string `json:"tools,omitzero"` + // Internal: Type is part of the SDK's internal API surface and is not intended for external + // use. + Type MCPServerConfigMemoryType `json:"type"` } +func (MCPServerConfigMemory) mcpServerConfig() {} + func (MCPServerConfigStdio) mcpServerConfig() {} // Recorded MCP server connection failure. @@ -4862,7 +6681,7 @@ type MCPSetEnvValueModeResult struct { type MCPStartServerRequest struct { // 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"` + Config MCPSerializableServerConfig `json:"config,omitempty"` // Name of the MCP server to start ServerName string `json:"serverName"` } @@ -4873,6 +6692,8 @@ type MCPStartServerRequest struct { type MCPStartServersResult struct { // Non-default servers allowed by policy AllowedServers []MCPAllowedServer `json:"allowedServers,omitzero"` + // Servers whose connection attempt failed. + FailedServers []MCPFailedServer `json:"failedServers,omitzero"` // Servers filtered out before startup FilteredServers []MCPFilteredServer `json:"filteredServers"` } @@ -4885,6 +6706,13 @@ type MCPStopServerRequest struct { ServerName string `json:"serverName"` } +// Metadata controlling an MCP task's lifetime. +// Experimental: MCPTaskMetadata is part of an experimental API and may change or be removed. +type MCPTaskMetadata struct { + // Task time-to-live. + Ttl *int64 `json:"ttl,omitempty"` +} + // MCP tool metadata with tool name, optional description, and normalized MCP Apps discovery // metadata. // Experimental: MCPTools is part of an experimental API and may change or be removed. @@ -5097,8 +6925,14 @@ 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"` + // Informational notices the service published for this model, such as an upcoming change or + // a recommended alternative. Present only when the service published at least one notice. + // Hosts should surface these without implying anything is wrong with the model. + InfoMessages []ModelMessage `json:"infoMessages,omitzero"` // Model capability category for grouping in the model picker ModelPickerCategory *ModelPickerCategory `json:"modelPickerCategory,omitempty"` // Relative cost tier for token-based billing users @@ -5107,8 +6941,44 @@ type Model struct { Name string `json:"name"` // Policy state (if applicable) Policy *ModelPolicy `json:"policy,omitempty"` + // Context-window tiers this model offers, when the provider advertises them independently + // of tiered token pricing. Copilot models carry their tiers in `billing.tokenPrices`; a + // provider that has no pricing to publish (an agent host reached over AHP, for example) + // declares them here instead, so the model picker can still offer the tier toggle. + SupportedContextTiers []string `json:"supportedContextTiers,omitzero"` // Supported reasoning effort levels (only present if model supports reasoning effort) SupportedReasoningEfforts []string `json:"supportedReasoningEfforts,omitzero"` + // Warnings the service published for this model, such as a deprecated client version. + // Present only when the service published at least one warning. The model remains usable; + // hosts should surface these as advisory rather than blocking. + WarningMessages []ModelMessage `json:"warningMessages,omitzero"` + // Warning text the service requires hosts to surface for this model. Present only when the + // service published at least one warning. + WarningText *ModelWarningText `json:"warningText,omitempty"` +} + +// Managed, repository, and CLI model overrides to overlay onto the session at startup. +// Experimental: ModelApplyStartupOverlayRequest is part of an experimental API and may +// change or be removed. +type ModelApplyStartupOverlayRequest struct { + // Model explicitly selected by the CLI, when provided. + CLIModel *string `json:"cliModel,omitempty"` + // Whether the overlay is being applied while resuming a deferred session. + DeferredResume *bool `json:"deferredResume,omitempty"` + // Model required by device-managed policy, when configured. + DeviceManagedModel *string `json:"deviceManagedModel,omitempty"` + // Startup default model from the enterprise policy helper, when configured. Weakest of the + // managed sources: it applies only when neither device nor server policy names a model, and + // an explicit user selection still wins. + PolicyHelperModel *string `json:"policyHelperModel,omitempty"` + // Context tier selected by repository settings, when configured. + RepoContextTier *string `json:"repoContextTier,omitempty"` + // Model selected by repository settings, when configured. + RepoModel *string `json:"repoModel,omitempty"` + // Reasoning effort selected by repository settings, when configured. + RepoReasoningEffort *string `json:"repoReasoningEffort,omitempty"` + // Model required by server-managed policy, when configured. + ServerManagedModel *string `json:"serverManagedModel,omitempty"` } // Billing information @@ -5142,6 +7012,12 @@ type ModelBillingPromo struct { // Human-readable promotion message. Does not include the expiry timestamp; consumers may // format endsAt and append it when present. Message *string `json:"message,omitempty"` + // Whether the service asked hosts to give this promotion a prominent surface, such as a + // dedicated banner, in addition to listing it with the model. `true` requests that surface + // and `false` asks for the model list only. Absent means the service expressed no + // preference — for example a response that predates the field — so hosts should apply their + // own default rather than read it as `false`. + ShowBanner *bool `json:"showBanner,omitempty"` } // Token-level pricing information for this model @@ -5155,6 +7031,8 @@ type ModelBillingTokenPrices struct { CachePrice *float64 `json:"cachePrice,omitempty"` // AI Credits cost per billing batch of cached (read) tokens CacheReadPrice *float64 `json:"cacheReadPrice,omitempty"` + // AI Credits cost per billing batch of 1-hour cache-write (cache creation) tokens. + CacheWrite1hPrice *float64 `json:"cacheWrite1hPrice,omitempty"` // AI Credits cost per billing batch of cache-write (cache creation) tokens. CacheWritePrice *float64 `json:"cacheWritePrice,omitempty"` // Use maxPromptTokens instead. Prompt token budget for the default tier. The total context @@ -5181,6 +7059,8 @@ type ModelBillingTokenPricesLongContext struct { CachePrice *float64 `json:"cachePrice,omitempty"` // AI Credits cost per billing batch of cached (read) tokens CacheReadPrice *float64 `json:"cacheReadPrice,omitempty"` + // AI Credits cost per billing batch of 1-hour cache-write (cache creation) tokens. + CacheWrite1hPrice *float64 `json:"cacheWrite1hPrice,omitempty"` // AI Credits cost per billing batch of cache-write (cache creation) tokens. CacheWritePrice *float64 `json:"cacheWritePrice,omitempty"` // Use maxPromptTokens instead. Prompt token budget for the long context tier. The total @@ -5307,6 +7187,41 @@ type ModelListRequest struct { SkipCache *bool `json:"skipCache,omitempty"` } +// A service-published message about a model, carrying a stable machine-readable code +// alongside human-readable text. +// Experimental: ModelMessage is part of an experimental API and may change or be removed. +type ModelMessage struct { + // Stable machine-readable identifier for the message, such as `client_version_deprecated`. + // Hosts can key custom presentation off this; unrecognized codes should fall back to + // displaying `message`. + Code string `json:"code"` + // Human-readable message text intended for display to the user. + Message string `json:"message"` +} + +// Experimental: ModelPickerPersistenceRequest is part of an experimental API and may change +// or be removed. +type ModelPickerPersistenceRequest struct { + // Whether context tier was explicitly selected and should be persisted. + ContextTierExplicit *bool `json:"contextTierExplicit,omitempty"` + // Whether reasoning effort was explicitly selected and should be persisted. + ReasoningEffortExplicit *bool `json:"reasoningEffortExplicit,omitempty"` + // Filesystem and environment context used to resolve settings persistence. + SettingsContext ModelPickerSettingsContext `json:"settingsContext"` +} + +// Filesystem and environment context used to resolve model-picker settings. +// Experimental: ModelPickerSettingsContext is part of an experimental API and may change or +// be removed. +type ModelPickerSettingsContext struct { + // Optional Copilot configuration directory containing persisted settings. + ConfigDir *string `json:"configDir,omitempty"` + // Environment variables consulted while resolving model-picker settings. + Environment any `json:"environment"` + // User home directory used when resolving persisted settings. + HomeDirectory string `json:"homeDirectory"` +} + // Policy state (if applicable) // Experimental: ModelPolicy is part of an experimental API and may change or be removed. type ModelPolicy struct { @@ -5338,9 +7253,23 @@ type ModelSetReasoningEffortResult struct { // Experimental: ModelsListRequest is part of an experimental API and may change or be // removed. type ModelsListRequest struct { - // 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. + // GitHub token accepted for compatibility with existing SDK clients. When provided, + // resolves this token instead of using the current account. GitHubToken *string `json:"gitHubToken,omitempty"` + // Opaque account identifier returned by `account.getAllUsers`. When omitted, the current + // account is used. + SelectionID *string `json:"selectionId,omitempty"` +} + +// Experimental: ModelSwitchConfirmation is part of an experimental API and may change or be +// removed. +type ModelSwitchConfirmation struct { + // Current conversation token count before switching models. + CurrentTokens float64 `json:"currentTokens"` + // Target model token limit used by the compaction preflight. + TargetLimit float64 `json:"targetLimit"` + // Display name of the model that requires compaction confirmation. + TargetModelDisplayName string `json:"targetModelDisplayName"` } // Target model identifier and optional reasoning effort, summary, capability overrides, and @@ -5348,6 +7277,9 @@ type ModelsListRequest struct { // Experimental: ModelSwitchToRequest is part of an experimental API and may change or be // removed. type ModelSwitchToRequest struct { + // Explicit response to a model-switch compaction preflight. Omit to request a confirmation + // projection when compaction is necessary. + CompactionDecision *string `json:"compactionDecision,omitempty"` // 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"` @@ -5359,16 +7291,29 @@ type ModelSwitchToRequest struct { DeferIfModelChangeQueued *bool `json:"deferIfModelChangeQueued,omitempty"` // Override individual model capabilities resolved by the runtime ModelCapabilities *ModelCapabilitiesOverride `json:"modelCapabilities,omitempty"` + // Settings scope used when persisting the selected model. + ModelChangeScope *string `json:"modelChangeScope,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"` + // Optional settings context and explicit-override flags used to persist a picker selection. + PickerPersistence *ModelPickerPersistenceRequest `json:"pickerPersistence,omitempty"` // 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"` + // Optional repository settings scope to persist after the switch commits. + RepoScope *string `json:"repoScope,omitempty"` + // Require the target to be currently available and enabled before applying the switch. + RequireAvailable *bool `json:"requireAvailable,omitempty"` + // When true, evaluate context-window compaction policy before applying the switch. + RunCompactionPreflight *bool `json:"runCompactionPreflight,omitempty"` + // Origin to record on the effective `session.model_change` event for trusted in-process + // calls. Transport SDK calls are always recorded as `sdk`, regardless of this value. + Source *ModelChangeSource `json:"source,omitempty"` // Output verbosity level to request for supported models Verbosity *Verbosity `json:"verbosity,omitempty"` } @@ -5377,55 +7322,119 @@ type ModelSwitchToRequest struct { // Experimental: ModelSwitchToResult is part of an experimental API and may change or be // removed. type ModelSwitchToResult struct { + // Compaction confirmation projection when status is confirmation_required + Confirmation *ModelSwitchConfirmation `json:"confirmation,omitempty"` // 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"` + // Deprecation warnings associated with the selected model or options. + DeprecationWarnings []string `json:"deprecationWarnings,omitzero"` + // User-facing outcome message for the model switch. + Message *string `json:"message,omitempty"` // Currently active model identifier after the switch ModelID *string `json:"modelId,omitempty"` + // Persistence failure encountered after applying the model switch. + PersistenceError *string `json:"persistenceError,omitempty"` + // Lifecycle result for the requested switch + Status *string `json:"status,omitempty"` + // User-facing warning produced while applying the model switch. + Warning *string `json:"warning,omitempty"` +} + +// Service-published warning text that hosts should display when presenting a model. +// Experimental: ModelWarningText is part of an experimental API and may change or be +// removed. +type ModelWarningText struct { + // Data-retention warning for the model. The text may contain Markdown links and should be + // rendered as Markdown when supported. + DataRetention *string `json:"dataRetention,omitempty"` } // Agent interaction mode to apply to the session. // Experimental: ModeSetRequest is part of an experimental API and may change or be removed. type ModeSetRequest struct { + // Explicit response to a model-switch compaction preflight. + CompactionDecision *string `json:"compactionDecision,omitempty"` + // Session whose plan-mode base state should be inherited. + InheritPlanBaseFromSessionID *string `json:"inheritPlanBaseFromSessionId,omitempty"` // The session mode the agent is operating in Mode SessionMode `json:"mode"` + // Whether the selected plan model should be persisted. + PersistPlanSelection *bool `json:"persistPlanSelection,omitempty"` + // Settings context used when persisting the selected plan model. + PickerSettingsContext *ModelPickerSettingsContext `json:"pickerSettingsContext,omitempty"` + // Context tier to use with the dedicated plan model. + PlanContextTier *string `json:"planContextTier,omitempty"` + // Action to perform when leaving plan mode. + PlanExitAction *string `json:"planExitAction,omitempty"` + // Dedicated model to use in plan mode, when configured. + PlanModel *string `json:"planModel,omitempty"` + // Whether a dedicated plan model is configured. + PlanModelConfigured *bool `json:"planModelConfigured,omitempty"` + // Reasoning effort to use with the dedicated plan model. + PlanReasoningEffort *string `json:"planReasoningEffort,omitempty"` + // Whether leaving plan mode should restore the session's previous model. + RestorePlanModel *bool `json:"restorePlanModel,omitempty"` +} + +// Outcome of a session mode change, including any model switch it triggered and follow-up +// the host must perform. +// Experimental: ModeSetResult is part of an experimental API and may change or be removed. +type ModeSetResult struct { + // Whether the host should arm an interactive continuation after the mode change. + ArmInteractiveContinuation *bool `json:"armInteractiveContinuation,omitempty"` + // Compaction confirmation required before the mode change can complete. + Confirmation *ModelSwitchConfirmation `json:"confirmation,omitempty"` + // Whether the host must defer implementing the requested mode change. + DeferImplementation *bool `json:"deferImplementation,omitempty"` + // Deprecation warnings associated with the model selected by the mode change. + DeprecationWarnings []string `json:"deprecationWarnings,omitzero"` + // User-facing outcome message for the model switch triggered by the mode change. + Message *string `json:"message,omitempty"` + // Whether applying the mode changed the active model. + ModelChanged bool `json:"modelChanged"` + // Lifecycle status of the requested mode change. + Status string `json:"status"` + // User-facing warning produced while applying the mode change. + Warning *string `json:"warning,omitempty"` +} + +// Result of moving in-flight MCP loading to the background. +// Experimental: MoveMCPLoadingToBackgroundResult is part of an experimental API and may +// change or be removed. +type MoveMCPLoadingToBackgroundResult struct { + // Whether an in-flight MCP load was moved to the background, releasing turns that were + // waiting on it. False when no MCP load was in flight or the waiting turns had already been + // released. + MovedToBackground bool `json:"movedToBackground"` } -// A named BYOK provider connection (transport + credentials). +// External SDK input for a named custom model provider. Ingested by the native protocol +// boundary before host dispatch. // Experimental: NamedProviderConfig is part of an experimental API and may change or be // removed. type NamedProviderConfig struct { - // API key. Optional for local providers like Ollama. + // Static API key used to authenticate provider requests. APIKey *string `json:"apiKey,omitempty"` - // Azure-specific provider options. + // Azure authentication configuration for the provider. Azure *ProviderConfigAzure `json:"azure,omitempty"` - // API endpoint URL. + // Base URL for provider API requests. BaseURL string `json:"baseUrl"` - // Bearer token for authentication. Sets the Authorization header directly. Takes precedence - // over apiKey when both are set. + // Static bearer token used to authenticate provider requests. BearerToken *string `json:"bearerToken,omitempty"` - // 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. + // Whether the host supplies bearer tokens dynamically. HasBearerTokenProvider *bool `json:"hasBearerTokenProvider,omitempty"` - // Custom HTTP headers to include in all outbound requests to the provider. + // Additional HTTP headers included with provider requests. Headers map[string]string `json:"headers,omitzero"` - // Stable identifier referenced by BYOK model definitions. Must not contain '/'. + // Unique provider name used to qualify model selection IDs. Name string `json:"name"` - // Provider transport. Defaults to "http". + // Transport used to communicate with the provider. Transport *ProviderConfigTransport `json:"transport,omitempty"` - // Provider type. Defaults to "openai" for generic OpenAI-compatible APIs. + // Provider protocol family. Type *ProviderConfigType `json:"type,omitempty"` - // Wire API format (openai/azure only). Defaults to "completions". + // Wire API used to communicate with the provider. WireAPI *ProviderConfigWireAPI `json:"wireApi,omitempty"` } @@ -5491,8 +7500,10 @@ type OpenCanvasInstance struct { // Experimental: OptionsUpdateAdditionalContentExclusionPolicy is part of an experimental // API and may change or be removed. type OptionsUpdateAdditionalContentExclusionPolicy struct { - LastUpdatedAt any `json:"last_updated_at"` - Rules []OptionsUpdateAdditionalContentExclusionPolicyRule `json:"rules"` + // Opaque policy update timestamp supplied by the host. + LastUpdatedAt any `json:"last_updated_at"` + // Content-exclusion rules to apply. + Rules []OptionsUpdateAdditionalContentExclusionPolicyRule `json:"rules"` // Allowed values for the `OptionsUpdateAdditionalContentExclusionPolicyScope` enumeration. Scope OptionsUpdateAdditionalContentExclusionPolicyScope `json:"scope"` } @@ -5502,9 +7513,12 @@ type OptionsUpdateAdditionalContentExclusionPolicy struct { // Experimental: OptionsUpdateAdditionalContentExclusionPolicyRule is part of an // experimental API and may change or be removed. type OptionsUpdateAdditionalContentExclusionPolicyRule struct { - IfAnyMatch []string `json:"ifAnyMatch,omitzero"` + // Conditions of which at least one must match. + IfAnyMatch []string `json:"ifAnyMatch,omitzero"` + // Conditions none of which may match. IfNoneMatch []string `json:"ifNoneMatch,omitzero"` - Paths []string `json:"paths"` + // Path patterns covered by this rule. + Paths []string `json:"paths"` // Source descriptor for a `session.options.update` content-exclusion rule, with source name // and type. Source OptionsUpdateAdditionalContentExclusionPolicyRuleSource `json:"source"` @@ -5515,7 +7529,9 @@ type OptionsUpdateAdditionalContentExclusionPolicyRule struct { // Experimental: OptionsUpdateAdditionalContentExclusionPolicyRuleSource is part of an // experimental API and may change or be removed. type OptionsUpdateAdditionalContentExclusionPolicyRuleSource struct { + // Name of the policy source. Name string `json:"name"` + // Type of the policy source. Type string `json:"type"` } @@ -5819,6 +7835,24 @@ func (PermissionDecisionApproveForLocationApprovalCustomTool) Kind() PermissionD return PermissionDecisionApproveForLocationApprovalKindCustomTool } +// Location-scoped approval details for an extension's access to sensitive environment +// variables, keyed by extension name and the exact set of variable names. +// Experimental: PermissionDecisionApproveForLocationApprovalExtensionEnvAccess is part of +// an experimental API and may change or be removed. +type PermissionDecisionApproveForLocationApprovalExtensionEnvAccess struct { + // Names of the sensitive environment variables this approval covers. Values are never + // persisted. + EnvironmentVariables []string `json:"environmentVariables"` + // Extension name. + ExtensionName string `json:"extensionName"` +} + +func (PermissionDecisionApproveForLocationApprovalExtensionEnvAccess) permissionDecisionApproveForLocationApproval() { +} +func (PermissionDecisionApproveForLocationApprovalExtensionEnvAccess) Kind() PermissionDecisionApproveForLocationApprovalKind { + return PermissionDecisionApproveForLocationApprovalKindExtensionEnvAccess +} + // Location-scoped approval details for extension-management operations, optionally narrowed // by operation. // Experimental: PermissionDecisionApproveForLocationApprovalExtensionManagement is part of @@ -5979,6 +8013,24 @@ func (PermissionDecisionApproveForSessionApprovalCustomTool) Kind() PermissionDe return PermissionDecisionApproveForSessionApprovalKindCustomTool } +// Session-scoped approval details for an extension's access to sensitive environment +// variables, keyed by extension name and the exact set of variable names. +// Experimental: PermissionDecisionApproveForSessionApprovalExtensionEnvAccess is part of an +// experimental API and may change or be removed. +type PermissionDecisionApproveForSessionApprovalExtensionEnvAccess struct { + // Names of the sensitive environment variables this approval covers. Values are never + // persisted. + EnvironmentVariables []string `json:"environmentVariables"` + // Extension name. + ExtensionName string `json:"extensionName"` +} + +func (PermissionDecisionApproveForSessionApprovalExtensionEnvAccess) permissionDecisionApproveForSessionApproval() { +} +func (PermissionDecisionApproveForSessionApprovalExtensionEnvAccess) Kind() PermissionDecisionApproveForSessionApprovalKind { + return PermissionDecisionApproveForSessionApprovalKindExtensionEnvAccess +} + // Session-scoped approval details for extension-management operations, optionally narrowed // by operation. // Experimental: PermissionDecisionApproveForSessionApprovalExtensionManagement is part of @@ -6091,10 +8143,29 @@ 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"` + // Whether the responding client could ask a user interactively, was running headlessly, or + // had no response path. Omit when the client cannot determine this authoritatively. + ResponseCapability *PermissionResponseCapability `json:"responseCapability,omitempty"` + // 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 @@ -6160,7 +8231,9 @@ type PermissionLocationResolveResult struct { // be removed. type PermissionPathsAddParams struct { // Directory to add to the allow-list. The runtime resolves and validates the path before - // adding. + // adding, then loads conventional `.github/skills/` and `.github/agents/` definitions under + // it when their subsystem gates are enabled. Adding the directory is therefore also a trust + // decision for configuration stored there. Path string `json:"path"` } @@ -6187,9 +8260,11 @@ type PermissionPathsAllowedCheckResult struct { // removed. type PermissionPathsConfig struct { // 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). + // directory). Conventional `.github/skills/` and `.github/agents/` definitions under them + // also join the session catalogs when their subsystem gates are enabled, so supplying a + // directory is a trust decision for configuration stored there. When `unrestricted` is + // true, these are still pre-populated on the UnrestrictedPathManager so they remain visible + // via getDirectories() (e.g. for @-mention completion). AdditionalDirectories []string `json:"additionalDirectories,omitzero"` // Whether to include the system temp directory in the allowed list (defaults to true). // Ignored when `unrestricted` is true. @@ -6282,8 +8357,10 @@ type PermissionRulesSet struct { // Experimental: PermissionsConfigureAdditionalContentExclusionPolicy is part of an // experimental API and may change or be removed. type PermissionsConfigureAdditionalContentExclusionPolicy struct { - LastUpdatedAt any `json:"last_updated_at"` - Rules []PermissionsConfigureAdditionalContentExclusionPolicyRule `json:"rules"` + // Opaque policy update timestamp supplied by the host. + LastUpdatedAt any `json:"last_updated_at"` + // Content-exclusion rules to apply. + Rules []PermissionsConfigureAdditionalContentExclusionPolicyRule `json:"rules"` // Allowed values for the `PermissionsConfigureAdditionalContentExclusionPolicyScope` // enumeration. Scope PermissionsConfigureAdditionalContentExclusionPolicyScope `json:"scope"` @@ -6294,9 +8371,12 @@ type PermissionsConfigureAdditionalContentExclusionPolicy struct { // Experimental: PermissionsConfigureAdditionalContentExclusionPolicyRule is part of an // experimental API and may change or be removed. type PermissionsConfigureAdditionalContentExclusionPolicyRule struct { - IfAnyMatch []string `json:"ifAnyMatch,omitzero"` + // Conditions of which at least one must match. + IfAnyMatch []string `json:"ifAnyMatch,omitzero"` + // Conditions none of which may match. IfNoneMatch []string `json:"ifNoneMatch,omitzero"` - Paths []string `json:"paths"` + // Path patterns covered by this rule. + Paths []string `json:"paths"` // Source descriptor for a `session.permissions.configure` content-exclusion rule, with // source name and type. Source PermissionsConfigureAdditionalContentExclusionPolicyRuleSource `json:"source"` @@ -6307,7 +8387,9 @@ type PermissionsConfigureAdditionalContentExclusionPolicyRule struct { // Experimental: PermissionsConfigureAdditionalContentExclusionPolicyRuleSource is part of // an experimental API and may change or be removed. type PermissionsConfigureAdditionalContentExclusionPolicyRuleSource struct { + // Name of the policy source. Name string `json:"name"` + // Type of the policy source. Type string `json:"type"` } @@ -6355,9 +8437,17 @@ type PermissionsFolderTrustAddTrustedResult struct { } // No parameters. -// Experimental: PermissionsGetAllowAllRequest is part of an experimental API and may change -// or be removed. -type PermissionsGetAllowAllRequest struct { +// Experimental: PermissionsGetModeRequest is part of an experimental API and may change or +// be removed. +type PermissionsGetModeRequest struct { +} + +// Current permission mode. +// Experimental: PermissionsGetModeResult is part of an experimental API and may change or +// be removed. +type PermissionsGetModeResult struct { + // Current permission mode + Mode PermissionMode `json:"mode"` } // Tool approval to persist and apply @@ -6407,6 +8497,24 @@ func (PermissionsLocationsAddToolApprovalDetailsCustomTool) Kind() PermissionsLo return PermissionsLocationsAddToolApprovalDetailsKindCustomTool } +// Location-persisted tool approval details for an extension's access to sensitive +// environment variables, keyed by extension name and the exact set of variable names. +// Experimental: PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess is part of an +// experimental API and may change or be removed. +type PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess struct { + // Names of the sensitive environment variables this approval covers. Values are never + // persisted. + EnvironmentVariables []string `json:"environmentVariables"` + // Extension name. + ExtensionName string `json:"extensionName"` +} + +func (PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess) permissionsLocationsAddToolApprovalDetails() { +} +func (PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess) Kind() PermissionsLocationsAddToolApprovalDetailsKind { + return PermissionsLocationsAddToolApprovalDetailsKindExtensionEnvAccess +} + // Location-persisted tool approval details for extension-management operations, optionally // narrowed by operation. // Experimental: PermissionsLocationsAddToolApprovalDetailsExtensionManagement is part of an @@ -6602,24 +8710,6 @@ type PermissionsResetSessionApprovalsResult struct { Success bool `json:"success"` } -// Allow-all mode to apply for the session. -// Experimental: PermissionsSetAllowAllRequest is part of an experimental API and may change -// or be removed. -type PermissionsSetAllowAllRequest struct { - // 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"`. - Enabled *bool `json:"enabled,omitempty"` - // Allow-all mode to apply. `on` enables full allow-all; `auto` enables advisory LLM - // 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 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"` -} - // Allow-all toggle for tool permission requests, with an optional telemetry source. // Experimental: PermissionsSetApproveAllRequest is part of an experimental API and may // change or be removed. @@ -6638,6 +8728,32 @@ type PermissionsSetApproveAllResult struct { Success bool `json:"success"` } +// Permission mode to apply for the session. +// Experimental: PermissionsSetModeRequest is part of an experimental API and may change or +// be removed. +type PermissionsSetModeRequest struct { + // Optional judge model id for assisted mode. When omitted, the session resolves the + // provider default: `gpt-5.5` for CAPI sessions and the active session model for BYOK + // sessions. + AssistedApprovalModel *string `json:"assistedApprovalModel,omitempty"` + // Permission mode to apply + Mode PermissionMode `json:"mode"` + // Optional source for permission-mode telemetry. Defaults to `rpc` when omitted for SDK + // callers. + Source *PermissionModeSource `json:"source,omitempty"` +} + +// Indicates whether the requested permission mode was applied and reports the authoritative +// post-mutation mode. +// Experimental: PermissionsSetModeResult is part of an experimental API and may change or +// be removed. +type PermissionsSetModeResult struct { + // Authoritative permission mode after the mutation + Mode PermissionMode `json:"mode"` + // Whether the operation succeeded + Success bool `json:"success"` +} + // Toggles whether permission prompts should be bridged into session events for this client. // Experimental: PermissionsSetRequiredRequest is part of an experimental API and may change // or be removed. @@ -6721,7 +8837,8 @@ type PlanReadResult struct { // Experimental: PlanReadSQLTodosResult is part of an experimental API and may change or be // removed. type PlanReadSQLTodosResult struct { - // Rows from the session SQL todos table, ordered by creation time and id. + // Rows from the session SQL todos table, ordered by creation time with insertion order used + // to break ties when available and id used for WITHOUT ROWID tables. Rows []PlanSQLTodosRow `json:"rows"` } @@ -6733,7 +8850,8 @@ type PlanReadSQLTodosWithDependenciesResult struct { // or the SELECT failed. Read independently from `rows`, so a broken todo_deps table does // not affect the rows result and vice versa. Dependencies []PlanSQLTodoDependency `json:"dependencies"` - // Rows from the session SQL todos table, ordered by creation time and id. Empty when no + // Rows from the session SQL todos table, ordered by creation time with insertion order used + // to break ties when available and id used for WITHOUT ROWID tables. Empty when no // database, no todos table, or the SELECT failed. Rows []PlanSQLTodosRow `json:"rows"` } @@ -6753,6 +8871,11 @@ type PlanSQLTodoDependency struct { // because the SQL schema is best-effort and the agent may not have populated every column. // Experimental: PlanSQLTodosRow is part of an experimental API and may change or be removed. type PlanSQLTodosRow struct { + // Todo creation time, as stored by the session SQL schema's `datetime('now')` default: + // `YYYY-MM-DD HH:MM:SS` in UTC. Lets clients attribute todos to the work item that created + // them (e.g. scoping a goal's progress to the todos it produced) rather than to the whole + // session. + CreatedAt *string `json:"createdAt,omitempty"` // Todo description. Description *string `json:"description,omitempty"` // Todo identifier. @@ -6814,6 +8937,20 @@ type PluginListResult struct { Plugins []InstalledPluginInfo `json:"plugins"` } +// Trusted built-in plugin directories to use for this runtime process. +// Experimental: PluginsBuiltinSetRequest is part of an experimental API and may change or +// be removed. +type PluginsBuiltinSetRequest struct { + // Complete replacement set of trusted built-in plugin directories. Every entry must be an + // absolute local filesystem path no longer than 4096 characters. + Paths []string `json:"paths"` +} + +// Experimental: PluginsBuiltinSetResult is part of an experimental API and may change or be +// removed. +type PluginsBuiltinSetResult struct { +} + // Plugin names (or specs) to disable. // Experimental: PluginsDisableRequest is part of an experimental API and may change or be // removed. @@ -6982,6 +9119,31 @@ type PluginUpdateResult struct { SkillsInstalled int64 `json:"skillsInstalled"` } +// Serializable definition of a caller-implemented tool whose execution is handled over the +// SDK connection. +// Experimental: ProtocolExternalToolDefinition is part of an experimental API and may +// change or be removed. +type ProtocolExternalToolDefinition struct { + // Tool-loading deferral policy. + Defer *ProtocolExternalToolDefer `json:"defer,omitempty"` + // Model-visible explanation of what the tool does. + Description string `json:"description"` + // Whether the tool executes commands in a terminal. + IsTerminal *bool `json:"isTerminal,omitempty"` + // Optional caller-defined metadata associated with the tool. + Metadata map[string]any `json:"metadata,omitzero"` + // Unique model-visible tool name. + Name string `json:"name"` + // Whether this definition replaces a built-in tool with the same name. + OverridesBuiltInTool *bool `json:"overridesBuiltInTool,omitempty"` + // JSON Schema describing the tool's input arguments. + Parameters map[string]any `json:"parameters,omitzero"` + // Whether execution bypasses the normal tool permission prompt. + SkipPermission *bool `json:"skipPermission,omitempty"` + // Optional human-readable display title. + Title *string `json:"title,omitempty"` +} + // BYOK providers and/or models to add to the session's registry at runtime. Both fields are // optional; provide providers, models, or both. // Experimental: ProviderAddRequest is part of an experimental API and may change or be @@ -7036,9 +9198,13 @@ type ProviderConfig struct { MaxOutputTokens *float64 `json:"maxOutputTokens,omitempty"` // Maximum prompt/input tokens for the model. MaxPromptTokens *float64 `json:"maxPromptTokens,omitempty"` + // Overrides for model capabilities when they cannot be inferred from modelId. + ModelCapabilities *ModelCapabilitiesOverride `json:"modelCapabilities,omitempty"` // Well-known model ID used for capability lookup. When set, agent behavior config and token // limits are inferred from this model. ModelID *string `json:"modelId,omitempty"` + // Provider name used for model and telemetry attribution. + ProviderName *string `json:"providerName,omitempty"` // Provider transport. Defaults to "http". Transport *ProviderConfigTransport `json:"transport,omitempty"` // Provider type. Defaults to "openai" for generic OpenAI-compatible APIs. @@ -7107,7 +9273,7 @@ type ProviderModelConfig struct { // Display name for model pickers. Defaults to the provider-qualified selection id // (`provider/id`). Name *string `json:"name,omitempty"` - // Name of the NamedProviderConfig that serves this model. + // Name of the configured provider that serves this model. Provider string `json:"provider"` // The model name sent to the provider API for inference. Defaults to `id`. WireModel *string `json:"wireModel,omitempty"` @@ -7136,8 +9302,8 @@ type ProviderSessionToken struct { // Experimental: ProviderTokenAcquireRequest is part of an experimental API and may change // or be removed. type ProviderTokenAcquireRequest struct { - // 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`. + // 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 the configured provider name. ProviderName string `json:"providerName"` // Target session identifier SessionID string `json:"sessionId"` @@ -7589,6 +9755,7 @@ type QueueDeferSessionIdleRequest struct { // Experimental: QueueDuplicateAtRequest is part of an experimental API and may change or be // removed. type QueueDuplicateAtRequest struct { + // Stable opaque ID of the queued item to duplicate. ID string `json:"id"` } @@ -7640,6 +9807,7 @@ type QueueHasPendingResult struct { // Experimental: QueueInsertAtRequest is part of an experimental API and may change or be // removed. type QueueInsertAtRequest struct { + // Queued message contents and delivery metadata. Message QueueInsertMessage `json:"message"` // Zero-based position in the public visible queue. Values outside the queue clamp to an end. Position int64 `json:"position"` @@ -7724,12 +9892,19 @@ type QueuePendingItems struct { ID string `json:"id"` // Whether this item is a queued user message or a queued slash command / model change Kind QueuePendingItemsKind `json:"kind"` + // Stable identity of the queued user message. Present for message rows and absent for slash + // commands and model changes. + MessageID *string `json:"messageId,omitempty"` } // 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 { + // How many leading entries of `steeringMessages` have already been folded into the running + // turn (and so have an emitted `user.message`), as opposed to still waiting for one. Absent + // for hosts that do not distinguish the two. + InFlightSteeringCount *int64 `json:"inFlightSteeringCount,omitempty"` // Pending queued items in submission order. Includes user messages, queued slash commands, // and queued model changes; omits internal system items. Items []QueuePendingItems `json:"items"` @@ -7742,6 +9917,7 @@ type QueuePendingItemsResult struct { // Experimental: QueueRemoveAtRequest is part of an experimental API and may change or be // removed. type QueueRemoveAtRequest struct { + // Stable opaque ID of the queued item to remove. ID string `json:"id"` } @@ -7766,6 +9942,7 @@ type QueueRemoveMostRecentResult struct { // Experimental: QueueSendNowRequest is part of an experimental API and may change or be // removed. type QueueSendNowRequest struct { + // Stable opaque ID of the queued item to steer into the live turn. ID string `json:"id"` } @@ -7786,6 +9963,7 @@ type QueueSendNowResult struct { // Experimental: QueueSetDrainPausedRequest is part of an experimental API and may change or // be removed. type QueueSetDrainPausedRequest struct { + // Whether queued-lane draining should be paused. Paused bool `json:"paused"` } @@ -7807,9 +9985,12 @@ type QueueSnapshotResult struct { // Experimental: QueueUpdateTextRequest is part of an experimental API and may change or be // removed. type QueueUpdateTextRequest struct { + // Optional replacement prompt displayed to the user. DisplayPrompt *string `json:"displayPrompt,omitempty"` - ID string `json:"id"` - Prompt string `json:"prompt"` + // Stable opaque ID of the queued item to edit. + ID string `json:"id"` + // Replacement prompt sent to the model. + Prompt string `json:"prompt"` } // Result of editing a queued message. @@ -7852,16 +10033,19 @@ 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. // Internal: RegisterExtensionToolsParams is an internal SDK API and is not part of the // public surface. type RegisterExtensionToolsParams struct { - // 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. + // In-process ExtensionLoader handle used only by the CLI and excluded from the public SDK + // surface. // Internal: Loader is part of the SDK's internal API surface and is not intended for // external use. Loader any `json:"loader"` @@ -7877,8 +10061,7 @@ type RegisterExtensionToolsParams struct { // Internal: RegisterExtensionToolsResult is an internal SDK API and is not part of the // public surface. type RegisterExtensionToolsResult struct { - // In-process unsubscribe function (CLI-only optimization). Marked internal: replaced by an - // explicit `extensions.unregister` RPC in the SDK migration. + // In-process unsubscribe function used only by the CLI. // Internal: Unsubscribe is part of the SDK's internal API surface and is not intended for // external use. Unsubscribe any `json:"unsubscribe"` @@ -7959,9 +10142,8 @@ type RemoteControlStatusActive struct { // Whether the MC session may steer this session. IsSteerable bool `json:"isSteerable"` // 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. + // excluded from the public SDK surface. Retained as an optional compatibility field; native + // remote control does not populate or consume it. // Internal: PromptManager is part of the SDK's internal API surface and is not intended for // external use. PromptManager any `json:"promptManager,omitempty"` @@ -8116,6 +10298,10 @@ type RemoteSessionRepository struct { type RunOptions struct { // Per-invocation resource ceiling overrides. Limits *FactoryRunLimits `json:"limits,omitempty"` + // Whether to emit factory phase names to the session transcript. + LogPhaseNames *bool `json:"logPhaseNames,omitempty"` + // Whether to notify the originating session when the factory completes. + NotifyOnComplete *bool `json:"notifyOnComplete,omitempty"` // Run identifier whose journal and progress should seed this resumed run. ResumeFromRunID *string `json:"resumeFromRunId,omitempty"` } @@ -8130,26 +10316,43 @@ 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 exporting CARGO_HOME/GOPATH/etc. Default: true (enabled by default; - // set to false to opt out). - AllowDevToolCaches *bool `json:"allowDevToolCaches,omitempty"` + // Whether to auto-grant read access to tool directories discovered on PATH and in toolchain + // environment variables (GOROOT, JAVA_HOME, VIRTUAL_ENV, and similar), and to common + // developer-tool caches, config, and toolchains. Writable grants cover scratch caches, the + // Unix GitHub CLI cache, and Cargo's registry, git store, and lock/tracker files. A + // relocated CARGO_HOME gets the same narrow split: registry and git are read-write; bin is + // read-only; the home root, config.toml, and credentials.toml stay ungranted. Set to false + // to disable every grant listed above; user-installed toolchains and caches then need + // explicit userPolicy.filesystem readonlyPaths and readwritePaths entries. The working + // directory (see addCurrentWorkingDirectory), temporary storage, session log paths, and + // system locations follow their own rules and stay granted. 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. For the same +// capability independent of sandboxing, and matched to the credential's GitHub host, see +// `shell.credentials`; the two are additive. +// 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. @@ -8203,11 +10406,14 @@ 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. + // HTTP proxy for sandboxed process traffic. Linux restricts egress to the proxy endpoint, + // requires that endpoint to be reachable over IPv4 (the [::] dual-stack wildcard is + // accepted and routed through the IPv4 gateway), and does not support proxy credentials. + // macOS relies on applications honoring proxy environment variables. Windows also + // configures a per-AppContainer WinHTTP proxy, but enforcement depends on the application's + // networking stack. Configure supported credentials in the separate `username` and + // `password` fields. A credential-free http:// loopback URL uses the localhost proxy form, + // while an https:// or authenticated loopback URL uses the URL form. Proxy *SandboxConfigUserPolicyNetworkProxy `json:"proxy,omitempty"` } @@ -8223,12 +10429,12 @@ type SandboxConfigUserPolicyNetworkProxy struct { // 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. + // scheme's standard port when omitted; an explicit port must be between 1 and 65535. + // 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 proxy 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. @@ -8243,8 +10449,20 @@ 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 +// Managed sandbox enforcement state for a session. +// Experimental: SandboxEnforcementStatus is part of an experimental API and may change or +// be removed. +type SandboxEnforcementStatus struct { + // Whether an enforcement failure has permanently blocked the session. + Blocked bool `json:"blocked"` + // The first sandbox enforcement failure that blocked the session. + Reason *string `json:"reason,omitempty"` + // Whether the effective managed policy requires an available sandbox backend. + Required bool `json:"required"` +} + +// 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. @@ -8570,6 +10788,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) @@ -8629,6 +10849,47 @@ type SessionAgentListRequest struct { type SessionAgentSetPromptResult struct { } +// Credential-free authentication identity safe to expose to hosts and user interfaces. +// Experimental: SessionAuthInfoResult is part of an experimental API and may change or be +// removed. +type SessionAuthInfoResult struct { + // Snapshot of the authenticated user's Copilot subscription info, if known + CopilotUser *CopilotUserResponse `json:"copilotUser,omitempty"` + // Name of the environment variable that supplied the credential, when applicable + EnvVar *string `json:"envVar,omitempty"` + // Authentication host + Host string `json:"host"` + // Authenticated login, when available + Login *string `json:"login,omitempty"` + // Opaque SDK GitHub credential registration backing this identity. Routing metadata only; + // never a credential. + RegistrationID *string `json:"registrationId,omitempty"` + // Authentication type + Type AuthInfoType `json:"type"` +} + +// Internal GitHub login parameters. +// Experimental: SessionAuthLoginRequest is part of an experimental API and may change or be +// removed. +type SessionAuthLoginRequest struct { + // GitHub host URL + Host string `json:"host"` + // GitHub login + Login string `json:"login"` + // Whether to persist the token after login + Persist *bool `json:"persist,omitempty"` + // GitHub authentication token + Token string `json:"token"` +} + +// Parameters identifying a GitHub authentication to log out. +// Experimental: SessionAuthLogoutUserRequest is part of an experimental API and may change +// or be removed. +type SessionAuthLogoutUserRequest struct { + // Authentication information to log out + AuthInfo AuthInfo `json:"authInfo"` +} + // Authentication status and account metadata for the session. // Experimental: SessionAuthStatus is part of an experimental API and may change or be // removed. @@ -8647,6 +10908,16 @@ type SessionAuthStatus struct { StatusMessage *string `json:"statusMessage,omitempty"` } +// Parameters for switching the session's active authentication. +// Experimental: SessionAuthSwitchRequest is part of an experimental API and may change or +// be removed. +type SessionAuthSwitchRequest struct { + // Authentication information to activate + AuthInfo AuthInfo `json:"authInfo"` + // Optional token paired with the authentication information + Token *string `json:"token,omitempty"` +} + // Map of sessionId -> bytes freed by removing the session's workspace directory. // Experimental: SessionBulkDeleteResult is part of an experimental API and may change or be // removed. @@ -8667,6 +10938,16 @@ type SessionCancelAllBackgroundAgentsResult int64 type SessionCanvasCloseResult struct { } +// Experimental: SessionCanvasProviderRegisterResult is part of an experimental API and may +// change or be removed. +type SessionCanvasProviderRegisterResult struct { +} + +// Experimental: SessionCanvasProviderUnregisterResult is part of an experimental API and +// may change or be removed. +type SessionCanvasProviderUnregisterResult struct { +} + // Experimental: SessionCommandsListRequest is part of an experimental API and may change or // be removed. type SessionCommandsListRequest struct { @@ -8717,17 +10998,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. @@ -9059,8 +11388,10 @@ type SessionFSSqliteQueryResult struct { // Experimental: SessionFSSqliteTransactionError is part of an experimental API and may // change or be removed. type SessionFSSqliteTransactionError struct { + // Machine-readable classification of the transaction failure. ErrorClass SessionFSSqliteTransactionErrorClass `json:"errorClass"` - Message string `json:"message"` + // Human-readable transaction failure message. + Message string `json:"message"` } // Statements to execute atomically. Providers apply busy handling for every call. @@ -9068,7 +11399,8 @@ type SessionFSSqliteTransactionError struct { // change or be removed. type SessionFSSqliteTransactionRequest struct { // Target session identifier - SessionID string `json:"sessionId"` + SessionID string `json:"sessionId"` + // Ordered SQL statements to execute in one transaction. Statements []SessionFSSqliteTransactionStatement `json:"statements"` } @@ -9076,8 +11408,10 @@ type SessionFSSqliteTransactionRequest struct { // 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"` + // Classified transaction failure, when execution did not succeed. + Error *SessionFSSqliteTransactionError `json:"error,omitempty"` + // Per-statement query results in input order. + Results []SessionFSSqliteQueryResult `json:"results"` } // One statement in an atomic SQLite transaction. @@ -9134,6 +11468,26 @@ type SessionFSWriteFileRequest struct { SessionID string `json:"sessionId"` } +// Authentication accounts available to the internal session host. +// Experimental: SessionGitHubAuthGetAllAuthAvailableResult is part of an experimental API +// and may change or be removed. +type SessionGitHubAuthGetAllAuthAvailableResult []SessionAuthStatus + +// Whether the current authentication was logged out. +// Experimental: SessionGitHubAuthLogoutResult is part of an experimental API and may change +// or be removed. +type SessionGitHubAuthLogoutResult bool + +// Whether the requested authentication was logged out. +// Experimental: SessionGitHubAuthLogoutUserResult is part of an experimental API and may +// change or be removed. +type SessionGitHubAuthLogoutUserResult bool + +// Experimental: SessionGitHubAuthSwitchToAuthResult is part of an experimental API and may +// change or be removed. +type SessionGitHubAuthSwitchToAuthResult struct { +} + // Experimental: SessionHistoryCompactRequest is part of an experimental API and may change // or be removed. type SessionHistoryCompactRequest struct { @@ -9164,6 +11518,12 @@ type SessionInstalledPlugin struct { Enabled bool `json:"enabled"` // Installation timestamp (ISO-8601) InstalledAt string `json:"installed_at"` + // Absolute path of the marketplace directory a live plugin was resolved from. Present only + // on live, never-persisted records — those synthesized at session start for a + // directory/local marketplace, whose cache_path points at the real plugin directory on disk + // rather than a copy under the installed-plugins cache. Its presence is what marks a record + // as live, and no record carrying it is ever written to the persisted installedPlugins key. + InstalledFrom *string `json:"installed_from,omitempty"` // Marketplace the plugin came from (empty string for direct repo installs) Marketplace string `json:"marketplace"` // Plugin name @@ -9195,9 +11555,12 @@ type SessionInstalledPluginSource struct { // Experimental: SessionInstalledPluginSourceGitHub is part of an experimental API and may // change or be removed. type SessionInstalledPluginSourceGitHub struct { + // Optional repository-relative path to the plugin. Path *string `json:"path,omitempty"` - Ref *string `json:"ref,omitempty"` - Repo string `json:"repo"` + // Optional Git ref to resolve. + Ref *string `json:"ref,omitempty"` + // GitHub repository in `owner/repo` form. + Repo string `json:"repo"` // Optional full 40-character hexadecimal commit SHA. Sha *string `json:"sha,omitempty"` // Constant value. Always "github". @@ -9208,6 +11571,7 @@ type SessionInstalledPluginSourceGitHub struct { // Experimental: SessionInstalledPluginSourceLocal is part of an experimental API and may // change or be removed. type SessionInstalledPluginSourceLocal struct { + // Local filesystem path to the plugin. Path string `json:"path"` // Constant value. Always "local". Source SessionInstalledPluginSourceLocalSource `json:"source"` @@ -9218,13 +11582,16 @@ type SessionInstalledPluginSourceLocal struct { // Experimental: SessionInstalledPluginSourceURL is part of an experimental API and may // change or be removed. type SessionInstalledPluginSourceURL struct { + // Optional source-relative path to the plugin. Path *string `json:"path,omitempty"` - Ref *string `json:"ref,omitempty"` + // Optional Git ref to resolve. + 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"` + // URL of the plugin source. + URL string `json:"url"` } // Baseline data provenance for a prediction. @@ -9321,7 +11688,8 @@ func (SessionLimitPredictionResultUnavailable) Kind() SessionLimitPredictionResu // change or be removed. type SessionLimitPredictionTierOption struct { // AI-credit cap for this tier. - Cap float64 `json:"cap"` + Cap float64 `json:"cap"` + // Semantic usage tier. Tier SessionLimitPredictionTier `json:"tier"` } @@ -9361,6 +11729,14 @@ func (LocalSessionMetadataValue) sessionListEntryIsRemote() bool { type RemoteSessionMetadataValue struct { // Most recent working directory context. Context *SessionContext `json:"context,omitempty"` + // Host-supplied human description of what the session is doing right now ("running tests", + // "waiting for approval"). Optional in the protocol and absent on hosts that do not publish + // it, so never rely on it -- it enriches `hostStatus`, it does not replace it. + HostActivity *string `json:"hostActivity,omitempty"` + // Live status as the owning host reports it in its session listing, so a row for a session + // running elsewhere can show that it is running. Absent for hosts that publish no such + // status (the cloud task managers), which read as idle. + HostStatus *RemoteSessionHostStatus `json:"hostStatus,omitempty"` // Last-modified time as an ISO 8601 timestamp. ModifiedTime string `json:"modifiedTime"` // Optional human-friendly name set via /rename. @@ -9424,6 +11800,34 @@ 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. `allow-auto-only` + // blocks full allow-all but permits advisory auto-approval. Any other value is accepted + // rather than failing the session, but is enforced as `disable`: the key is only present to + // restrict something, so a mode this runtime cannot interpret fails closed to the most + // restrictive one it knows. Omit the key entirely to impose no restriction. + DisableBypassPermissionsMode *string `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 { + // Managed permission policy injected by the SDK host. + Permissions *SessionManagedPermissions `json:"permissions,omitempty"` +} + // Standard MCP CallToolResult // Experimental: SessionMCPAppsCallToolResult is part of an experimental API and may change // or be removed. @@ -9552,15 +11956,12 @@ type SessionModelListRequest struct { // Experimental: SessionModelPriceCategory is part of an experimental API and may change or // be removed. type SessionModelPriceCategory struct { - ID string `json:"id"` + // CAPI model identifier. + ID string `json:"id"` + // Cost category assigned to the model. PriceCategory ModelPickerPriceCategory `json:"priceCategory"` } -// Experimental: SessionModeSetResult is part of an experimental API and may change or be -// removed. -type SessionModeSetResult struct { -} - // Experimental: SessionNameSetResult is part of an experimental API and may change or be // removed. type SessionNameSetResult struct { @@ -9576,11 +11977,15 @@ type SessionOpenOptions struct { 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`). + // context and `@`-mention completion). Conventional `.github/skills/` and `.github/agents/` + // definitions under each directory also join the session's project catalogs when their + // existing subsystem gates are enabled: added-root skills require both + // `enableConfigDiscovery` and effective `enableSkills`; added-root agents require + // `enableConfigDiscovery`. Supplying a directory therefore activates configuration from it + // and should be treated as a trust decision. 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 during session creation and cold + // resume and is not persisted, so a cold resume must re-supply the directories. AdditionalDirectories []string `json:"additionalDirectories,omitzero"` // Runtime context discriminator for agent filtering. AgentContext *string `json:"agentContext,omitempty"` @@ -9615,11 +12020,14 @@ 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 - // `assistant.message` event. Off by default; may change or be removed while the citations - // surface is experimental. + // Experimental: enable native model citations for supported Anthropic and OpenAI models, + // normalized onto the `assistant.message` event. Off by default; may change or be removed + // while the citations 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 @@ -9649,6 +12057,9 @@ type SessionOpenOptions struct { EnableOnDemandInstructionDiscovery *bool `json:"enableOnDemandInstructionDiscovery,omitempty"` // Whether shell-script safety heuristics are enabled. EnableScriptSafety *bool `json:"enableScriptSafety,omitempty"` + // Whether skill loading is enabled. When omitted, an SDK skill provider enables skills by + // default. + EnableSkills *bool `json:"enableSkills,omitempty"` // Whether model responses stream as delta events. EnableStreaming *bool `json:"enableStreaming,omitempty"` // How MCP server environment values are interpreted. @@ -9672,10 +12083,24 @@ type SessionOpenOptions struct { ExpAssignments any `json:"expAssignments,omitempty"` // Feature-flag values resolved by the host. FeatureFlags map[string]bool `json:"featureFlags,omitzero"` + // Whether the requesting SDK session has a skill provider. The provider remains ephemeral + // and is never persisted in session options or history. When enableSkills is false, it + // remains bound but dormant and receives no callbacks. Cloud, relay, handoff, and raw + // sessions.open flows reject it because they cannot safely pre-register the callback + // handler. + // Experimental: HasSkillProvider is part of an experimental API and may change or be + // removed. + // Internal: HasSkillProvider is part of the SDK's internal API surface and is not intended + // for external use. + HasSkillProvider *bool `json:"hasSkillProvider,omitempty"` // 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. IncludedBuiltinAgents []string `json:"includedBuiltinAgents,omitzero"` + // Built-in skill names to include in this session. When specified, only these + // runtime-bundled skills are available. Skills from other sources with the same name remain + // available. + IncludedBuiltinSkills []string `json:"includedBuiltinSkills,omitzero"` // Installed plugins visible to the session. InstalledPlugins []InstalledPlugin `json:"installedPlugins,omitzero"` // Stable integration identifier for analytics. @@ -9686,6 +12111,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"` @@ -9723,6 +12151,11 @@ type SessionOpenOptions struct { RunningInInteractiveMode *bool `json:"runningInInteractiveMode,omitempty"` // Resolved sandbox configuration. SandboxConfig *SandboxConfig `json:"sandboxConfig,omitempty"` + // Origin of the sandbox choice. The runtime uses this only for internal telemetry + // provenance; managed policy is derived independently. + // Internal: SandboxConfigSource is part of the SDK's internal API surface and is not + // intended for external use. + SandboxConfigSource *SandboxConfigSource `json:"sandboxConfigSource,omitempty"` // Capabilities enabled for this session. SessionCapabilities []SessionCapability `json:"sessionCapabilities,omitzero"` // Optional stable session identifier to use for a new session. @@ -9755,8 +12188,10 @@ type SessionOpenOptions struct { // Experimental: SessionOpenOptionsAdditionalContentExclusionPolicy is part of an // experimental API and may change or be removed. type SessionOpenOptionsAdditionalContentExclusionPolicy struct { - LastUpdatedAt any `json:"last_updated_at"` - Rules []SessionOpenOptionsAdditionalContentExclusionPolicyRule `json:"rules"` + // Opaque policy update timestamp supplied by the host. + LastUpdatedAt any `json:"last_updated_at"` + // Content-exclusion rules to apply. + Rules []SessionOpenOptionsAdditionalContentExclusionPolicyRule `json:"rules"` // Allowed values for the `SessionOpenOptionsAdditionalContentExclusionPolicyScope` // enumeration. Scope SessionOpenOptionsAdditionalContentExclusionPolicyScope `json:"scope"` @@ -9767,9 +12202,12 @@ type SessionOpenOptionsAdditionalContentExclusionPolicy struct { // Experimental: SessionOpenOptionsAdditionalContentExclusionPolicyRule is part of an // experimental API and may change or be removed. type SessionOpenOptionsAdditionalContentExclusionPolicyRule struct { - IfAnyMatch []string `json:"ifAnyMatch,omitzero"` + // Conditions of which at least one must match. + IfAnyMatch []string `json:"ifAnyMatch,omitzero"` + // Conditions none of which may match. IfNoneMatch []string `json:"ifNoneMatch,omitzero"` - Paths []string `json:"paths"` + // Path patterns covered by this rule. + Paths []string `json:"paths"` // Source descriptor for a `sessions.open` content-exclusion rule, with source name and type. Source SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource `json:"source"` } @@ -9778,7 +12216,9 @@ type SessionOpenOptionsAdditionalContentExclusionPolicyRule struct { // Experimental: SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource is part of an // experimental API and may change or be removed. type SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource struct { + // Name of the policy source. Name string `json:"name"` + // Type of the policy source. Type string `json:"type"` } @@ -9817,10 +12257,8 @@ func (SessionsOpenAttach) Kind() SessionOpenParamsKind { // Experimental: SessionsOpenCloud is part of an experimental API and may change or be // removed. type SessionsOpenCloud struct { - // 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. + // In-process callback invoked when the cloud task is created, before connection. Internal + // because function references cannot cross the JSON-RPC boundary. // Internal: OnTaskCreated is part of the SDK's internal API surface and is not intended for // external use. OnTaskCreated any `json:"onTaskCreated,omitempty"` @@ -10146,7 +12584,7 @@ type SessionSetCredentialsParams struct { // 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. - Credentials AuthInfo `json:"credentials,omitempty"` + Credentials SettableAuthInfo `json:"credentials,omitempty"` } // Indicates whether the credential update succeeded. @@ -10170,8 +12608,10 @@ type SessionSetCredentialsResult struct { // Experimental: SessionSettingsBuiltInToolAvailabilitySnapshot is part of an experimental // API and may change or be removed. type SessionSettingsBuiltInToolAvailabilitySnapshot struct { + // Whether the create-pull-request tool is available. CreatePullRequest *bool `json:"createPullRequest,omitempty"` - ReportProgress *bool `json:"reportProgress,omitempty"` + // Whether the report-progress tool is available. + ReportProgress *bool `json:"reportProgress,omitempty"` } // Named Rust-owned settings predicate to evaluate for this session. @@ -10188,6 +12628,7 @@ type SessionSettingsEvaluatePredicateRequest struct { // Experimental: SessionSettingsEvaluatePredicateResult is part of an experimental API and // may change or be removed. type SessionSettingsEvaluatePredicateResult struct { + // Whether the named settings predicate evaluated to enabled. Enabled bool `json:"enabled"` } @@ -10195,26 +12636,35 @@ type SessionSettingsEvaluatePredicateResult struct { // Experimental: SessionSettingsJobSnapshot is part of an experimental API and may change or // be removed. type SessionSettingsJobSnapshot struct { + // Availability of job-specific built-in tools. BuiltInToolAvailability *SessionSettingsBuiltInToolAvailabilitySnapshot `json:"builtInToolAvailability,omitempty"` - EventType *string `json:"eventType,omitempty"` - IsTriggerJob *bool `json:"isTriggerJob,omitempty"` + // GitHub Actions event type for the job. + EventType *string `json:"eventType,omitempty"` + // Whether this is the workflow's trigger job. + IsTriggerJob *bool `json:"isTriggerJob,omitempty"` } // Redacted model routing settings for a session. // Experimental: SessionSettingsModelSnapshot is part of an experimental API and may change // or be removed. type SessionSettingsModelSnapshot struct { - CallbackURL *string `json:"callbackUrl,omitempty"` + // Agent service callback URL for job and progress updates. + CallbackURL *string `json:"callbackUrl,omitempty"` + // Default reasoning effort for the selected model. DefaultReasoningEffort *string `json:"defaultReasoningEffort,omitempty"` - InstanceID *string `json:"instanceId,omitempty"` - Model *string `json:"model,omitempty"` + // Agent job identifier for the session. + InstanceID *string `json:"instanceId,omitempty"` + // Selected model identifier. + Model *string `json:"model,omitempty"` } // Online-evaluation settings safe to expose across the SDK boundary. // Experimental: SessionSettingsOnlineEvaluationSnapshot is part of an experimental API and // may change or be removed. type SessionSettingsOnlineEvaluationSnapshot struct { - DisableOnlineEvaluation *bool `json:"disableOnlineEvaluation,omitempty"` + // Whether online evaluation is disabled. + DisableOnlineEvaluation *bool `json:"disableOnlineEvaluation,omitempty"` + // Whether online-evaluation output-file generation is enabled. EnableOnlineEvaluationOutputFile *bool `json:"enableOnlineEvaluationOutputFile,omitempty"` } @@ -10222,18 +12672,30 @@ type SessionSettingsOnlineEvaluationSnapshot struct { // Experimental: SessionSettingsRepoSnapshot is part of an experimental API and may change // or be removed. type SessionSettingsRepoSnapshot struct { - Branch *string `json:"branch,omitempty"` - Commit *string `json:"commit,omitempty"` - Host *string `json:"host,omitempty"` - HostProtocol *string `json:"hostProtocol,omitempty"` - ID *float64 `json:"id,omitempty"` - Name *string `json:"name,omitempty"` - OwnerID *float64 `json:"ownerId,omitempty"` - OwnerName *string `json:"ownerName,omitempty"` - PrCommitCount *float64 `json:"prCommitCount,omitempty"` - ReadWrite *bool `json:"readWrite,omitempty"` - SecretScanningURL *string `json:"secretScanningUrl,omitempty"` - ServerURL *string `json:"serverUrl,omitempty"` + // Checked-out repository branch. + Branch *string `json:"branch,omitempty"` + // Checked-out commit SHA. + Commit *string `json:"commit,omitempty"` + // GitHub server host name. + Host *string `json:"host,omitempty"` + // Protocol used to access the GitHub host. + HostProtocol *string `json:"hostProtocol,omitempty"` + // GitHub repository database ID. + ID *float64 `json:"id,omitempty"` + // Repository name. + Name *string `json:"name,omitempty"` + // GitHub repository owner database ID. + OwnerID *float64 `json:"ownerId,omitempty"` + // Repository owner login. + OwnerName *string `json:"ownerName,omitempty"` + // Number of commits in the pull request. + PrCommitCount *float64 `json:"prCommitCount,omitempty"` + // Whether the repository is writable. + ReadWrite *bool `json:"readWrite,omitempty"` + // GitHub secret-scanning service URL. + SecretScanningURL *string `json:"secretScanningUrl,omitempty"` + // GitHub server base URL. + ServerURL *string `json:"serverUrl,omitempty"` } // Redacted, serializable view of session runtime settings for SDK boundary consumers. @@ -10241,30 +12703,49 @@ type SessionSettingsRepoSnapshot struct { // Experimental: SessionSettingsSnapshot is part of an experimental API and may change or be // removed. type SessionSettingsSnapshot struct { - ClientName *string `json:"clientName,omitempty"` - Job SessionSettingsJobSnapshot `json:"job"` - Model SessionSettingsModelSnapshot `json:"model"` + // Name of the SDK client that created the session. + ClientName *string `json:"clientName,omitempty"` + // Redacted job settings. + Job SessionSettingsJobSnapshot `json:"job"` + // Redacted model routing settings. + Model SessionSettingsModelSnapshot `json:"model"` + // Online-evaluation settings safe for SDK consumers. OnlineEvaluation SessionSettingsOnlineEvaluationSnapshot `json:"onlineEvaluation"` - Repo SessionSettingsRepoSnapshot `json:"repo"` - StartTimeMs *float64 `json:"startTimeMs,omitempty"` - TimeoutMs *float64 `json:"timeoutMs,omitempty"` - Validation SessionSettingsValidationSnapshot `json:"validation"` - Version *string `json:"version,omitempty"` + // Redacted repository and host settings. + Repo SessionSettingsRepoSnapshot `json:"repo"` + // Session start time as Unix epoch milliseconds. + StartTimeMs *float64 `json:"startTimeMs,omitempty"` + // Session timeout in milliseconds. + TimeoutMs *float64 `json:"timeoutMs,omitempty"` + // Redacted validation and memory-tool settings. + Validation SessionSettingsValidationSnapshot `json:"validation"` + // Agent runtime version selector copied from the session settings, such as `latest` or a + // runtime release identifier. + Version *string `json:"version,omitempty"` } // Redacted validation and memory-tool settings for a session. // Experimental: SessionSettingsValidationSnapshot is part of an experimental API and may // change or be removed. type SessionSettingsValidationSnapshot struct { - AdvisoryEnabled *bool `json:"advisoryEnabled,omitempty"` - CodeqlEnabled *bool `json:"codeqlEnabled,omitempty"` - CodeReviewEnabled *bool `json:"codeReviewEnabled,omitempty"` - CodeReviewModel *string `json:"codeReviewModel,omitempty"` - DependabotTimeout *float64 `json:"dependabotTimeout,omitempty"` - MemoryStoreEnabled *bool `json:"memoryStoreEnabled,omitempty"` - MemoryVoteEnabled *bool `json:"memoryVoteEnabled,omitempty"` - SecretScanningEnabled *bool `json:"secretScanningEnabled,omitempty"` - Timeout *float64 `json:"timeout,omitempty"` + // Whether advisory validation is enabled. + AdvisoryEnabled *bool `json:"advisoryEnabled,omitempty"` + // Whether CodeQL validation is enabled. + CodeqlEnabled *bool `json:"codeqlEnabled,omitempty"` + // Whether code-review validation is enabled. + CodeReviewEnabled *bool `json:"codeReviewEnabled,omitempty"` + // Model used for code-review validation. + CodeReviewModel *string `json:"codeReviewModel,omitempty"` + // Dependabot validation timeout budget in seconds. + DependabotTimeout *float64 `json:"dependabotTimeout,omitempty"` + // Whether the memory-store tool is enabled. + MemoryStoreEnabled *bool `json:"memoryStoreEnabled,omitempty"` + // Whether the memory-vote tool is enabled. + MemoryVoteEnabled *bool `json:"memoryVoteEnabled,omitempty"` + // Whether secret-scanning validation is enabled. + SecretScanningEnabled *bool `json:"secretScanningEnabled,omitempty"` + // General validation timeout budget in seconds. + Timeout *float64 `json:"timeout,omitempty"` } // UUID prefix to resolve to a unique session ID. @@ -10507,12 +12988,27 @@ type SessionsPruneOldRequest struct { OlderThanDays int64 `json:"olderThanDays"` } +// Pagination options for reading an inactive or active local session's persisted event +// journal. +// Experimental: SessionsReadPersistedEventsRequest is part of an experimental API and may +// change or be removed. +type SessionsReadPersistedEventsRequest struct { + // Opaque cursor returned by a previous persisted-event read. Omit on the first call. + Cursor *string `json:"cursor,omitempty"` + // Direction to page through persisted history. Forward starts at the beginning; backward + // starts with the newest events. Events in each page remain chronological. + Direction *EventsReadDirection `json:"direction,omitempty"` + // Maximum number of events to return in this batch (1–1000, default 200). + Max *int64 `json:"max,omitempty"` + // Session ID whose persisted event journal should be read. + SessionID string `json:"sessionId"` +} + // Optional registration options. // Experimental: SessionsRegisterExtensionToolsOnSessionOptions is part of an experimental // API and may change or be removed. type SessionsRegisterExtensionToolsOnSessionOptions struct { - // In-process `() => boolean` gating callback (CLI-only optimization). Marked internal: - // replaced by runtime-side enable/disable RPCs in the SDK migration. + // In-process `() => boolean` gating callback used only by the CLI. // Internal: Enabled is part of the SDK's internal API surface and is not intended for // external use. Enabled any `json:"enabled,omitempty"` @@ -10696,8 +13192,9 @@ type SessionUpdateOptionsParams struct { EnableScriptSafety *bool `json:"enableScriptSafety,omitempty"` // Whether to enable cross-session store writes and reads. EnableSessionStore *bool `json:"enableSessionStore,omitempty"` - // Whether to enable skill directory scanning and loading. Falls back to - // enableConfigDiscovery when unset. + // Whether skill loading is enabled. Explicit false disables every source, including a bound + // SDK provider; changing the value invalidates the loaded skill snapshot. When omitted, + // creation falls back to enableConfigDiscovery unless an SDK skill provider is registered. EnableSkills *bool `json:"enableSkills,omitempty"` // Whether to stream model responses. EnableStreaming *bool `json:"enableStreaming,omitempty"` @@ -10721,6 +13218,10 @@ type SessionUpdateOptionsParams struct { // are available, subject to runtime availability and exclusions. Custom agents with the // same name remain available. Set to null to remove the allowlist restriction. IncludedBuiltinAgents []string `json:"includedBuiltinAgents,omitzero"` + // Built-in skill names to include in this session. When specified, only these + // runtime-bundled skills are available. Skills from other sources with the same name remain + // available. Set to null to remove the allowlist restriction. + IncludedBuiltinSkills []string `json:"includedBuiltinSkills,omitzero"` // Full set of installed plugins for the session. Replaces the existing list; the runtime // invalidates the skills cache only when the list materially changes. InstalledPlugins []SessionInstalledPlugin `json:"installedPlugins,omitzero"` @@ -10759,6 +13260,11 @@ type SessionUpdateOptionsParams struct { RunningInInteractiveMode *bool `json:"runningInInteractiveMode,omitempty"` // Resolved sandbox configuration. SandboxConfig *SandboxConfig `json:"sandboxConfig,omitempty"` + // Origin of the sandbox choice. The runtime uses this only for internal telemetry + // provenance; managed policy is derived independently. + // Internal: SandboxConfigSource is part of the SDK's internal API surface and is not + // intended for external use. + SandboxConfigSource *SandboxConfigSource `json:"sandboxConfigSource,omitempty"` // 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. @@ -10832,6 +13338,68 @@ type SessionWorkingDirectoryContext struct { type SessionWorkspacesCreateFileResult struct { } +// Authentication credentials accepted by session.gitHubAuth.setCredentials. Session-owned +// token-provider identities cannot be installed through this method. +// Experimental: SettableAuthInfo is part of an experimental API and may change or be +// removed. +type SettableAuthInfo interface { + settableAuthInfo() + settableAuthInfoType() SettableAuthInfoType +} + +type RawSettableAuthInfoData struct { + Discriminator SettableAuthInfoType + Raw json.RawMessage +} + +func (RawSettableAuthInfoData) settableAuthInfo() {} +func (r RawSettableAuthInfoData) settableAuthInfoType() SettableAuthInfoType { + return r.Discriminator +} +func (APIKeyAuthInfo) settableAuthInfo() {} +func (APIKeyAuthInfo) settableAuthInfoType() SettableAuthInfoType { + return SettableAuthInfoTypeAPIKey +} +func (CopilotAPITokenAuthInfo) settableAuthInfo() {} +func (CopilotAPITokenAuthInfo) settableAuthInfoType() SettableAuthInfoType { + return SettableAuthInfoTypeCopilotAPIToken +} +func (EnvAuthInfo) settableAuthInfo() {} +func (EnvAuthInfo) settableAuthInfoType() SettableAuthInfoType { + return SettableAuthInfoTypeEnv +} +func (GhCLIAuthInfo) settableAuthInfo() {} +func (GhCLIAuthInfo) settableAuthInfoType() SettableAuthInfoType { + return SettableAuthInfoTypeGhCLI +} +func (HMACAuthInfo) settableAuthInfo() {} +func (HMACAuthInfo) settableAuthInfoType() SettableAuthInfoType { + return SettableAuthInfoTypeHMAC +} + +// Token authentication accepted by session.gitHubAuth.setCredentials. +// Experimental: SettableTokenAuthInfo is part of an experimental API and may change or be +// removed. +type SettableTokenAuthInfo struct { + // 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. + CopilotUser *CopilotUserResponse `json:"copilotUser,omitempty"` + // Authentication host. + Host string `json:"host"` + // The token value itself. Treat as a secret. + Token string `json:"token"` +} + +func (SettableTokenAuthInfo) settableAuthInfo() {} +func (SettableTokenAuthInfo) settableAuthInfoType() SettableAuthInfoType { + return SettableAuthInfoTypeToken +} +func (UserAuthInfo) settableAuthInfo() {} +func (UserAuthInfo) settableAuthInfoType() SettableAuthInfoType { + return SettableAuthInfoTypeUser +} + // User-requested shell execution cancellation handle. // Experimental: ShellCancelUserRequestedRequest is part of an experimental API and may // change or be removed. @@ -10840,6 +13408,55 @@ type ShellCancelUserRequestedRequest struct { RequestID string `json:"requestId"` } +// Command-scoped GitHub credential injection for the shell commands an agent runs. +// +// Each channel is opt-in and independent, and injection is scoped to the individual command +// spawn: the credential is resolved from the session's *current* authentication at every +// spawn +// and reaches only spawns whose script actually invokes `git` or `gh`. Because nothing is +// retained between spawns, replacing the session credential +// (`session.gitHubAuth.setCredentials`) +// changes what the next spawned command presents — which seeding a credential into the +// runtime +// process's own environment cannot do, since a child's environment is fixed at `exec`. +// +// The credential is matched to the host it authenticates to, so a github.com credential is +// never +// presented to a GitHub Enterprise host and vice versa. Where a channel cannot express that +// boundary it injects nothing rather than crossing it -- see `gh` below. +// +// This is independent of `sandboxConfig`: it is a decision about which identity the agent +// presents, not about what the agent may touch, and it works on every platform whether or +// not +// an OS sandboxing backend is available. `sandboxConfig.auth` remains the sandbox-scoped +// spelling and is additive with this one. +// Experimental: ShellCredentials is part of an experimental API and may change or be +// removed. +type ShellCredentials struct { + // Whether to authenticate the agent's `gh` commands as the session's GitHub credential, by + // exporting `GH_TOKEN` to a spawn that runs `gh`. Any inherited `gh` credential is removed + // from + // spawns that do not, so the credential stays command-scoped. + // + // Applies to a github.com credential only. `gh` picks its credential variable from the host + // a + // command targets rather than the one the credential belongs to, and the command can choose + // that + // target, so `GH_ENTERPRISE_TOKEN` would offer a single-tenant enterprise credential to + // every + // other enterprise host. A session whose credential is enterprise-scoped therefore runs `gh` + // unauthenticated; its `git` commands are unaffected, because `http..extraheader` is + // scoped + // to one host by construction. Default: false (opt-in). + Gh *bool `json:"gh,omitempty"` + // Whether to authenticate the agent's `git` commands as the session's GitHub credential, by + // injecting an `http..extraheader` (plus `insteadOf` rewrites so SSH-spelled remotes + // for + // that host use the authenticated HTTPS transport). Applied only to a spawn that runs a + // remote-contacting `git` subcommand. Default: false (opt-in). + Git *bool `json:"git,omitempty"` +} + // Shell command to run, with optional working directory and timeout in milliseconds. // Experimental: ShellExecRequest is part of an experimental API and may change or be // removed. @@ -10901,6 +13518,8 @@ type ShellKillResult struct { // 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 { + // Command-scoped GitHub credential injection for shell commands. + Credentials *ShellCredentials `json:"credentials,omitempty"` // Controls automatic non-interactive profile loading where supported. Explicit initScripts // are unaffected. InitProfile *ShellInitProfile `json:"initProfile,omitempty"` @@ -10943,6 +13562,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 @@ -10991,6 +13612,66 @@ type SkillList struct { Skills []Skill `json:"skills"` } +// Catalog-only metadata for one SDK-provided skill. The complete SKILL.md is fetched +// separately and lazily. +// Experimental: SkillProviderDescriptor is part of an experimental API and may change or be +// removed. +type SkillProviderDescriptor struct { + // Optional freeform argument hint used by slash-command catalogs. + ArgumentHint *string `json:"argumentHint,omitempty"` + // Description used in skill catalogs without fetching content. + Description string `json:"description"` + // Whether model invocation is disabled. Defaults to false. + DisableModelInvocation *bool `json:"disableModelInvocation,omitempty"` + // Invocation and display name. + Name string `json:"name"` + // Whether users may invoke the skill directly. Defaults to true. + UserInvocable *bool `json:"userInvocable,omitempty"` +} + +// Identifies the target session. +// Experimental: SkillProviderListRequest is part of an experimental API and may change or +// be removed. +type SkillProviderListRequest struct { + // Target session identifier + SessionID string `json:"sessionId"` +} + +// Catalog metadata returned by an SDK session's skill provider. Catalogs are limited to +// 1024 descriptors and 1 MiB of aggregate metadata. +// Experimental: SkillProviderListResult is part of an experimental API and may change or be +// removed. +// Internal: SkillProviderListResult is an internal SDK API and is not part of the public +// surface. +type SkillProviderListResult struct { + // Skill descriptors in provider order. Invocation names must be unique under + // case-insensitive comparison. + Skills []SkillProviderDescriptor `json:"skills"` +} + +// Identifies one SDK-provided skill by invocation name. +// Experimental: SkillProviderReadRequest is part of an experimental API and may change or +// be removed. +// Internal: SkillProviderReadRequest is an internal SDK API and is not part of the public +// surface. +type SkillProviderReadRequest struct { + // Invocation name of the skill to read. + Name string `json:"name"` + // Target session identifier + SessionID string `json:"sessionId"` +} + +// Complete text-only SKILL.md content returned by an SDK session's skill provider. Related +// files and assets are not supported. +// Experimental: SkillProviderReadResult is part of an experimental API and may change or be +// removed. +// Internal: SkillProviderReadResult is an internal SDK API and is not part of the public +// surface. +type SkillProviderReadResult struct { + // Complete SKILL.md text. The runtime enforces a 1 MiB UTF-8 byte limit. + Markdown string `json:"markdown"` +} + // Skill names to mark as disabled in global configuration, replacing any previous list. // Experimental: SkillsConfigSetDisabledSkillsRequest is part of an experimental API and may // change or be removed. @@ -11004,6 +13685,22 @@ type SkillsConfigSetDisabledSkillsRequest struct { type SkillsConfigSetDisabledSkillsResult struct { } +// Adds or removes a single skill from the global disabled list, leaving every other entry +// untouched. +// Experimental: SkillsConfigSetSkillDisabledRequest is part of an experimental API and may +// change or be removed. +type SkillsConfigSetSkillDisabledRequest struct { + // True to disable the skill, false to enable it + Disabled bool `json:"disabled"` + // Name of the skill to add to or remove from the disabled list + Name string `json:"name"` +} + +// Experimental: SkillsConfigSetSkillDisabledResult is part of an experimental API and may +// change or be removed. +type SkillsConfigSetSkillDisabledResult struct { +} + // Name of the skill to disable for the session. // Experimental: SkillsDisableRequest is part of an experimental API and may change or be // removed. @@ -11061,11 +13758,14 @@ type SkillsInvokedSkill struct { AllowedTools []string `json:"allowedTools,omitzero"` // Full content of the skill file Content string `json:"content"` + // Whether model invocation was disabled when this skill was invoked + DisableModelInvocation *bool `json:"disableModelInvocation,omitempty"` // Turn number when the skill was invoked InvokedAtTurn int64 `json:"invokedAtTurn"` // Unique identifier for the skill Name string `json:"name"` - // Path to the SKILL.md file + // Path to the SKILL.md file, or an empty string for an SDK-provided skill without a + // filesystem identity Path string `json:"path"` } @@ -11153,6 +13853,22 @@ func (r RawSlashCommandInvocationResultData) Kind() SlashCommandInvocationResult return r.Discriminator } +// Experimental: SlashCommandAddTimelineEntryResult is part of an experimental API and may +// change or be removed. +type SlashCommandAddTimelineEntryResult struct { + // Timeline entry the host should append. + Entry SlashCommandTimelineEntry `json:"entry"` + // Optional text the host should prefill into the input editor. + PrefillInput *string `json:"prefillInput,omitempty"` + // Whether command execution changed persisted runtime settings. + RuntimeSettingsChanged *bool `json:"runtimeSettingsChanged,omitempty"` +} + +func (SlashCommandAddTimelineEntryResult) slashCommandInvocationResult() {} +func (SlashCommandAddTimelineEntryResult) Kind() SlashCommandInvocationResultKind { + return SlashCommandInvocationResultKindAddTimelineEntry +} + // Slash-command invocation result that submits an agent prompt, with display prompt, // optional mode, optional user-facing notice, and settings-change flag. // Experimental: SlashCommandAgentPromptResult is part of an experimental API and may change @@ -11183,6 +13899,8 @@ func (SlashCommandAgentPromptResult) Kind() SlashCommandInvocationResultKind { type SlashCommandCompletedResult struct { // Optional user-facing message describing the completed command Message *string `json:"message,omitempty"` + // Optional target session mode applied without submitting an agent prompt + Mode *SessionMode `json:"mode,omitempty"` // True when the invocation mutated user runtime settings; consumers caching settings should // refresh RuntimeSettingsChanged *bool `json:"runtimeSettingsChanged,omitempty"` @@ -11214,6 +13932,60 @@ func (SlashCommandSelectSubcommandResult) Kind() SlashCommandInvocationResultKin return SlashCommandInvocationResultKindSelectSubcommand } +// Experimental: SlashCommandSetModelResult is part of an experimental API and may change or +// be removed. +type SlashCommandSetModelResult struct { + // Model selected by the command. + Model string `json:"model"` + // Reasoning effort selected for the model. + ReasoningEffort *string `json:"reasoningEffort,omitempty"` + // Repository settings scope modified by the command. + RepoScope *string `json:"repoScope,omitempty"` + // User-settings snapshot to restore if the host cancels the model switch. + RevertOnCancel any `json:"revertOnCancel,omitempty"` + // Whether command execution changed persisted runtime settings. + RuntimeSettingsChanged *bool `json:"runtimeSettingsChanged,omitempty"` + // Settings scope modified by the command. + Scope *string `json:"scope,omitempty"` + // User-facing warning produced while selecting the model. + Warning *string `json:"warning,omitempty"` +} + +func (SlashCommandSetModelResult) slashCommandInvocationResult() {} +func (SlashCommandSetModelResult) Kind() SlashCommandInvocationResultKind { + return SlashCommandInvocationResultKindSetModel +} + +// Experimental: SlashCommandSetPlanModelResult is part of an experimental API and may +// change or be removed. +type SlashCommandSetPlanModelResult struct { + // User-facing confirmation message for the plan-model selection. + Message string `json:"message"` + // Dedicated model selected for plan mode. + PlanModel *string `json:"planModel,omitempty"` + // Whether command execution changed persisted runtime settings. + RuntimeSettingsChanged *bool `json:"runtimeSettingsChanged,omitempty"` +} + +func (SlashCommandSetPlanModelResult) slashCommandInvocationResult() {} +func (SlashCommandSetPlanModelResult) Kind() SlashCommandInvocationResultKind { + return SlashCommandInvocationResultKindSetPlanModel +} + +// Experimental: SlashCommandShowDialogResult is part of an experimental API and may change +// or be removed. +type SlashCommandShowDialogResult struct { + // Dialog the host should display. + Dialog SlashCommandModelPickerDialog `json:"dialog"` + // Whether command execution changed persisted runtime settings. + RuntimeSettingsChanged *bool `json:"runtimeSettingsChanged,omitempty"` +} + +func (SlashCommandShowDialogResult) slashCommandInvocationResult() {} +func (SlashCommandShowDialogResult) Kind() SlashCommandInvocationResultKind { + return SlashCommandInvocationResultKindShowDialog +} + // Slash-command invocation result containing text output plus Markdown/ANSI rendering flags. // Experimental: SlashCommandTextResult is part of an experimental API and may change or be // removed. @@ -11234,6 +14006,19 @@ func (SlashCommandTextResult) Kind() SlashCommandInvocationResultKind { return SlashCommandInvocationResultKindText } +// Experimental: SlashCommandModelPickerDialog is part of an experimental API and may change +// or be removed. +type SlashCommandModelPickerDialog struct { + // Discriminator for a model-picker dialog. + Kind SlashCommandModelPickerDialogKind `json:"kind"` + // Model that should be enabled before it can be selected. + ModelToEnable *string `json:"modelToEnable,omitempty"` + // Settings scope the picker should modify. + Scope *string `json:"scope,omitempty"` + // Model-selection target represented by the picker. + Target *string `json:"target,omitempty"` +} + // Selectable slash-command subcommand option with name, description, and optional group // label. // Experimental: SlashCommandSelectSubcommandOption is part of an experimental API and may @@ -11247,6 +14032,17 @@ type SlashCommandSelectSubcommandOption struct { Name string `json:"name"` } +// Experimental: SlashCommandTimelineEntry is part of an experimental API and may change or +// be removed. +type SlashCommandTimelineEntry struct { + // Text displayed for the timeline entry. + Text string `json:"text"` + // Timeline entry presentation type. + Type string `json:"type"` + // Optional URL associated with the timeline entry. + URL *string `json:"url,omitempty"` +} + // Configured per-agent subagent overrides // Experimental: SubagentSettings is part of an experimental API and may change or be // removed. @@ -11272,6 +14068,47 @@ type SubagentSettingsEntry struct { EffortLevel *string `json:"effortLevel,omitempty"` // Model override for matching subagents Model *string `json:"model,omitempty"` + // Whether the configured model strategy is preferred or required + ModelPolicy *AgentModelPolicy `json:"modelPolicy,omitempty"` +} + +// Task completion notification with summary from the agent +// Experimental: TaskCompleteData is part of an experimental API and may change or be +// removed. +type TaskCompleteData struct { + // 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"` +} + +// Experimental: TaskCompletionDecision is part of an experimental API and may change or be +// removed. +type TaskCompletionDecision struct { + // Objective eligibility token captured when the decision was evaluated. + CompletionEligibilityToken *int64 `json:"completionEligibilityToken,omitempty"` + // Whether completion was accepted after the reviewer-rejection budget was exhausted. + CompletionRejectionBudgetExhausted *bool `json:"completionRejectionBudgetExhausted,omitempty"` + // Active autopilot objective evaluated by the completion reviewer. + ObjectiveID *int64 `json:"objectiveId,omitempty"` + // Semantic result of evaluating the task completion request. + Outcome TaskCompletionOutcome `json:"outcome"` + // Rationale for the completion decision, when one is available. + Reason *string `json:"reason,omitempty"` + // Whether the rationale was derived from completion-reviewer output. + ReviewerDerived *bool `json:"reviewerDerived,omitempty"` + // Information-flow metadata captured from the completion reviewer. + ReviewerResultMeta any `json:"reviewerResultMeta,omitempty"` } // Tracked task union returned by task APIs, containing either an agent task or a shell task. @@ -11309,6 +14146,8 @@ type TaskAgentInfo struct { CompletedAt *time.Time `json:"completedAt,omitempty"` // Short description of the task Description string `json:"description"` + // Friendly, non-unique name intended for display + DisplayName *string `json:"displayName,omitempty"` // Error message when the task failed Error *string `json:"error,omitempty"` // Whether task execution is synchronously awaited or managed in the background @@ -11567,7 +14406,7 @@ type TasksStartAgentRequest struct { Description *string `json:"description,omitempty"` // Optional model override Model *string `json:"model,omitempty"` - // Short name for the agent, used to generate a human-readable ID + // Friendly, non-unique name used when displaying the agent Name string `json:"name"` // Task prompt for the agent Prompt string `json:"prompt"` @@ -11624,6 +14463,108 @@ type ToolList struct { Tools []Tool `json:"tools"` } +// Canonical result returned by a session tool. +// Experimental: ToolResult is part of an experimental API and may change or be removed. +type ToolResult interface { + toolResult() +} + +func (ToolResultExpanded) toolResult() {} + +type ToolStringResult string + +func (ToolStringResult) toolResult() {} + +// Expanded canonical result returned by a session tool. +// Experimental: ToolResultExpanded is part of an experimental API and may change or be +// removed. +type ToolResultExpanded struct { + // Base64-encoded binary results returned to the model. + BinaryResultsForLlm []ExternalToolTextResultForLlmBinaryResultsForLlm `json:"binaryResultsForLlm,omitzero"` + // Sources returned by the tool that the model may cite. + CitableSources []any `json:"citableSources,omitzero"` + // Structured content blocks returned to the model. + Contents []ExternalToolTextResultForLlmContent `json:"contents,omitzero"` + // Error message for an unsuccessful execution. + Error *string `json:"error,omitempty"` + // Metadata propagated with the tool result, including information-flow labels. + MCPMeta map[string]any `json:"mcpMeta,omitzero"` + // Messages to inject after the tool result. + NewMessages []ToolResultNewMessage `json:"newMessages,omitzero"` + // Whether post-tool-use failure hooks have already processed this result. + PostToolUseFailureHooksProcessed *bool `json:"postToolUseFailureHooksProcessed,omitempty"` + // Execution outcome classification. + ResultType ToolResultType `json:"resultType"` + // Detailed log content available for session display. + SessionLog *string `json:"sessionLog,omitempty"` + // Skill invocation metadata produced by the tool. + SkillInvocation any `json:"skillInvocation,omitempty"` + // Whether large-output post-processing should be skipped. + SkipLargeOutputProcessing *bool `json:"skipLargeOutputProcessing,omitempty"` + // Structured result content in addition to the model-facing text. + StructuredContent any `json:"structuredContent,omitempty"` + // Completion-review decision produced by the task-completion tool. + TaskCompletionDecision *TaskCompletionDecision `json:"taskCompletionDecision,omitempty"` + // Text result returned to the model. + TextResultForLlm string `json:"textResultForLlm"` + // Deferred tool names made available by this result. + ToolReferences []string `json:"toolReferences,omitzero"` + // Tool-specific telemetry payload. + ToolTelemetry any `json:"toolTelemetry,omitempty"` + // Optional UI resource produced by the tool. + UIResource any `json:"uiResource,omitempty"` +} + +// A message injected by a tool result. +// Experimental: ToolResultNewMessage is part of an experimental API and may change or be +// removed. +type ToolResultNewMessage struct { + // Message content to inject after the tool result. + Content string `json:"content"` + // Source attributed to the injected message. + Source string `json:"source"` +} + +// A tool name and arguments to execute through the session's native invocation pipeline. +// Experimental: ToolsExecuteRequest is part of an experimental API and may change or be +// removed. +type ToolsExecuteRequest struct { + // Arguments supplied to the tool. + Arguments any `json:"arguments"` + // Name of the currently offered tool to execute. + Name string `json:"name"` + // Optional identifier used to correlate this invocation with its tool call. + ToolCallID *string `json:"toolCallId,omitempty"` +} + +// Options controlling how Rust-owned built-in tool descriptors are materialized. +// Experimental: ToolsGetBuiltinDescriptorsRequest is part of an experimental API and may +// change or be removed. +type ToolsGetBuiltinDescriptorsRequest struct { + // Whether background task completion notifications are enabled. + BackgroundTaskNotificationsEnabled *bool `json:"backgroundTaskNotificationsEnabled,omitempty"` + // Whether tool descriptors should include authoring metadata. + IncludeAuthor *bool `json:"includeAuthor,omitempty"` + // Whether descriptors should favor fewer user-intervention prompts. + ReduceUserIntervention *bool `json:"reduceUserIntervention,omitempty"` + // Shell-specific names and description lines for shell tools. + ShellConfig *ToolsShellDescriptorConfig `json:"shellConfig,omitempty"` + // Whether the configured shell supports PowerShell 7 syntax. + ShellSupportsPowerShell7Syntax *bool `json:"shellSupportsPowerShell7Syntax,omitempty"` + // Default shell timeout in milliseconds. + ShellTimeoutMs *float64 `json:"shellTimeoutMs,omitempty"` + // Whether semantic skill lookup is available. + SkillEmbeddingEnabled *bool `json:"skillEmbeddingEnabled,omitempty"` +} + +// Rust-owned built-in tool descriptors for the session. +// Experimental: ToolsGetBuiltinDescriptorsResult is part of an experimental API and may +// change or be removed. +type ToolsGetBuiltinDescriptorsResult struct { + // Built-in tool descriptors materialized for the session. + Tools []BuiltinToolDescriptor `json:"tools"` +} + // Current lightweight tool metadata snapshot for the session. // Experimental: ToolsGetCurrentMetadataResult is part of an experimental API and may change // or be removed. @@ -11649,6 +14590,51 @@ type ToolsListRequest struct { Model *string `json:"model,omitempty"` } +// Complete externally implemented tool list for the calling connection. An empty list +// removes every tool previously supplied by that connection. +// Experimental: ToolsSetRequest is part of an experimental API and may change or be removed. +type ToolsSetRequest struct { + // Complete replacement list for the calling connection. + Tools []ProtocolExternalToolDefinition `json:"tools"` +} + +// Empty result after replacing the calling connection's externally implemented tools. +// Experimental: ToolsSetResult is part of an experimental API and may change or be removed. +type ToolsSetResult struct { +} + +// Shell-specific names and description lines used to materialize built-in shell tool +// descriptors. +// Experimental: ToolsShellDescriptorConfig is part of an experimental API and may change or +// be removed. +type ToolsShellDescriptorConfig struct { + // Additional model-facing shell description lines. + DescriptionLines []string `json:"descriptionLines"` + // Human-readable shell name. + DisplayName string `json:"displayName"` + // Tool name used to list active shells. + ListShellsToolName string `json:"listShellsToolName"` + // Tool name used to read shell output. + ReadShellToolName string `json:"readShellToolName"` + // Tool name used to start shell commands. + ShellToolName string `json:"shellToolName"` + // Stable shell type identifier. + ShellType string `json:"shellType"` + // Tool name used to stop shell commands. + StopShellToolName string `json:"stopShellToolName"` +} + +// Task-completion tool arguments and final result used to build a label-safe session event +// payload. +// Experimental: ToolsTaskCompleteEventDataRequest is part of an experimental API and may +// change or be removed. +type ToolsTaskCompleteEventDataRequest struct { + // Final expanded result returned by the task_complete tool. + FinalResult ToolResultExpanded `json:"finalResult"` + // Arguments supplied to the completed task_complete tool call. + ToolArgs any `json:"toolArgs"` +} + // Empty result after applying subagent settings // Experimental: ToolsUpdateSubagentSettingsResult is part of an experimental API and may // change or be removed. @@ -11713,8 +14699,14 @@ func (UIElicitationStringValue) uiElicitationFieldValue() {} type UIElicitationRequest struct { // Message describing what information is needed from the user Message string `json:"message"` + // MCP request metadata. + Meta map[string]any `json:"_meta,omitzero"` + // Elicitation mode. Omitted and form are equivalent for structured elicitation. + Mode *MCPElicitationFormMode `json:"mode,omitempty"` // JSON Schema describing the form fields to present to the user RequestedSchema UIElicitationSchema `json:"requestedSchema"` + // MCP task metadata. + Task *MCPTaskMetadata `json:"task,omitempty"` } // The elicitation response (accept with form values, decline, or cancel) @@ -11725,6 +14717,8 @@ type UIElicitationResponse struct { Action UIElicitationResponseAction `json:"action"` // The form values submitted by the user (present when action is 'accept') Content map[string]UIElicitationFieldValue `json:"content,omitzero"` + // MCP response metadata. + Meta map[string]any `json:"_meta,omitzero"` } // The form values submitted by the user (present when action is 'accept') @@ -11939,14 +14933,12 @@ type UIElicitationStringOneOfFieldOneOf struct { // removed. type UIEphemeralQueryRequest struct { // 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 and excluded from the public SDK surface. // Internal: AbortSignal is part of the SDK's internal API surface and is not intended for // external use. AbortSignal any `json:"abortSignal,omitempty"` // 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. + // it. Internal and excluded from the public SDK surface. // Internal: OnChunk is part of the SDK's internal API surface and is not intended for // external use. OnChunk any `json:"onChunk,omitempty"` @@ -11954,11 +14946,12 @@ type UIEphemeralQueryRequest struct { Question string `json:"question"` } -// Transient answer generated from current conversation context. +// Completed transient query. Ordered chunks and the terminal outcome are also delivered +// through `ui.ephemeral_query` session events while it runs. // Experimental: UIEphemeralQueryResult is part of an experimental API and may change or be // removed. type UIEphemeralQueryResult struct { - // Full assistant response text. + // Answer returned by the model Answer string `json:"answer"` } @@ -12135,6 +15128,9 @@ type UpdateSubagentSettingsRequest struct { // Experimental: UsageGetMetricsResult is part of an experimental API and may change or be // removed. type UsageGetMetricsResult struct { + // Per-agent usage metrics, keyed by agent instance identifier. The main conversation uses + // the stable key `main`. + AgentMetrics map[string]UsageMetricsAgentMetric `json:"agentMetrics,omitzero"` // Aggregated code change metrics CodeChanges UsageMetricsCodeChanges `json:"codeChanges"` // Currently active model identifier @@ -12160,6 +15156,26 @@ type UsageGetMetricsResult struct { TotalUserRequests int64 `json:"totalUserRequests"` } +// Usage attributed to one agent instance, including its identity, API duration, AI units, +// and per-model breakdown. +// Experimental: UsageMetricsAgentMetric is part of an experimental API and may change or be +// removed. +type UsageMetricsAgentMetric struct { + // Human-readable label for this subagent invocation, copied from the originating + // `subagent.started` event. For task-tool subagents this is the invocation's task + // description rather than the agent's configured display name, so group by `agentName` for + // stable per-agent labels. + AgentDisplayName *string `json:"agentDisplayName,omitempty"` + // Configured agent name, when this is a subagent + AgentName *string `json:"agentName,omitempty"` + // Per-model usage for this agent, keyed by model identifier + ModelMetrics map[string]UsageMetricsModelMetric `json:"modelMetrics"` + // Time spent in model API calls by this agent, in milliseconds + TotalAPIDurationMs int64 `json:"totalApiDurationMs"` + // Accumulated nano-AI units cost for this agent + TotalNanoAiu float64 `json:"totalNanoAiu"` +} + // Aggregated code change metrics // Experimental: UsageMetricsCodeChanges is part of an experimental API and may change or be // removed. @@ -12345,6 +15361,23 @@ func (UserToolSessionApprovalCustomTool) Kind() UserToolSessionApprovalKind { return UserToolSessionApprovalKindCustomTool } +// Session-scoped tool-approval rule for an extension's access to sensitive environment +// variables, keyed by extension name and the exact set of variable names. +// Experimental: UserToolSessionApprovalExtensionEnvAccess is part of an experimental API +// and may change or be removed. +type UserToolSessionApprovalExtensionEnvAccess struct { + // Names of the sensitive environment variables this approval covers. Values are never + // persisted. + EnvironmentVariables []string `json:"environmentVariables"` + // Extension name + ExtensionName string `json:"extensionName"` +} + +func (UserToolSessionApprovalExtensionEnvAccess) userToolSessionApproval() {} +func (UserToolSessionApprovalExtensionEnvAccess) Kind() UserToolSessionApprovalKind { + return UserToolSessionApprovalKindExtensionEnvAccess +} + // Session-scoped tool-approval rule for extension-management operations, optionally // narrowed by operation. // Experimental: UserToolSessionApprovalExtensionManagement is part of an experimental API @@ -12532,7 +15565,9 @@ type WorkspacesAddSummaryRequest struct { // Experimental: WorkspacesAddSummaryResult is part of an experimental API and may change or // be removed. type WorkspacesAddSummaryResult struct { - Summary any `json:"summary,omitempty"` + // Metadata for the persisted summary. + Summary any `json:"summary,omitempty"` + // Refreshed metadata for the containing workspace. Workspace any `json:"workspace,omitempty"` } @@ -12606,24 +15641,40 @@ type WorkspacesGetWorkspaceResult struct { } type WorkspacesGetWorkspaceResultWorkspace struct { - Branch *string `json:"branch,omitempty"` - ChronicleSyncDismissed *bool `json:"chronicle_sync_dismissed,omitempty"` - ClientName *string `json:"client_name,omitempty"` - CreatedAt *time.Time `json:"created_at,omitempty"` - Cwd *string `json:"cwd,omitempty"` - GitRoot *string `json:"git_root,omitempty"` + // Current Git branch. + Branch *string `json:"branch,omitempty"` + // Whether the per-session Chronicle upgrade prompt was dismissed for the workspace. + ChronicleSyncDismissed *bool `json:"chronicle_sync_dismissed,omitempty"` + // Name of the client that created the workspace. + ClientName *string `json:"client_name,omitempty"` + // Timestamp when the workspace was created. + CreatedAt *time.Time `json:"created_at,omitempty"` + // Current working directory associated with the workspace. + Cwd *string `json:"cwd,omitempty"` + // Git repository root associated with the workspace. + GitRoot *string `json:"git_root,omitempty"` // Allowed values for the `WorkspacesWorkspaceDetailsHostType` enumeration. - HostType *WorkspacesWorkspaceDetailsHostType `json:"host_type,omitempty"` - ID string `json:"id"` - McLastEventID *string `json:"mc_last_event_id,omitempty"` - McSessionID *string `json:"mc_session_id,omitempty"` - McTaskID *string `json:"mc_task_id,omitempty"` - Name *string `json:"name,omitempty"` - RemoteSteerable *bool `json:"remote_steerable,omitempty"` - Repository *string `json:"repository,omitempty"` - SummaryCount *int64 `json:"summary_count,omitempty"` - UpdatedAt *time.Time `json:"updated_at,omitempty"` - UserNamed *bool `json:"user_named,omitempty"` + HostType *WorkspacesWorkspaceDetailsHostType `json:"host_type,omitempty"` + // Stable workspace identifier. + ID string `json:"id"` + // Most recent Mission Control event identifier observed for the workspace. + McLastEventID *string `json:"mc_last_event_id,omitempty"` + // Mission Control session identifier associated with the workspace. + McSessionID *string `json:"mc_session_id,omitempty"` + // Mission Control task identifier associated with the workspace. + McTaskID *string `json:"mc_task_id,omitempty"` + // Workspace display name. + Name *string `json:"name,omitempty"` + // Whether the workspace session can be steered remotely. + RemoteSteerable *bool `json:"remote_steerable,omitempty"` + // Repository identifier associated with the workspace. + Repository *string `json:"repository,omitempty"` + // Number of persisted summaries in the workspace. + SummaryCount *int64 `json:"summary_count,omitempty"` + // Timestamp when the workspace was last updated. + UpdatedAt *time.Time `json:"updated_at,omitempty"` + // Whether the workspace name was explicitly chosen by the user. + UserNamed *bool `json:"user_named,omitempty"` } // Workspace checkpoints in chronological order; empty when the workspace is not enabled. @@ -12830,7 +15881,19 @@ const ( AgentInfoSourceUser AgentInfoSource = "user" ) -// Kind of attention required when status === "attention". Meaningful only when status === +// Whether configured models are advisory preferences or required constraints +// Experimental: AgentModelPolicy is part of an experimental API and may change or be +// removed. +type AgentModelPolicy string + +const ( + // Treat the authored models as advisory preferences that callers may override. + AgentModelPolicyPreferred AgentModelPolicy = "preferred" + // Require subagent execution to use one of the authored models. + AgentModelPolicyRequired AgentModelPolicy = "required" +) + +// Kind of attention required when status === "attention". Meaningful only when status === // "attention". // Experimental: AgentRegistryLiveTargetEntryAttentionKind is part of an experimental API // and may change or be removed. @@ -13013,9 +16076,386 @@ const ( AuthInfoTypeGhCLI AuthInfoType = "gh-cli" AuthInfoTypeHMAC AuthInfoType = "hmac" AuthInfoTypeToken AuthInfoType = "token" + AuthInfoTypeTokenProvider AuthInfoType = "token-provider" AuthInfoTypeUser AuthInfoType = "user" ) +// Routing preference used when the session model is `auto`. +// Experimental: AutoTier is part of an experimental API and may change or be removed. +type AutoTier string + +const ( + // Balance efficiency and intelligence. + AutoTierBalance AutoTier = "balance" + // Optimize for efficiency. + AutoTierEfficiency AutoTier = "efficiency" + // Optimize for intelligence. + AutoTierIntelligence AutoTier = "intelligence" +) + +// Custom input-format kind. +// Experimental: BuiltinToolFormatType is part of an experimental API and may change or be +// removed. +type BuiltinToolFormatType string + +const ( + // The tool input is parsed with the supplied grammar. + BuiltinToolFormatTypeGrammar BuiltinToolFormatType = "grammar" +) + +// Root JSON Schema type for a built-in tool input. +// Experimental: BuiltinToolInputSchemaType is part of an experimental API and may change or +// be removed. +type BuiltinToolInputSchemaType string + +const ( + // The tool accepts a JSON object. + BuiltinToolInputSchemaTypeObject BuiltinToolInputSchemaType = "object" +) + +// Canonical digest algorithm for a validated MCP card +// Experimental: CardDigestAlgorithm is part of an experimental API and may change or be +// removed. +type CardDigestAlgorithm string + +const ( + // SHA-256 over RFC 8785 canonical JSON encoded as UTF-8. + CardDigestAlgorithmSha256Rfc8785 CardDigestAlgorithm = "sha256-rfc8785" +) + +// AI skills are discovery-only and cannot be installed through this surface +type CatalogAiSkillCandidateInstallability string + +const ( + CatalogAiSkillCandidateInstallabilityNotInstallableKind CatalogAiSkillCandidateInstallability = "not-installable-kind" +) + +// Media type of the underlying AI skill card +type CatalogAiSkillCandidateMediaType string + +const ( + CatalogAiSkillCandidateMediaTypeApplicationAiSkill CatalogAiSkillCandidateMediaType = "application/ai-skill" +) + +// Media type advertised for the referenced AI skill card +type CatalogAiSkillCandidateProvenanceMediaType string + +const ( + CatalogAiSkillCandidateProvenanceMediaTypeApplicationAiSkill CatalogAiSkillCandidateProvenanceMediaType = "application/ai-skill" +) + +// Why the catalog authority did not accept the caller's identity +// Experimental: CatalogAuthenticationRequiredReason is part of an experimental API and may +// change or be removed. +type CatalogAuthenticationRequiredReason string + +const ( + // A credential was presented and its lifetime has elapsed. A silent refresh is worth + // attempting before prompting anyone. + CatalogAuthenticationRequiredReasonCredentialExpired CatalogAuthenticationRequiredReason = "credential-expired" + // A credential was presented and the authority refused it, for example because it was + // revoked, malformed, or issued for another audience. Refreshing the same rejected + // credential is not useful; the caller must sign in again. + CatalogAuthenticationRequiredReasonCredentialRejected CatalogAuthenticationRequiredReason = "credential-rejected" + // No credential was presented, so there is nothing to refresh and the caller must sign in. + CatalogAuthenticationRequiredReasonNoCredential CatalogAuthenticationRequiredReason = "no-credential" +) + +// Kind discriminator for CatalogCandidate. +// Experimental: CatalogCandidateKind is part of an experimental API and may change or be +// removed. +type CatalogCandidateKind string + +const ( + CatalogCandidateKindAiSkill CatalogCandidateKind = "ai-skill" + CatalogCandidateKindMCPServer CatalogCandidateKind = "mcp-server" +) + +// Kind discriminator for CatalogCandidateSource. +type CatalogCandidateSourceKind string + +const ( + CatalogCandidateSourceKindEmbedded CatalogCandidateSourceKind = "embedded" + CatalogCandidateSourceKindURL CatalogCandidateSourceKind = "url" +) + +// A wire feature a caller can require of the catalog surface, negotiated per request. A +// grant means the runtime understands the feature's contract, not that the deployment has +// enabled the operation; typed unavailable results report availability separately. +// Experimental: CatalogCapability is part of an experimental API and may change or be +// removed. +type CatalogCapability string + +const ( + // Understands `application/ai-skill` candidates as discovery-only and typed non-installable. + CatalogCapabilityAiSkillDiscovery CatalogCapability = "ai-skill-discovery" + // Understands the legacy `application/mcp-server+json` media type. + CatalogCapabilityLegacyMCPServerCard CatalogCapability = "legacy-mcp-server-card" + // Understands side-effect-free MCP install-plan requests, results, and plan handles; + // `planning-unavailable` separately reports that planning is not enabled. + CatalogCapabilityMCPInstallPlanning CatalogCapability = "mcp-install-planning" + // Understands the current `application/mcp-server-card+json` media type. + CatalogCapabilityMCPServerCard CatalogCapability = "mcp-server-card" + // Understands plans that enumerate every eligible transport rather than a single preferred + // one. + CatalogCapabilityMultipleTransportChoice CatalogCapability = "multiple-transport-choice" +) + +// Which wire-contract rule an upstream response broke +// Experimental: CatalogContractViolationReason is part of an experimental API and may +// change or be removed. +type CatalogContractViolationReason string + +const ( + // A result carried both a URL and embedded data, when exactly one is permitted. + CatalogContractViolationReasonBothURLAndData CatalogContractViolationReason = "both-url-and-data" + // Two results claimed the same normalised identity. + CatalogContractViolationReasonDuplicateIdentity CatalogContractViolationReason = "duplicate-identity" + // A result carried neither a URL nor embedded data, when exactly one is required. + CatalogContractViolationReasonNeitherURLNorData CatalogContractViolationReason = "neither-url-nor-data" + // A result declared no media type, or one this contract does not model. + CatalogContractViolationReasonUnknownMediaType CatalogContractViolationReason = "unknown-media-type" +) + +// Why a presented handle was rejected +// Experimental: CatalogHandleRejectionReason is part of an experimental API and may change +// or be removed. +type CatalogHandleRejectionReason string + +const ( + // The handle was issued by a different runtime instance. + CatalogHandleRejectionReasonForeign CatalogHandleRejectionReason = "foreign" + // The handle is unparseable, unknown, or was issued for a different operation. + CatalogHandleRejectionReasonInvalid CatalogHandleRejectionReason = "invalid" + // The handle has already been used, and handles are single-use. + CatalogHandleRejectionReasonReplayed CatalogHandleRejectionReason = "replayed" + // The handle's time to live has elapsed. + CatalogHandleRejectionReasonStale CatalogHandleRejectionReason = "stale" +) + +// Which kind of opaque handle was presented +// Experimental: CatalogHandleType is part of an experimental API and may change or be +// removed. +type CatalogHandleType string + +const ( + // A search candidate handle. + CatalogHandleTypeCandidate CatalogHandleType = "candidate" + // An install plan handle. + CatalogHandleTypePlan CatalogHandleType = "plan" +) + +// Which request field was rejected before any work was done +// Experimental: CatalogInvalidRequestField is part of an experimental API and may change or +// be removed. +type CatalogInvalidRequestField string + +const ( + // The supplied card was missing its media type, URL, or data. + CatalogInvalidRequestFieldCard CatalogInvalidRequestField = "card" + // The negotiation block was missing or malformed. + CatalogInvalidRequestFieldContract CatalogInvalidRequestField = "contract" + // The requested candidate kinds were empty or contained a duplicate. + CatalogInvalidRequestFieldKinds CatalogInvalidRequestField = "kinds" + // The requested result count fell outside its permitted range. + CatalogInvalidRequestFieldLimit CatalogInvalidRequestField = "limit" + // The search query was empty or longer than permitted. + CatalogInvalidRequestFieldQuery CatalogInvalidRequestField = "query" + // The requested configuration scope is not one this runtime writes. + CatalogInvalidRequestFieldScope CatalogInvalidRequestField = "scope" + // The plan source was missing or malformed. + CatalogInvalidRequestFieldSource CatalogInvalidRequestField = "source" +) + +// How a card failed validation +// Experimental: CatalogMalformedCardReason is part of an experimental API and may change or +// be removed. +type CatalogMalformedCardReason string + +const ( + // The document is not well-formed JSON. + CatalogMalformedCardReasonInvalidJSON CatalogMalformedCardReason = "invalid-json" + // A field the media type requires is absent. + CatalogMalformedCardReasonMissingRequiredField CatalogMalformedCardReason = "missing-required-field" + // The document does not satisfy its media type's schema. + CatalogMalformedCardReasonSchemaViolation CatalogMalformedCardReason = "schema-violation" + // The document exceeded the permitted size. + CatalogMalformedCardReasonSizeLimitExceeded CatalogMalformedCardReason = "size-limit-exceeded" + // The declared media type is not one this runtime understands. + CatalogMalformedCardReasonUnsupportedMediaType CatalogMalformedCardReason = "unsupported-media-type" +) + +// Whether an MCP server candidate can be planned for installation +// Experimental: CatalogMCPServerInstallability is part of an experimental API and may +// change or be removed. +type CatalogMCPServerInstallability string + +const ( + // An install plan can be computed for this MCP server candidate. + CatalogMCPServerInstallabilityInstallable CatalogMCPServerInstallability = "installable" + // Policy forbids installing this MCP server candidate. + CatalogMCPServerInstallabilityNotInstallablePolicy CatalogMCPServerInstallability = "not-installable-policy" +) + +// Media type a catalog card is interpreted as +// Experimental: CatalogMediaType is part of an experimental API and may change or be +// removed. +type CatalogMediaType string + +const ( + // An AI skill card. Representable and searchable, but typed non-installable. + CatalogMediaTypeApplicationAiSkill CatalogMediaType = "application/ai-skill" + // The current MCP server card media type. + CatalogMediaTypeApplicationMCPServerCardJSON CatalogMediaType = "application/mcp-server-card+json" + // The legacy MCP server card media type, accepted for compatibility. + CatalogMediaTypeApplicationMCPServerJSON CatalogMediaType = "application/mcp-server+json" +) + +// Why capability and protocol-version negotiation refused a caller +// Experimental: CatalogNegotiationRefusedReason is part of an experimental API and may +// change or be removed. +type CatalogNegotiationRefusedReason string + +const ( + // The caller requires at least one capability this runtime cannot honour. + CatalogNegotiationRefusedReasonUnsupportedCapability CatalogNegotiationRefusedReason = "unsupported-capability" + // The caller's protocol version is below the lowest this runtime serves. + CatalogNegotiationRefusedReasonUnsupportedProtocolVersion CatalogNegotiationRefusedReason = "unsupported-protocol-version" +) + +// Categorised network failure, low cardinality so it can be aggregated without carrying a +// URL +// Experimental: CatalogNetworkFailureReason is part of an experimental API and may change +// or be removed. +type CatalogNetworkFailureReason string + +const ( + // The connection was refused or reset. + CatalogNetworkFailureReasonConnectionRefused CatalogNetworkFailureReason = "connection-refused" + // The authority's name could not be resolved. + CatalogNetworkFailureReasonDns CatalogNetworkFailureReason = "dns" + // The authority returned another status the runtime treats as a failure. + CatalogNetworkFailureReasonHTTPStatus CatalogNetworkFailureReason = "http-status" + // No network is available, so nothing was attempted. + CatalogNetworkFailureReasonOffline CatalogNetworkFailureReason = "offline" + // The configured proxy returned 407 and requires authentication. + CatalogNetworkFailureReasonProxyAuthenticationRequired CatalogNetworkFailureReason = "proxy-authentication-required" + // The authority rate-limited requests and supplied or implied a bounded cooldown. + CatalogNetworkFailureReasonRateLimited CatalogNetworkFailureReason = "rate-limited" + // A redirect was refused by the runtime's redirect policy. + CatalogNetworkFailureReasonRedirectRejected CatalogNetworkFailureReason = "redirect-rejected" + // The response exceeded the permitted size. + CatalogNetworkFailureReasonResponseTooLarge CatalogNetworkFailureReason = "response-too-large" + // The authority returned a transient 5xx response. + CatalogNetworkFailureReasonServiceUnavailable CatalogNetworkFailureReason = "service-unavailable" + // The request exceeded its time budget. + CatalogNetworkFailureReasonTimeout CatalogNetworkFailureReason = "timeout" + // The TLS handshake or certificate validation failed. + CatalogNetworkFailureReasonTls CatalogNetworkFailureReason = "tls" +) + +// Why a discoverable candidate cannot be installed +// Experimental: CatalogNotInstallableReason is part of an experimental API and may change +// or be removed. +type CatalogNotInstallableReason string + +const ( + // AI skills are discoverable but have no typed importer in this phase. + CatalogNotInstallableReasonAiSkillNotInstallable CatalogNotInstallableReason = "ai-skill-not-installable" + // This kind of resource is not installable through this surface. + CatalogNotInstallableReasonKindNotInstallable CatalogNotInstallableReason = "kind-not-installable" + // Policy forbids installing this candidate. + CatalogNotInstallableReasonPolicyForbids CatalogNotInstallableReason = "policy-forbids" +) + +// Kind discriminator for CatalogSearchResult. +type CatalogSearchResultKind string + +const ( + CatalogSearchResultKindAuthenticationRequired CatalogSearchResultKind = "authentication-required" + CatalogSearchResultKindContractViolation CatalogSearchResultKind = "contract-violation" + CatalogSearchResultKindInvalidRequest CatalogSearchResultKind = "invalid-request" + CatalogSearchResultKindMalformedCard CatalogSearchResultKind = "malformed-card" + CatalogSearchResultKindNegotiationRefused CatalogSearchResultKind = "negotiation-refused" + CatalogSearchResultKindNetworkFailure CatalogSearchResultKind = "network-failure" + CatalogSearchResultKindPolicyRejected CatalogSearchResultKind = "policy-rejected" + CatalogSearchResultKindSucceeded CatalogSearchResultKind = "succeeded" + CatalogSearchResultKindUnavailable CatalogSearchResultKind = "unavailable" + CatalogSearchResultKindUnsafeRetrieval CatalogSearchResultKind = "unsafe-retrieval" + CatalogSearchResultKindUnsupportedKind CatalogSearchResultKind = "unsupported-kind" +) + +// Why a catalog operation is not available on this runtime +// Experimental: CatalogUnavailableReason is part of an experimental API and may change or +// be removed. +type CatalogUnavailableReason string + +const ( + // No catalog authority is configured for this runtime. + CatalogUnavailableReasonAuthorityNotConfigured CatalogUnavailableReason = "authority-not-configured" + // The surface is disabled by policy on this runtime. + CatalogUnavailableReasonDisabledByPolicy CatalogUnavailableReason = "disabled-by-policy" + // Install planning is not wired up on this runtime build. + CatalogUnavailableReasonPlanningUnavailable CatalogUnavailableReason = "planning-unavailable" + // Bounded search is not wired up on this runtime build. + CatalogUnavailableReasonSearchUnavailable CatalogUnavailableReason = "search-unavailable" +) + +// Why no usable transport could be offered +// Experimental: CatalogUnavailableTransportReason is part of an experimental API and may +// change or be removed. +type CatalogUnavailableTransportReason string + +const ( + // The card advertises no transport this runtime can use. + CatalogUnavailableTransportReasonNoEligibleTransport CatalogUnavailableTransportReason = "no-eligible-transport" + // Eligible remotes could not be enumerated, so no explicit choice can be offered. + CatalogUnavailableTransportReasonRemoteEnumerationUnavailable CatalogUnavailableTransportReason = "remote-enumeration-unavailable" + // Every advertised transport is of a kind this runtime does not implement. + CatalogUnavailableTransportReasonTransportNotSupported CatalogUnavailableTransportReason = "transport-not-supported" +) + +// Which hardened-fetch control refused a retrieval +// Experimental: CatalogUnsafeRetrievalReason is part of an experimental API and may change +// or be removed. +type CatalogUnsafeRetrievalReason string + +const ( + // The URL resolved to a loopback, private, link-local, or cloud metadata address. + CatalogUnsafeRetrievalReasonBlockedAddress CatalogUnsafeRetrievalReason = "blocked-address" + // The URL used a scheme the runtime refuses to fetch. + CatalogUnsafeRetrievalReasonBlockedScheme CatalogUnsafeRetrievalReason = "blocked-scheme" + // The URL embedded credentials. + CatalogUnsafeRetrievalReasonCredentialsInURL CatalogUnsafeRetrievalReason = "credentials-in-url" + // The authority is not permitted for card retrieval. + CatalogUnsafeRetrievalReasonHostNotPermitted CatalogUnsafeRetrievalReason = "host-not-permitted" + // The configured proxy policy refused the request. + CatalogUnsafeRetrievalReasonProxyRejected CatalogUnsafeRetrievalReason = "proxy-rejected" + // A redirect target resolved to a blocked address. + CatalogUnsafeRetrievalReasonRedirectToBlockedAddress CatalogUnsafeRetrievalReason = "redirect-to-blocked-address" +) + +// Whether a pending slash-command invocation effect was applied or cancelled by the host. +// Experimental: CommandsInvocationEffectOutcome is part of an experimental API and may +// change or be removed. +type CommandsInvocationEffectOutcome string + +const ( + // The host applied the pending invocation effect. + CommandsInvocationEffectOutcomeApplied CommandsInvocationEffectOutcome = "applied" + // The host cancelled the pending invocation effect, so any provisional state must be + // reverted. + CommandsInvocationEffectOutcomeCancelled CommandsInvocationEffectOutcome = "cancelled" +) + +// Experimental: CommandsInvocationOrigin is part of an experimental API and may change or +// be removed. +type CommandsInvocationOrigin string + +const ( + CommandsInvocationOriginSettings CommandsInvocationOrigin = "settings" +) + // Neutral SDK discriminator for the connected remote session kind. // Experimental: ConnectedRemoteSessionMetadataKind is part of an experimental API and may // change or be removed. @@ -13311,6 +16751,8 @@ const ( FactoryDurableOperationMarkRunStarted FactoryDurableOperation = "markRunStarted" // Reading the authoritative AI-credit total. FactoryDurableOperationReconcileCreditTotal FactoryDurableOperation = "reconcileCreditTotal" + // Renewing the durable owner lease that proves this process still owns the run. + FactoryDurableOperationRefreshLease FactoryDurableOperation = "refreshLease" // Rolling back an uncommitted subagent admission. FactoryDurableOperationReleaseAgent FactoryDurableOperation = "releaseAgent" // Persisting subagent admission accounting. @@ -13365,9 +16807,11 @@ const ( type FactoryRunFailureType string const ( - FactoryRunFailureTypeFactoryDurableFailure FactoryRunFailureType = "factory_durable_failure" - 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" + FactoryRunFailureTypeFactoryProviderDisconnected FactoryRunFailureType = "factory_provider_disconnected" + FactoryRunFailureTypeFactoryResumeDeclined FactoryRunFailureType = "factory_resume_declined" ) // Current or terminal state of a factory run. @@ -13390,6 +16834,28 @@ const ( FactoryRunStatusRunning FactoryRunStatus = "running" ) +// Why the runtime is requesting a GitHub credential. +// Experimental: GitHubTokenAcquireReason is part of an experimental API and may change or +// be removed. +type GitHubTokenAcquireReason string + +const ( + // The runtime is acquiring the registration's first credential. + GitHubTokenAcquireReasonInitial GitHubTokenAcquireReason = "initial" + // The runtime is replacing a credential that is approaching expiry. + GitHubTokenAcquireReasonRefresh GitHubTokenAcquireReason = "refresh" +) + +// Kind discriminator for GitHubTokenAcquireResult. +// Experimental: GitHubTokenAcquireResultKind is part of an experimental API and may change +// or be removed. +type GitHubTokenAcquireResultKind string + +const ( + GitHubTokenAcquireResultKindCancelled GitHubTokenAcquireResultKind = "cancelled" + GitHubTokenAcquireResultKindToken GitHubTokenAcquireResultKind = "token" +) + // 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). @@ -13498,7 +16964,24 @@ const ( HMACAuthInfoHostHTTPSGitHubCom HMACAuthInfoHost = "https://github.com" ) -// Hook event name dispatched through the SDK callback transport. +// Configuration tier that contributed a discovered hook action. +// Experimental: HookOrigin is part of an experimental API and may change or be removed. +type HookOrigin string + +const ( + // Hook provided by an enabled installed or explicit plugin. Projectless rows omit + // projectPath and do not expand a project directory. + HookOriginPlugin HookOrigin = "plugin" + // Hook enforced by centrally managed policy. + HookOriginPolicy HookOrigin = "policy" + // Hook loaded from repository settings or the repository hook directory. + HookOriginRepository HookOrigin = "repository" + // Hook loaded from user settings or the user's hook directory. + HookOriginUser HookOrigin = "user" +) + +// Hook event name. Discovery emits the file-configurable subset; SDK callbacks additionally +// support callback-only events. // Experimental: HookType is part of an experimental API and may change or be removed. type HookType string @@ -13755,6 +17238,15 @@ const ( MCPAppsSetHostContextDetailsThemeLight MCPAppsSetHostContextDetailsTheme = "light" ) +// Structured MCP elicitation mode. +// Experimental: MCPElicitationFormMode is part of an experimental API and may change or be +// removed. +type MCPElicitationFormMode string + +const ( + MCPElicitationFormModeForm MCPElicitationFormMode = "form" +) + // Kind discriminator for MCPHeadersHandlePendingHeadersRefreshRequest. type MCPHeadersHandlePendingHeadersRefreshRequestKind string @@ -13777,6 +17269,8 @@ const ( ) // Kind discriminator for MCPOauthPendingRequestResponse. +// Experimental: MCPOauthPendingRequestResponseKind is part of an experimental API and may +// change or be removed. type MCPOauthPendingRequestResponseKind string const ( @@ -13784,141 +17278,451 @@ const ( MCPOauthPendingRequestResponseKindToken MCPOauthPendingRequestResponseKind = "token" ) -// 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: MCPSamplingExecutionAction is part of an experimental API and may change or -// be removed. -type MCPSamplingExecutionAction string +// Why a passive MCP OAuth probe determined authentication is needed. +// Experimental: MCPOauthProbeNeedsAuthReason is part of an experimental API and may change +// or be removed. +type MCPOauthProbeNeedsAuthReason string const ( - // The sampling inference was cancelled before completion. - MCPSamplingExecutionActionCancelled MCPSamplingExecutionAction = "cancelled" - // The sampling inference failed or was rejected. - MCPSamplingExecutionActionFailure MCPSamplingExecutionAction = "failure" - // The sampling inference completed and produced a result. - MCPSamplingExecutionActionSuccess MCPSamplingExecutionAction = "success" + // No token was sent and the server requires authentication. + MCPOauthProbeNeedsAuthReasonInitial MCPOauthProbeNeedsAuthReason = "initial" + // A cached token was sent and rejected. + MCPOauthProbeNeedsAuthReasonRefresh MCPOauthProbeNeedsAuthReason = "refresh" + // The server returned a 403 insufficient_scope challenge, indicating additional scopes or + // audience are needed. + MCPOauthProbeNeedsAuthReasonUpscope MCPOauthProbeNeedsAuthReason = "upscope" ) -// 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) -// Experimental: MCPServerConfigDeferTools is part of an experimental API and may change or -// be removed. -type MCPServerConfigDeferTools string +// Status discriminator for MCPOauthProbeResult. +type MCPOauthProbeResultStatus string const ( - // Tools may be deferred under certain conditions - MCPServerConfigDeferToolsAuto MCPServerConfigDeferTools = "auto" - // Tools are always included in the initial tool list, even when tool search is enabled. - MCPServerConfigDeferToolsNever MCPServerConfigDeferTools = "never" + MCPOauthProbeResultStatusAuthenticated MCPOauthProbeResultStatus = "authenticated" + MCPOauthProbeResultStatusFailed MCPOauthProbeResultStatus = "failed" + MCPOauthProbeResultStatusNeedsAuth MCPOauthProbeResultStatus = "needs-auth" + MCPOauthProbeResultStatusNoAuthRequired MCPOauthProbeResultStatus = "no-auth-required" ) -// OAuth grant type to use when authenticating to the remote MCP server. -// Experimental: MCPServerConfigHTTPOauthGrantType is part of an experimental API and may -// change or be removed. -type MCPServerConfigHTTPOauthGrantType string +// Whether a planned configuration change would create or modify an entry +// Experimental: MCPPlanConfigurationOperation is part of an experimental API and may change +// or be removed. +type MCPPlanConfigurationOperation string const ( - // Interactive browser-based authorization code flow with PKCE. - MCPServerConfigHTTPOauthGrantTypeAuthorizationCode MCPServerConfigHTTPOauthGrantType = "authorization_code" - // Headless client credentials flow using the configured OAuth client. - MCPServerConfigHTTPOauthGrantTypeClientCredentials MCPServerConfigHTTPOauthGrantType = "client_credentials" + // Creates a configuration entry that does not exist yet. + MCPPlanConfigurationOperationAdd MCPPlanConfigurationOperation = "add" + // Modifies a configuration entry that already exists. + MCPPlanConfigurationOperationUpdate MCPPlanConfigurationOperation = "update" ) -// Remote transport type. Defaults to "http" when omitted. -// Experimental: MCPServerConfigHTTPType is part of an experimental API and may change or be +// Discriminator for an enumerated required value +// Experimental: MCPPlanEnumValueType is part of an experimental API and may change or be // removed. -type MCPServerConfigHTTPType string +type MCPPlanEnumValueType string const ( - // Streamable HTTP transport. - MCPServerConfigHTTPTypeHTTP MCPServerConfigHTTPType = "http" - // Server-Sent Events transport. - MCPServerConfigHTTPTypeSSE MCPServerConfigHTTPType = "sse" + // One of a fixed, non-empty set of permitted values. + MCPPlanEnumValueTypeEnum MCPPlanEnumValueType = "enum" ) -// Configuration source: user, workspace, plugin, or builtin -// Experimental: MCPServerSource is part of an experimental API and may change or be removed. -type MCPServerSource string +// Kind discriminator for MCPPlanInstallResult. +type MCPPlanInstallResultKind string const ( - // Server bundled with the runtime. - MCPServerSourceBuiltin MCPServerSource = "builtin" - // Server contributed by an installed plugin. - MCPServerSourcePlugin MCPServerSource = "plugin" - // Server configured in the user's global MCP configuration. - MCPServerSourceUser MCPServerSource = "user" - // Server configured by the current workspace. - MCPServerSourceWorkspace MCPServerSource = "workspace" + MCPPlanInstallResultKindAuthenticationRequired MCPPlanInstallResultKind = "authentication-required" + MCPPlanInstallResultKindContractViolation MCPPlanInstallResultKind = "contract-violation" + MCPPlanInstallResultKindHandleRejected MCPPlanInstallResultKind = "handle-rejected" + MCPPlanInstallResultKindInvalidRequest MCPPlanInstallResultKind = "invalid-request" + MCPPlanInstallResultKindMalformedCard MCPPlanInstallResultKind = "malformed-card" + MCPPlanInstallResultKindNegotiationRefused MCPPlanInstallResultKind = "negotiation-refused" + MCPPlanInstallResultKindNetworkFailure MCPPlanInstallResultKind = "network-failure" + MCPPlanInstallResultKindNotInstallable MCPPlanInstallResultKind = "not-installable" + MCPPlanInstallResultKindPlanned MCPPlanInstallResultKind = "planned" + MCPPlanInstallResultKindPolicyRejected MCPPlanInstallResultKind = "policy-rejected" + MCPPlanInstallResultKindUnavailable MCPPlanInstallResultKind = "unavailable" + MCPPlanInstallResultKindUnavailableTransport MCPPlanInstallResultKind = "unavailable-transport" + MCPPlanInstallResultKindUnsafeRetrieval MCPPlanInstallResultKind = "unsafe-retrieval" ) -// 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 +// Discriminator for a candidate-backed install-plan source +// Experimental: MCPPlanInstallSourceCandidateKind is part of an experimental API and may +// change or be removed. +type MCPPlanInstallSourceCandidateKind string const ( - // The server is connected and available. - MCPServerStatusConnected MCPServerStatus = "connected" - // The server is configured but disabled. - MCPServerStatusDisabled MCPServerStatus = "disabled" - // The server failed to connect or initialize. - MCPServerStatusFailed MCPServerStatus = "failed" - // The server requires authentication before it can connect. - MCPServerStatusNeedsAuth MCPServerStatus = "needs-auth" - // The server is not configured for this session. - 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" + // Plan from a candidate returned by catalog search. + MCPPlanInstallSourceCandidateKindCandidate MCPPlanInstallSourceCandidateKind = "candidate" ) -// 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: MCPSetEnvValueModeDetails is part of an experimental API and may change or -// be removed. -type MCPSetEnvValueModeDetails string +// Discriminator for a caller-supplied-card install-plan source +// Experimental: MCPPlanInstallSourceCardKind is part of an experimental API and may change +// or be removed. +type MCPPlanInstallSourceCardKind string const ( - // Treat MCP server environment values as literal strings. - MCPSetEnvValueModeDetailsDirect MCPSetEnvValueModeDetails = "direct" - // Treat MCP server environment values as host-side references to resolve before launch. - MCPSetEnvValueModeDetailsIndirect MCPSetEnvValueModeDetails = "indirect" + // Plan directly from a caller-supplied card. + MCPPlanInstallSourceCardKindCard MCPPlanInstallSourceCardKind = "card" ) -// Consumer allowed to call an MCP tool. -// Experimental: MCPToolUIVisibility is part of an experimental API and may change or be -// removed. -type MCPToolUIVisibility string +// Kind discriminator for MCPPlanInstallSource. +type MCPPlanInstallSourceKind string const ( - // An MCP App view may call the tool. - MCPToolUIVisibilityApp MCPToolUIVisibility = "app" - // The model may call the tool. - MCPToolUIVisibilityModel MCPToolUIVisibility = "model" + MCPPlanInstallSourceKindCandidate MCPPlanInstallSourceKind = "candidate" + MCPPlanInstallSourceKindCard MCPPlanInstallSourceKind = "card" ) -// The current agent mode for this session (e.g., 'interactive', 'plan', 'autopilot') -// Experimental: MetadataSnapshotCurrentMode is part of an experimental API and may change +// Discriminator for a package-backed transport choice +// Experimental: MCPPlanPackageInstallMethod is part of an experimental API and may change // or be removed. -type MetadataSnapshotCurrentMode string +type MCPPlanPackageInstallMethod string const ( - // The agent is working autonomously toward task completion. - MetadataSnapshotCurrentModeAutopilot MetadataSnapshotCurrentMode = "autopilot" - // The agent is responding interactively to the user. - MetadataSnapshotCurrentModeInteractive MetadataSnapshotCurrentMode = "interactive" - // The agent is preparing a plan before making changes. - MetadataSnapshotCurrentModePlan MetadataSnapshotCurrentMode = "plan" + // Install and run a local package. + MCPPlanPackageInstallMethodPackage MCPPlanPackageInstallMethod = "package" ) -// Whether the remote task originated from Copilot Coding Agent (cca) or a CLI `--remote` +// Transport exposed by a locally launched package +// Experimental: MCPPlanPackageTransport is part of an experimental API and may change or be +// removed. +type MCPPlanPackageTransport string + +const ( + // A locally launched process spoken to over standard input and output. + MCPPlanPackageTransportStdio MCPPlanPackageTransport = "stdio" +) + +// What policy decided for a planned server +// Experimental: MCPPlanPolicyDecision is part of an experimental API and may change or be +// removed. +type MCPPlanPolicyDecision string + +const ( + // Policy permits the server. + MCPPlanPolicyDecisionAllowed MCPPlanPolicyDecision = "allowed" + // Policy forbids the server, so the plan cannot be applied. + MCPPlanPolicyDecisionBlocked MCPPlanPolicyDecision = "blocked" + // Policy permits the server only after an explicit approval. + MCPPlanPolicyDecisionRequiresApproval MCPPlanPolicyDecision = "requires-approval" +) + +// Which authority produced a policy decision +// Experimental: MCPPlanPolicySource is part of an experimental API and may change or be +// removed. +type MCPPlanPolicySource string + +const ( + // An enterprise allowlist evaluated the server. + MCPPlanPolicySourceEnterpriseAllowlist MCPPlanPolicySource = "enterprise-allowlist" + // Local trust settings evaluated the server. + MCPPlanPolicySourceLocalTrust MCPPlanPolicySource = "local-trust" + // No policy applied, so the server is permitted by default. + MCPPlanPolicySourceNone MCPPlanPolicySource = "none" + // The registry the card came from evaluated the server. + MCPPlanPolicySourceRegistryPolicy MCPPlanPolicySource = "registry-policy" +) + +// Discriminator for a remote-endpoint transport choice +// Experimental: MCPPlanRemoteInstallMethod is part of an experimental API and may change or +// be removed. +type MCPPlanRemoteInstallMethod string + +const ( + // Connect to a remote endpoint. + MCPPlanRemoteInstallMethodRemote MCPPlanRemoteInstallMethod = "remote" +) + +// Transport exposed by a remote endpoint +// Experimental: MCPPlanRemoteTransport is part of an experimental API and may change or be +// removed. +type MCPPlanRemoteTransport string + +const ( + // An HTTP endpoint. + MCPPlanRemoteTransportHTTP MCPPlanRemoteTransport = "http" + // A server-sent events endpoint. + MCPPlanRemoteTransportSSE MCPPlanRemoteTransport = "sse" + // A streamable HTTP endpoint. + MCPPlanRemoteTransportStreamableHTTP MCPPlanRemoteTransport = "streamable-http" +) + +// Discriminator for an enumerated required value +// Experimental: MCPPlanRequiredValueEnumKind is part of an experimental API and may change +// or be removed. +type MCPPlanRequiredValueEnumKind string + +const ( + // The value uses a fixed non-empty enumeration. + MCPPlanRequiredValueEnumKindEnum MCPPlanRequiredValueEnumKind = "enum" +) + +// Kind discriminator for MCPPlanRequiredValue. +type MCPPlanRequiredValueKind string + +const ( + MCPPlanRequiredValueKindEnum MCPPlanRequiredValueKind = "enum" + MCPPlanRequiredValueKindScalar MCPPlanRequiredValueKind = "scalar" +) + +// Discriminator for a scalar required value +// Experimental: MCPPlanRequiredValueScalarKind is part of an experimental API and may +// change or be removed. +type MCPPlanRequiredValueScalarKind string + +const ( + // The value uses one scalar type. + MCPPlanRequiredValueScalarKindScalar MCPPlanRequiredValueScalarKind = "scalar" +) + +// Scalar type a required value must conform to +// Experimental: MCPPlanScalarValueType is part of an experimental API and may change or be +// removed. +type MCPPlanScalarValueType string + +const ( + // A boolean. + MCPPlanScalarValueTypeBoolean MCPPlanScalarValueType = "boolean" + // A number. + MCPPlanScalarValueTypeNumber MCPPlanScalarValueType = "number" + // A filesystem path. + MCPPlanScalarValueTypePath MCPPlanScalarValueType = "path" + // Free text. + MCPPlanScalarValueTypeString MCPPlanScalarValueType = "string" +) + +// Configuration scope an MCP install plan targets +// Experimental: MCPPlanScope is part of an experimental API and may change or be removed. +type MCPPlanScope string + +const ( + // The user's own MCP configuration. + MCPPlanScopeUser MCPPlanScope = "user" +) + +// Transport discriminator for MCPPlanTransportChoice. +type MCPPlanTransportChoiceTransport string + +const ( + MCPPlanTransportChoiceTransportHTTP MCPPlanTransportChoiceTransport = "http" + MCPPlanTransportChoiceTransportSSE MCPPlanTransportChoiceTransport = "sse" + MCPPlanTransportChoiceTransportStdio MCPPlanTransportChoiceTransport = "stdio" + MCPPlanTransportChoiceTransportStreamableHTTP MCPPlanTransportChoiceTransport = "streamable-http" +) + +// Where a required value is applied when the planned server is launched +// Experimental: MCPPlanValueCategory is part of an experimental API and may change or be +// removed. +type MCPPlanValueCategory string + +const ( + // Set as an environment variable on the launched process. + MCPPlanValueCategoryEnvironmentVariable MCPPlanValueCategory = "environment-variable" + // Sent as a request header to a remote endpoint. + MCPPlanValueCategoryHeader MCPPlanValueCategory = "header" + // Passed to the packaged server itself. + MCPPlanValueCategoryPackageArgument MCPPlanValueCategory = "package-argument" + // Passed to the runtime that launches the package. + MCPPlanValueCategoryRuntimeArgument MCPPlanValueCategory = "runtime-argument" + // Substituted into the remote endpoint URL. + MCPPlanValueCategoryURLVariable MCPPlanValueCategory = "url-variable" +) + +// 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: MCPSamplingExecutionAction is part of an experimental API and may change or +// be removed. +type MCPSamplingExecutionAction string + +const ( + // The sampling inference was cancelled before completion. + MCPSamplingExecutionActionCancelled MCPSamplingExecutionAction = "cancelled" + // The sampling inference failed or was rejected. + MCPSamplingExecutionActionFailure MCPSamplingExecutionAction = "failure" + // The sampling inference completed and produced a result. + MCPSamplingExecutionActionSuccess MCPSamplingExecutionAction = "success" +) + +// Discriminator for an embedded MCP server card +// Experimental: MCPServerCardEmbeddedKind is part of an experimental API and may change or +// be removed. +type MCPServerCardEmbeddedKind string + +const ( + // Use the embedded card document. + MCPServerCardEmbeddedKindEmbedded MCPServerCardEmbeddedKind = "embedded" +) + +// JSON MCP card media type accepted for install planning +// Experimental: MCPServerCardMediaType is part of an experimental API and may change or be +// removed. +type MCPServerCardMediaType string + +const ( + // The current MCP server card media type. + MCPServerCardMediaTypeApplicationMCPServerCardJSON MCPServerCardMediaType = "application/mcp-server-card+json" + // The legacy MCP server card media type, accepted for compatibility. + MCPServerCardMediaTypeApplicationMCPServerJSON MCPServerCardMediaType = "application/mcp-server+json" +) + +// Kind discriminator for MCPServerCardReference. +type MCPServerCardReferenceKind string + +const ( + MCPServerCardReferenceKindEmbedded MCPServerCardReferenceKind = "embedded" + MCPServerCardReferenceKindURL MCPServerCardReferenceKind = "url" +) + +// Discriminator for a URL-backed MCP server card +// Experimental: MCPServerCardURLKind is part of an experimental API and may change or be +// removed. +type MCPServerCardURLKind string + +const ( + // Retrieve the card from its URL. + MCPServerCardURLKindURL MCPServerCardURLKind = "url" +) + +// 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) +// Experimental: MCPServerConfigDeferTools is part of an experimental API and may change or +// be removed. +type MCPServerConfigDeferTools string + +const ( + // Tools may be deferred under certain conditions + MCPServerConfigDeferToolsAuto MCPServerConfigDeferTools = "auto" + // Tools are always included in the initial tool list, even when tool search is enabled. + MCPServerConfigDeferToolsNever MCPServerConfigDeferTools = "never" +) + +// OAuth grant type to use when authenticating to the remote MCP server. +// Experimental: MCPServerConfigHTTPOauthGrantType is part of an experimental API and may +// change or be removed. +type MCPServerConfigHTTPOauthGrantType string + +const ( + // Interactive browser-based authorization code flow with PKCE. + MCPServerConfigHTTPOauthGrantTypeAuthorizationCode MCPServerConfigHTTPOauthGrantType = "authorization_code" + // Headless client credentials flow using the configured OAuth client. + MCPServerConfigHTTPOauthGrantTypeClientCredentials MCPServerConfigHTTPOauthGrantType = "client_credentials" +) + +// Remote transport type. Defaults to "http" when omitted. +// Experimental: MCPServerConfigHTTPType is part of an experimental API and may change or be +// removed. +type MCPServerConfigHTTPType string + +const ( + // Streamable HTTP transport. + MCPServerConfigHTTPTypeHTTP MCPServerConfigHTTPType = "http" + // Server-Sent Events transport. + MCPServerConfigHTTPTypeSSE MCPServerConfigHTTPType = "sse" +) + +// In-process MCP transport type. +// Experimental: MCPServerConfigMemoryType is part of an experimental API and may change or +// be removed. +type MCPServerConfigMemoryType string + +const ( + MCPServerConfigMemoryTypeMemory MCPServerConfigMemoryType = "memory" +) + +// Local MCP transport type. +// Experimental: MCPServerConfigStdioType is part of an experimental API and may change or +// be removed. +type MCPServerConfigStdioType string + +const ( + // Legacy alias for the local stdio transport. + MCPServerConfigStdioTypeLocal MCPServerConfigStdioType = "local" + // Server communicates over stdio with a local child process. + MCPServerConfigStdioTypeStdio MCPServerConfigStdioType = "stdio" +) + +// Configuration source: user, workspace, plugin, or builtin +// Experimental: MCPServerSource is part of an experimental API and may change or be removed. +type MCPServerSource string + +const ( + // Server bundled with the runtime. + MCPServerSourceBuiltin MCPServerSource = "builtin" + // Server contributed by an installed plugin. + MCPServerSourcePlugin MCPServerSource = "plugin" + // Server configured in the user's global MCP configuration. + MCPServerSourceUser MCPServerSource = "user" + // Server configured by the current workspace. + MCPServerSourceWorkspace MCPServerSource = "workspace" +) + +// 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 + +const ( + // The server is connected and available. + MCPServerStatusConnected MCPServerStatus = "connected" + // The server is configured but disabled. + MCPServerStatusDisabled MCPServerStatus = "disabled" + // The server failed to connect or initialize. + MCPServerStatusFailed MCPServerStatus = "failed" + // The server requires authentication before it can connect. + MCPServerStatusNeedsAuth MCPServerStatus = "needs-auth" + // The server is not configured for this session. + 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 +// 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: MCPSetEnvValueModeDetails is part of an experimental API and may change or +// be removed. +type MCPSetEnvValueModeDetails string + +const ( + // Treat MCP server environment values as literal strings. + MCPSetEnvValueModeDetailsDirect MCPSetEnvValueModeDetails = "direct" + // Treat MCP server environment values as host-side references to resolve before launch. + MCPSetEnvValueModeDetailsIndirect MCPSetEnvValueModeDetails = "indirect" +) + +// Consumer allowed to call an MCP tool. +// Experimental: MCPToolUIVisibility is part of an experimental API and may change or be +// removed. +type MCPToolUIVisibility string + +const ( + // An MCP App view may call the tool. + MCPToolUIVisibilityApp MCPToolUIVisibility = "app" + // The model may call the tool. + MCPToolUIVisibilityModel MCPToolUIVisibility = "model" +) + +// The current agent mode for this session (e.g., 'interactive', 'plan', 'autopilot') +// Experimental: MetadataSnapshotCurrentMode is part of an experimental API and may change +// or be removed. +type MetadataSnapshotCurrentMode string + +const ( + // The agent is working autonomously toward task completion. + MetadataSnapshotCurrentModeAutopilot MetadataSnapshotCurrentMode = "autopilot" + // The agent is responding interactively to the user. + MetadataSnapshotCurrentModeInteractive MetadataSnapshotCurrentMode = "interactive" + // The agent is preparing a plan before making changes. + MetadataSnapshotCurrentModePlan MetadataSnapshotCurrentMode = "plan" +) + +// Whether the remote task originated from Copilot Coding Agent (cca) or a CLI `--remote` // invocation. // Experimental: MetadataSnapshotRemoteMetadataTaskType is part of an experimental API and // may change or be removed. @@ -13931,6 +17735,38 @@ const ( MetadataSnapshotRemoteMetadataTaskTypeCLI MetadataSnapshotRemoteMetadataTaskType = "cli" ) +// Origin of an effective session model change. +// Experimental: ModelChangeSource is part of an experimental API and may change or be +// removed. +type ModelChangeSource string + +const ( + // Selecting an agent selected its configured model. + ModelChangeSourceAgent ModelChangeSource = "agent" + // The runtime selected the model automatically, such as rate-limit recovery or refusal + // fallback. + ModelChangeSourceAutomatic ModelChangeSource = "automatic" + // The user selected the model with the `/config` alias. + ModelChangeSourceConfigCommand ModelChangeSource = "config_command" + // Organization-managed settings selected the model. + ModelChangeSourceManagedSettings ModelChangeSource = "managed_settings" + // The user selected a model directly with `/model `. + ModelChangeSourceModelCommand ModelChangeSource = "model_command" + // The user selected the model in the model picker, including the picker opened by bare + // `/model`. + ModelChangeSourceModelPicker ModelChangeSource = "model_picker" + // Entering, leaving, or reconfiguring plan mode selected the model. + ModelChangeSourcePlanMode ModelChangeSource = "plan_mode" + // Repository settings selected the model. + ModelChangeSourceRepoSettings ModelChangeSource = "repo_settings" + // An SDK or RPC caller selected the model. + ModelChangeSourceSDK ModelChangeSource = "sdk" + // The user selected the model with `/settings`. + ModelChangeSourceSettingsCommand ModelChangeSource = "settings_command" + // Startup model resolution selected the model. + ModelChangeSourceStartup ModelChangeSource = "startup" +) + // Model capability category for grouping in the model picker // Experimental: ModelPickerCategory is part of an experimental API and may change or be // removed. @@ -14064,6 +17900,7 @@ type PermissionDecisionApproveForLocationApprovalKind string const ( PermissionDecisionApproveForLocationApprovalKindCommands PermissionDecisionApproveForLocationApprovalKind = "commands" PermissionDecisionApproveForLocationApprovalKindCustomTool PermissionDecisionApproveForLocationApprovalKind = "custom-tool" + PermissionDecisionApproveForLocationApprovalKindExtensionEnvAccess PermissionDecisionApproveForLocationApprovalKind = "extension-env-access" PermissionDecisionApproveForLocationApprovalKindExtensionManagement PermissionDecisionApproveForLocationApprovalKind = "extension-management" PermissionDecisionApproveForLocationApprovalKindExtensionPermissionAccess PermissionDecisionApproveForLocationApprovalKind = "extension-permission-access" PermissionDecisionApproveForLocationApprovalKindFactory PermissionDecisionApproveForLocationApprovalKind = "factory" @@ -14080,6 +17917,7 @@ type PermissionDecisionApproveForSessionApprovalKind string const ( PermissionDecisionApproveForSessionApprovalKindCommands PermissionDecisionApproveForSessionApprovalKind = "commands" PermissionDecisionApproveForSessionApprovalKindCustomTool PermissionDecisionApproveForSessionApprovalKind = "custom-tool" + PermissionDecisionApproveForSessionApprovalKindExtensionEnvAccess PermissionDecisionApproveForSessionApprovalKind = "extension-env-access" PermissionDecisionApproveForSessionApprovalKindExtensionManagement PermissionDecisionApproveForSessionApprovalKind = "extension-management" PermissionDecisionApproveForSessionApprovalKindExtensionPermissionAccess PermissionDecisionApproveForSessionApprovalKind = "extension-permission-access" PermissionDecisionApproveForSessionApprovalKindFactory PermissionDecisionApproveForSessionApprovalKind = "factory" @@ -14111,6 +17949,55 @@ 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 response followed the assisted-approval judge recommendation. + PermissionDecisionSourceAssistedApproval PermissionDecisionSource = "assisted_approval" + // 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 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 ( + // An Agent Client Protocol host. + PermissionDecisionSurfaceAcp PermissionDecisionSurface = "acp" + // 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. @@ -14123,19 +18010,51 @@ const ( PermissionLocationTypeRepo PermissionLocationType = "repo" ) -// Current or requested allow-all mode. -// Experimental: PermissionsAllowAllMode is part of an experimental API and may change or be -// removed. -type PermissionsAllowAllMode string +// Current or requested permission mode. +// Experimental: PermissionMode is part of an experimental API and may change or be removed. +type PermissionMode string const ( - // 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. - PermissionsAllowAllModeAuto PermissionsAllowAllMode = "auto" - // Permission requests follow the normal approval flow. - PermissionsAllowAllModeOff PermissionsAllowAllMode = "off" // Tool, path, and URL permission requests are automatically approved. - PermissionsAllowAllModeOn PermissionsAllowAllMode = "on" + PermissionModeAllowAll PermissionMode = "allow-all" + // Permission requests include an LLM safety recommendation; clients may automatically + // approve requests judged acceptable. + PermissionModeAssisted PermissionMode = "assisted" + // Permission requests follow the normal approval flow. + PermissionModeManual PermissionMode = "manual" +) + +// Optional source for permission-mode telemetry. Defaults to `rpc` when omitted for SDK +// callers. +// Experimental: PermissionModeSource is part of an experimental API and may change or be +// removed. +type PermissionModeSource string + +const ( + // The mode was set by confirming autopilot behavior. + PermissionModeSourceAutopilotConfirmation PermissionModeSource = "autopilot_confirmation" + // The mode was set from a CLI command-line flag. + PermissionModeSourceCLIFlag PermissionModeSource = "cli_flag" + // The mode was set through an RPC caller. + PermissionModeSourceRPC PermissionModeSource = "rpc" + // The mode was set by a slash command. + PermissionModeSourceSlashCommand PermissionModeSource = "slash_command" + // The mode was set at startup by the `defaultPermissionMode` user setting. + PermissionModeSourceUserSetting PermissionModeSource = "user_setting" +) + +// Response capability available to the client when it settled a permission request. +// Experimental: PermissionResponseCapability is part of an experimental API and may change +// or be removed. +type PermissionResponseCapability string + +const ( + // The client could return an automated response but could not ask a user. + PermissionResponseCapabilityHeadless PermissionResponseCapability = "headless" + // The client could ask a user for this decision. + PermissionResponseCapabilityInteractive PermissionResponseCapability = "interactive" + // The client had no response path available. + PermissionResponseCapabilityNone PermissionResponseCapability = "none" ) // Allowed values for the `PermissionsConfigureAdditionalContentExclusionPolicyScope` @@ -14157,6 +18076,7 @@ type PermissionsLocationsAddToolApprovalDetailsKind string const ( PermissionsLocationsAddToolApprovalDetailsKindCommands PermissionsLocationsAddToolApprovalDetailsKind = "commands" PermissionsLocationsAddToolApprovalDetailsKindCustomTool PermissionsLocationsAddToolApprovalDetailsKind = "custom-tool" + PermissionsLocationsAddToolApprovalDetailsKindExtensionEnvAccess PermissionsLocationsAddToolApprovalDetailsKind = "extension-env-access" PermissionsLocationsAddToolApprovalDetailsKindExtensionManagement PermissionsLocationsAddToolApprovalDetailsKind = "extension-management" PermissionsLocationsAddToolApprovalDetailsKindExtensionPermissionAccess PermissionsLocationsAddToolApprovalDetailsKind = "extension-permission-access" PermissionsLocationsAddToolApprovalDetailsKindFactory PermissionsLocationsAddToolApprovalDetailsKind = "factory" @@ -14180,22 +18100,6 @@ const ( PermissionsModifyRulesScopeSession PermissionsModifyRulesScope = "session" ) -// Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers. -// Experimental: PermissionsSetAllowAllSource is part of an experimental API and may change -// or be removed. -type PermissionsSetAllowAllSource string - -const ( - // Allow-all was enabled by confirming autopilot behavior. - PermissionsSetAllowAllSourceAutopilotConfirmation PermissionsSetAllowAllSource = "autopilot_confirmation" - // Allow-all was enabled from a CLI command-line flag. - PermissionsSetAllowAllSourceCLIFlag PermissionsSetAllowAllSource = "cli_flag" - // Allow-all was enabled through an RPC caller. - PermissionsSetAllowAllSourceRPC PermissionsSetAllowAllSource = "rpc" - // Allow-all was enabled by a slash command. - PermissionsSetAllowAllSourceSlashCommand PermissionsSetAllowAllSource = "slash_command" -) - // Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers. // Experimental: PermissionsSetApproveAllSource is part of an experimental API and may // change or be removed. @@ -14210,6 +18114,20 @@ const ( PermissionsSetApproveAllSourceRPC PermissionsSetApproveAllSource = "rpc" // Allow-all was enabled by a slash command. PermissionsSetApproveAllSourceSlashCommand PermissionsSetApproveAllSource = "slash_command" + // Allow-all was enabled at startup by the `defaultPermissionMode` user setting. + PermissionsSetApproveAllSourceUserSetting PermissionsSetApproveAllSource = "user_setting" +) + +// Controls whether the runtime may defer loading an external tool definition. +// Experimental: ProtocolExternalToolDefer is part of an experimental API and may change or +// be removed. +type ProtocolExternalToolDefer string + +const ( + // The runtime may defer the tool according to its tool-loading policy. + ProtocolExternalToolDeferAuto ProtocolExternalToolDefer = "auto" + // The runtime must include the tool without deferring it. + ProtocolExternalToolDeferNever ProtocolExternalToolDefer = "never" ) // Provider transport. Defaults to "http". @@ -14359,6 +18277,26 @@ const ( RemoteControlStatusStateOff RemoteControlStatusState = "off" ) +// What a remote host says one of its sessions is doing right now. Deliberately coarse: this +// is what a host can report for EVERY session in a catalogue listing, without a client +// subscribing to each one. AHP's `SessionSummary.status` is the source today; +// `input-needed` covers both a permission prompt and an `ask_user` question, since the +// summary does not say which. +// Experimental: RemoteSessionHostStatus is part of an experimental API and may change or be +// removed. +type RemoteSessionHostStatus string + +const ( + // The session ended its last turn in an error. + RemoteSessionHostStatusError RemoteSessionHostStatus = "error" + // No turn is running. + RemoteSessionHostStatusIdle RemoteSessionHostStatus = "idle" + // The session is blocked on the user: a permission prompt or an `ask_user` question. + RemoteSessionHostStatusInputNeeded RemoteSessionHostStatus = "input-needed" + // A turn is running. + RemoteSessionHostStatusWorking RemoteSessionHostStatus = "working" +) + // Whether the remote task originated from CCA or CLI `--remote`. // Experimental: RemoteSessionMetadataTaskType is part of an experimental API and may change // or be removed. @@ -14386,6 +18324,28 @@ const ( RemoteSessionModeOn RemoteSessionMode = "on" ) +// Origin of the sandbox choice supplied by an internal client. +// Experimental: SandboxConfigSource is part of an experimental API and may change or be +// removed. +type SandboxConfigSource string + +const ( + // The client applied the default because no sandbox preference was configured. + SandboxConfigSourceNeverConfigured SandboxConfigSource = "never_configured" + // A repository policy selected the sandbox state. + SandboxConfigSourceRepositoryPolicy SandboxConfigSource = "repository_policy" + // The user disabled the sandbox for the current session. + SandboxConfigSourceSessionDisabled SandboxConfigSource = "session_disabled" + // A command-line flag selected the sandbox state for this session. + SandboxConfigSourceSessionFlag SandboxConfigSource = "session_flag" + // The client disabled the sandbox because the host cannot enforce it. + SandboxConfigSourceUnsupportedHost SandboxConfigSource = "unsupported_host" + // The user's persisted settings disabled the sandbox. + SandboxConfigSourceUserDisabled SandboxConfigSource = "user_disabled" + // The user's persisted settings enabled the sandbox. + SandboxConfigSourceUserEnabled SandboxConfigSource = "user_enabled" +) + // The UI mode the agent was in when this message was sent. Defaults to the session's // current mode. // Experimental: SendAgentMode is part of an experimental API and may change or be removed. @@ -14847,6 +18807,19 @@ const ( SessionWorkingDirectoryContextHostTypeGitHub SessionWorkingDirectoryContextHostType = "github" ) +// Type discriminator for SettableAuthInfo. +type SettableAuthInfoType string + +const ( + SettableAuthInfoTypeAPIKey SettableAuthInfoType = "api-key" + SettableAuthInfoTypeCopilotAPIToken SettableAuthInfoType = "copilot-api-token" + SettableAuthInfoTypeEnv SettableAuthInfoType = "env" + SettableAuthInfoTypeGhCLI SettableAuthInfoType = "gh-cli" + SettableAuthInfoTypeHMAC SettableAuthInfoType = "hmac" + SettableAuthInfoTypeToken SettableAuthInfoType = "token" + SettableAuthInfoTypeUser SettableAuthInfoType = "user" +) + // 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 @@ -14913,7 +18886,7 @@ const ( SkillDiscoveryScopeProject SkillDiscoveryScope = "project" ) -// Source location type (e.g., project, personal-copilot, plugin, builtin) +// Source location type (e.g., project, personal-copilot, plugin, builtin, sdk) // Experimental: SkillSource is part of an experimental API and may change or be removed. type SkillSource string @@ -14932,6 +18905,8 @@ const ( SkillSourcePlugin SkillSource = "plugin" // Skill defined in the current project's skill directories. SkillSourceProject SkillSource = "project" + // Pathless skill supplied lazily by an SDK skill provider. + SkillSourceSDK SkillSource = "sdk" ) // Optional completion hint for the input (e.g. 'directory' for filesystem path completion) @@ -14948,9 +18923,13 @@ const ( type SlashCommandInvocationResultKind string const ( + SlashCommandInvocationResultKindAddTimelineEntry SlashCommandInvocationResultKind = "add-timeline-entry" SlashCommandInvocationResultKindAgentPrompt SlashCommandInvocationResultKind = "agent-prompt" SlashCommandInvocationResultKindCompleted SlashCommandInvocationResultKind = "completed" SlashCommandInvocationResultKindSelectSubcommand SlashCommandInvocationResultKind = "select-subcommand" + SlashCommandInvocationResultKindSetModel SlashCommandInvocationResultKind = "set-model" + SlashCommandInvocationResultKindSetPlanModel SlashCommandInvocationResultKind = "set-plan-model" + SlashCommandInvocationResultKindShowDialog SlashCommandInvocationResultKind = "show-dialog" SlashCommandInvocationResultKindText SlashCommandInvocationResultKind = "text" ) @@ -14969,6 +18948,13 @@ const ( SlashCommandKindSkill SlashCommandKind = "skill" ) +// Discriminator for a model-picker dialog. +type SlashCommandModelPickerDialogKind string + +const ( + SlashCommandModelPickerDialogKindModelPicker SlashCommandModelPickerDialogKind = "model-picker" +) + // Context tier override for matching subagents // Experimental: SubagentSettingsEntryContextTier is part of an experimental API and may // change or be removed. @@ -14983,6 +18969,21 @@ const ( SubagentSettingsEntryContextTierLongContext SubagentSettingsEntryContextTier = "long_context" ) +// Semantic result of evaluating a task completion request +// Experimental: TaskCompletionOutcome is part of an experimental API and may change or be +// removed. +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" +) + // Whether task execution is synchronously awaited or managed in the background // Experimental: TaskExecutionMode is part of an experimental API and may change or be // removed. @@ -15041,6 +19042,23 @@ const ( TaskStatusRunning TaskStatus = "running" ) +// Execution outcome classification. +// Experimental: ToolResultType is part of an experimental API and may change or be removed. +type ToolResultType string + +const ( + // Permission policy denied the tool request. + ToolResultTypeDenied ToolResultType = "denied" + // The tool failed. + ToolResultTypeFailure ToolResultType = "failure" + // The tool request was rejected before execution. + ToolResultTypeRejected ToolResultType = "rejected" + // The tool completed successfully. + ToolResultTypeSuccess ToolResultType = "success" + // The tool exceeded its execution timeout. + ToolResultTypeTimeout ToolResultType = "timeout" +) + // User's choice for auto-mode switching: yes (allow this turn), yes_always (allow + persist // as setting), or no (decline). // Experimental: UIAutoModeSwitchResponse is part of an experimental API and may change or @@ -15162,6 +19180,7 @@ type UserToolSessionApprovalKind string const ( UserToolSessionApprovalKindCommands UserToolSessionApprovalKind = "commands" UserToolSessionApprovalKindCustomTool UserToolSessionApprovalKind = "custom-tool" + UserToolSessionApprovalKindExtensionEnvAccess UserToolSessionApprovalKind = "extension-env-access" UserToolSessionApprovalKindExtensionManagement UserToolSessionApprovalKind = "extension-management" UserToolSessionApprovalKindExtensionPermissionAccess UserToolSessionApprovalKind = "extension-permission-access" UserToolSessionApprovalKindFactory UserToolSessionApprovalKind = "factory" @@ -15281,12 +19300,12 @@ func (a *ServerAccountAPI) GetCurrentAuth(ctx context.Context) (*AccountGetCurre return &result, nil } -// GetQuota gets Copilot quota usage for the authenticated user or supplied GitHub token. +// GetQuota gets Copilot quota usage for the current or opaquely selected authenticated user. // // RPC method: account.getQuota. // -// Parameters: Optional GitHub token used to look up quota for a specific user instead of -// the global auth context. +// Parameters: Optional opaque account selection or compatibility GitHub token used to look +// up quota. // // Returns: Quota usage snapshots for the resolved user, keyed by quota type. func (a *ServerAccountAPI) GetQuota(ctx context.Context, params *AccountGetQuotaRequest) (*AccountGetQuotaResult, error) { @@ -15301,11 +19320,13 @@ func (a *ServerAccountAPI) GetQuota(ctx context.Context, params *AccountGetQuota return &result, nil } -// Login stores authentication credentials after successful login (e.g., device code flow). +// Login validates and stores authentication credentials. When login is omitted, resolves +// the authenticated user from the token before persistence. // // RPC method: account.login. // -// Parameters: Credentials to store after successful authentication +// Parameters: Credentials to validate and store. Omit login to resolve the authenticated +// user from the token. // // Returns: Result of a successful login; throws on failure func (a *ServerAccountAPI) Login(ctx context.Context, params *AccountLoginRequest) (*AccountLoginResult, error) { @@ -15410,6 +19431,38 @@ func (a *ServerAgentsAPI) GetDiscoveryPaths(ctx context.Context, params *AgentsG return &result, nil } +// Experimental: ServerCatalogAPI contains experimental APIs that may change or be removed. +type ServerCatalogAPI serverAPI + +// Search requests a bounded catalog search. This host-implemented server method is +// available through SDK/TUI hosts; standalone and C-ABI runtimes whose host does not +// implement server-method dispatch return JSON-RPC MethodNotFound. A runtime with search +// available returns inert candidate summaries, each with an opaque single-use handle scoped +// to this runtime instance; a runtime without it returns the typed search-unavailable +// result. Public authorities may be searched anonymously, while an authority that requires +// credentials yields the typed authentication-required result. All returned text, URLs, and +// package metadata are untrusted external data and can never trigger instructions, tools, +// or installation. Read-only: nothing is installed, configured, or persisted. +// +// RPC method: catalog.search. +// +// Parameters: A bounded catalog search. Both the query length and the result count are +// capped by the schema so a caller cannot request an unbounded scan. +// +// Returns: Outcome of a catalog.search call: either bounded inert candidates, or one typed +// refusal. Never a partial success. +func (a *ServerCatalogAPI) Search(ctx context.Context, params *CatalogSearchRequest) (CatalogSearchResult, error) { + raw, err := a.client.Request(ctx, "catalog.search", params) + if err != nil { + return nil, err + } + result, err := unmarshalCatalogSearchResult(raw) + if err != nil { + return nil, err + } + return result, nil +} + // Experimental: ServerCommandsAPI contains experimental APIs that may change or be removed. type ServerCommandsAPI serverAPI @@ -15494,6 +19547,32 @@ func (a *ServerExtensionsAPI) Enable(ctx context.Context, params *DiscoveredExte return &result, nil } +// Experimental: ServerHooksAPI contains experimental APIs that may change or be removed. +type ServerHooksAPI serverAPI + +// Discovers hook actions enabled under server-side discovery settings from user, +// repository, plugin, and managed-policy sources. +// +// RPC method: hooks.discover. +// +// Parameters: Optional project paths and host-exclusion behavior for server-scoped hook +// discovery. +// +// Returns: Server-discovered hook actions and partial-load diagnostics from user, +// repository, plugin, and managed-policy sources. Concrete sessions may include additional +// session-specific hook sources. +func (a *ServerHooksAPI) Discover(ctx context.Context, params *HooksDiscoverRequest) (*HooksDiscoverResult, error) { + raw, err := a.client.Request(ctx, "hooks.discover", params) + if err != nil { + return nil, err + } + var result HooksDiscoverResult + 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 @@ -15605,6 +19684,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 @@ -15627,6 +19729,36 @@ func (a *ServerMCPAPI) Discover(ctx context.Context, params *MCPDiscoverRequest) return &result, nil } +// PlanInstall requests a side-effect-free MCP install plan from a catalog candidate handle +// or a caller-supplied card. This host-implemented server method is available through +// SDK/TUI hosts; standalone and C-ABI runtimes whose host does not implement server-method +// dispatch return JSON-RPC MethodNotFound. A runtime with planning available returns a +// normalised plan and opaque single-use plan handle; a runtime without it returns the typed +// planning-unavailable result. A completed plan reports resource identity, provenance, +// eligible transport choices, the user-scope target, required typed values and secret +// placeholders, the policy result, the configuration changes installing would make, and +// whether a reload would be needed. Planning never writes configuration, stores a secret, +// or reloads MCP servers, so abandoning a plan needs no call and leaves nothing behind. +// +// RPC method: mcp.planInstall. +// +// Parameters: A side-effect-free request for an MCP install plan. Computing a plan never +// writes configuration, stores a secret, or reloads MCP servers. +// +// Returns: Outcome of an mcp.planInstall call: either a normalised plan, or one typed +// refusal. Nothing is written in either case. +func (a *ServerMCPAPI) PlanInstall(ctx context.Context, params *MCPPlanInstallRequest) (MCPPlanInstallResult, error) { + raw, err := a.client.Request(ctx, "mcp.planInstall", params) + if err != nil { + return nil, err + } + result, err := unmarshalMCPPlanInstallResult(raw) + if err != nil { + return nil, err + } + return result, nil +} + // Experimental: ServerMCPConfigAPI contains experimental APIs that may change or be removed. type ServerMCPConfigAPI serverAPI @@ -15779,8 +19911,8 @@ func (a *ServerModelsAPI) GetBuiltInCatalog(ctx context.Context) (*BuiltInModelC // // RPC method: models.list. // -// Parameters: Optional GitHub token used to list models for a specific user instead of the -// global auth context. +// Parameters: Optional opaque account selection or compatibility GitHub token used to list +// models. // // Returns: List of Copilot models available to the resolved user, including capabilities // and billing metadata. @@ -15922,6 +20054,33 @@ func (a *ServerPluginsAPI) UpdateAll(ctx context.Context) (*PluginUpdateAllResul return &result, nil } +// Experimental: ServerPluginsBuiltinAPI contains experimental APIs that may change or be +// removed. +type ServerPluginsBuiltinAPI serverAPI + +// Set replaces this server's trusted built-in plugin directories while no sessions are +// active. +// +// RPC method: plugins.builtin.set. +// +// Parameters: Trusted built-in plugin directories to use for this runtime process. +func (a *ServerPluginsBuiltinAPI) Set(ctx context.Context, params *PluginsBuiltinSetRequest) (*PluginsBuiltinSetResult, error) { + raw, err := a.client.Request(ctx, "plugins.builtin.set", params) + if err != nil { + return nil, err + } + var result PluginsBuiltinSetResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Experimental: Builtin returns experimental APIs that may change or be removed. +func (s *ServerPluginsAPI) Builtin() *ServerPluginsBuiltinAPI { + return (*ServerPluginsBuiltinAPI)(s) +} + // Experimental: ServerPluginsMarketplacesAPI contains experimental APIs that may change or // be removed. type ServerPluginsMarketplacesAPI serverAPI @@ -16398,6 +20557,31 @@ func (a *ServerSessionsAPI) PruneOld(ctx context.Context, params *SessionsPruneO return &result, nil } +// ReadPersistedEvents reads a page of durable events directly from a local session's +// persisted journal without creating, resuming, or activating the session. The initial +// backward read uses a bounded tail scan for fast first paint; cursor continuations +// preserve the session event-log paging semantics. Persisted events may omit payloads that +// are reconstructed only for an active session. +// +// RPC method: sessions.readPersistedEvents. +// +// Parameters: Pagination options for reading an inactive or active local session's +// persisted event journal. +// +// Returns: Batch of session events returned by a read, with cursor and continuation +// metadata. +func (a *ServerSessionsAPI) ReadPersistedEvents(ctx context.Context, params *SessionsReadPersistedEventsRequest) (*EventsReadResult, error) { + raw, err := a.client.Request(ctx, "sessions.readPersistedEvents", params) + if err != nil { + return nil, err + } + var result EventsReadResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // ReleaseLock releases the in-use lock held by this process for a session. // // RPC method: sessions.releaseLock. @@ -16639,6 +20823,24 @@ func (a *ServerSkillsConfigAPI) SetDisabledSkills(ctx context.Context, params *S return &result, nil } +// SetSkillDisabled atomically adds or removes one skill from the disabled list. +// +// RPC method: skills.config.setSkillDisabled. +// +// Parameters: Adds or removes a single skill from the global disabled list, leaving every +// other entry untouched. +func (a *ServerSkillsConfigAPI) SetSkillDisabled(ctx context.Context, params *SkillsConfigSetSkillDisabledRequest) (*SkillsConfigSetSkillDisabledResult, error) { + raw, err := a.client.Request(ctx, "skills.config.setSkillDisabled", params) + if err != nil { + return nil, err + } + var result SkillsConfigSetSkillDisabledResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // Experimental: Config returns experimental APIs that may change or be removed. func (s *ServerSkillsAPI) Config() *ServerSkillsConfigAPI { return (*ServerSkillsConfigAPI)(s) @@ -16747,23 +20949,26 @@ 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 - Extensions *ServerExtensionsAPI - 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 + Catalog *ServerCatalogAPI + Commands *ServerCommandsAPI + Extensions *ServerExtensionsAPI + Hooks *ServerHooksAPI + 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. @@ -16787,16 +20992,38 @@ 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 +// uses its built-in extension launcher. +// +// 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} r.Account = (*ServerAccountAPI)(&r.common) r.AgentRegistry = (*ServerAgentRegistryAPI)(&r.common) r.Agents = (*ServerAgentsAPI)(&r.common) + r.Catalog = (*ServerCatalogAPI)(&r.common) r.Commands = (*ServerCommandsAPI)(&r.common) r.Extensions = (*ServerExtensionsAPI)(&r.common) + r.Hooks = (*ServerHooksAPI)(&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) @@ -17028,8 +21255,8 @@ type InternalServerRPC struct { // // RPC method: connect. // -// Parameters: Parameters for the `server.connect` handshake: an optional connection token -// and optional connection-level opt-ins (e.g. GitHub telemetry forwarding). +// Parameters: Connection-level opt-ins for the `server.connect` handshake. Transport +// authentication is consumed by the native protocol boundary before dispatch. // // Returns: Handshake result reporting the server's protocol version and package version on // success. @@ -17337,6 +21564,9 @@ func (a *CommandsAPI) Enqueue(ctx context.Context, params *EnqueueCommandParams) req := map[string]any{"sessionId": a.sessionID} if params != nil { req["command"] = params.Command + if params.DisplayText != nil { + req["displayText"] = *params.DisplayText + } } raw, err := a.client.Request(ctx, "session.commands.enqueue", req) if err != nil { @@ -17414,6 +21644,9 @@ func (a *CommandsAPI) Invoke(ctx context.Context, params *CommandsInvokeRequest) req["input"] = *params.Input } req["name"] = params.Name + if params.Origin != nil { + req["origin"] = *params.Origin + } } raw, err := a.client.Request(ctx, "session.commands.invoke", req) if err != nil { @@ -17967,9 +22200,22 @@ func (a *FactoryAPI) GetRunProgress(ctx context.Context, params *FactoryGetRunPr // // RPC method: session.factory.listRuns. // -// Returns: Factory runs in durable creation order. -func (a *FactoryAPI) ListRuns(ctx context.Context) (*FactoryListRunsResult, error) { +// Parameters: Parameters for paging factory runs. +// +// 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 { + 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.listRuns", req) if err != nil { return nil, err @@ -18019,6 +22265,12 @@ func (a *FactoryAPI) Resume(ctx context.Context, params *FactoryResumeRequest) ( if params.Limits != nil { req["limits"] = *params.Limits } + if params.LogPhaseNames != nil { + req["logPhaseNames"] = *params.LogPhaseNames + } + if params.NotifyOnComplete != nil { + req["notifyOnComplete"] = *params.NotifyOnComplete + } req["runId"] = params.RunID } raw, err := a.client.Request(ctx, "session.factory.resume", req) @@ -18679,6 +22931,26 @@ func (a *MCPAPI) ListTools(ctx context.Context, params *MCPListToolsRequest) (*M return &result, nil } +// MoveLoadingToBackground releases any turns waiting on an in-flight MCP load without +// cancelling the load, letting the agent proceed while MCP servers finish connecting in the +// background. No-op when no MCP load is in flight or waiting turns were already released. +// +// RPC method: session.mcp.moveLoadingToBackground. +// +// Returns: Result of moving in-flight MCP loading to the background. +func (a *MCPAPI) MoveLoadingToBackground(ctx context.Context) (*MoveMCPLoadingToBackgroundResult, error) { + req := map[string]any{"sessionId": a.sessionID} + raw, err := a.client.Request(ctx, "session.mcp.moveLoadingToBackground", req) + if err != nil { + return nil, err + } + var result MoveMCPLoadingToBackgroundResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // Reloads MCP server connections for the session. // // RPC method: session.mcp.reload. @@ -19111,6 +23383,35 @@ func (a *MCPOauthAPI) Login(ctx context.Context, params *MCPOauthLoginRequest) ( return &result, nil } +// Probe passively probes a configured remote MCP server to classify whether OAuth is +// required or a cached/override token is accepted. Does not start OAuth, emit pending OAuth +// requests, or mutate MCP connection state. +// +// RPC method: session.mcp.oauth.probe. +// +// Parameters: Remote MCP server name for a passive OAuth status probe. +// +// Returns: Passive MCP OAuth probe result. `authenticated` means the server accepted the +// probe request while an OAuth-origin access token was attached; it does not prove the +// server required or independently validated that token. The probe does not make a second +// unauthenticated request. Failed is an expected probe-domain outcome; JSON-RPC errors are +// reserved for API-call failures. +func (a *MCPOauthAPI) Probe(ctx context.Context, params *MCPOauthProbeRequest) (MCPOauthProbeResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["serverName"] = params.ServerName + } + raw, err := a.client.Request(ctx, "session.mcp.oauth.probe", req) + if err != nil { + return nil, err + } + result, err := unmarshalMCPOauthProbeResult(raw) + if err != nil { + return nil, err + } + return result, nil +} + // Responds to a pending MCP OAuth authorization request by its request id. // // RPC method: session.mcp.oauth.respond. @@ -19490,16 +23791,49 @@ func (a *ModeAPI) Get(ctx context.Context) (*SessionMode, error) { // RPC method: session.mode.set. // // Parameters: Agent interaction mode to apply to the session. -func (a *ModeAPI) Set(ctx context.Context, params *ModeSetRequest) (*SessionModeSetResult, error) { +// +// Returns: Outcome of a session mode change, including any model switch it triggered and +// follow-up the host must perform. +func (a *ModeAPI) Set(ctx context.Context, params *ModeSetRequest) (*ModeSetResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { + if params.CompactionDecision != nil { + req["compactionDecision"] = *params.CompactionDecision + } + if params.InheritPlanBaseFromSessionID != nil { + req["inheritPlanBaseFromSessionId"] = *params.InheritPlanBaseFromSessionID + } req["mode"] = params.Mode + if params.PersistPlanSelection != nil { + req["persistPlanSelection"] = *params.PersistPlanSelection + } + if params.PickerSettingsContext != nil { + req["pickerSettingsContext"] = *params.PickerSettingsContext + } + if params.PlanContextTier != nil { + req["planContextTier"] = *params.PlanContextTier + } + if params.PlanExitAction != nil { + req["planExitAction"] = *params.PlanExitAction + } + if params.PlanModel != nil { + req["planModel"] = *params.PlanModel + } + if params.PlanModelConfigured != nil { + req["planModelConfigured"] = *params.PlanModelConfigured + } + if params.PlanReasoningEffort != nil { + req["planReasoningEffort"] = *params.PlanReasoningEffort + } + if params.RestorePlanModel != nil { + req["restorePlanModel"] = *params.RestorePlanModel + } } raw, err := a.client.Request(ctx, "session.mode.set", req) if err != nil { return nil, err } - var result SessionModeSetResult + var result ModeSetResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } @@ -19597,6 +23931,9 @@ func (a *ModelAPI) SetReasoningEffort(ctx context.Context, params *ModelSetReaso func (a *ModelAPI) SwitchTo(ctx context.Context, params *ModelSwitchToRequest) (*ModelSwitchToResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { + if params.CompactionDecision != nil { + req["compactionDecision"] = *params.CompactionDecision + } if params.ContextTier != nil { req["contextTier"] = *params.ContextTier } @@ -19606,13 +23943,31 @@ func (a *ModelAPI) SwitchTo(ctx context.Context, params *ModelSwitchToRequest) ( if params.ModelCapabilities != nil { req["modelCapabilities"] = *params.ModelCapabilities } + if params.ModelChangeScope != nil { + req["modelChangeScope"] = *params.ModelChangeScope + } req["modelId"] = params.ModelID + if params.PickerPersistence != nil { + req["pickerPersistence"] = *params.PickerPersistence + } if params.ReasoningEffort != nil { req["reasoningEffort"] = *params.ReasoningEffort } if params.ReasoningSummary != nil { req["reasoningSummary"] = *params.ReasoningSummary } + if params.RepoScope != nil { + req["repoScope"] = *params.RepoScope + } + if params.RequireAvailable != nil { + req["requireAvailable"] = *params.RequireAvailable + } + if params.RunCompactionPreflight != nil { + req["runCompactionPreflight"] = *params.RunCompactionPreflight + } + if params.Source != nil { + req["source"] = *params.Source + } if params.Verbosity != nil { req["verbosity"] = *params.Verbosity } @@ -19795,6 +24150,9 @@ func (a *OptionsAPI) Update(ctx context.Context, params *SessionUpdateOptionsPar if params.IncludedBuiltinAgents != nil { req["includedBuiltinAgents"] = params.IncludedBuiltinAgents } + if params.IncludedBuiltinSkills != nil { + req["includedBuiltinSkills"] = params.IncludedBuiltinSkills + } if params.InstalledPlugins != nil { req["installedPlugins"] = params.InstalledPlugins } @@ -19840,6 +24198,9 @@ func (a *OptionsAPI) Update(ctx context.Context, params *SessionUpdateOptionsPar if params.SandboxConfig != nil { req["sandboxConfig"] = *params.SandboxConfig } + if params.SandboxConfigSource != nil { + req["sandboxConfigSource"] = *params.SandboxConfigSource + } if params.SessionCapabilities != nil { req["sessionCapabilities"] = params.SessionCapabilities } @@ -19936,18 +24297,18 @@ func (a *PermissionsAPI) Configure(ctx context.Context, params *PermissionsConfi return &result, nil } -// GetAllowAll returns the current allow-all permission mode for the session. +// GetMode returns the current permission mode for the session. // -// RPC method: session.permissions.getAllowAll. +// RPC method: session.permissions.getMode. // -// Returns: Current allow-all permission mode. -func (a *PermissionsAPI) GetAllowAll(ctx context.Context) (*AllowAllPermissionState, error) { +// Returns: Current permission mode. +func (a *PermissionsAPI) GetMode(ctx context.Context) (*PermissionsGetModeResult, error) { req := map[string]any{"sessionId": a.sessionID} - raw, err := a.client.Request(ctx, "session.permissions.getAllowAll", req) + raw, err := a.client.Request(ctx, "session.permissions.getMode", req) if err != nil { return nil, err } - var result AllowAllPermissionState + var result PermissionsGetModeResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } @@ -19966,6 +24327,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 } @@ -20083,68 +24447,62 @@ func (a *PermissionsAPI) ResetSessionApprovals(ctx context.Context, params *Perm return &result, nil } -// SetAllowAll 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. +// SetApproveAll enables or disables automatic approval of tool permission requests for the +// session. // -// RPC method: session.permissions.setAllowAll. +// RPC method: session.permissions.setApproveAll. // -// Parameters: Allow-all mode to apply for the session. +// Parameters: Allow-all toggle for tool permission requests, with an optional telemetry +// source. // -// Returns: Indicates whether the operation succeeded and reports the post-mutation state. -func (a *PermissionsAPI) SetAllowAll(ctx context.Context, params *PermissionsSetAllowAllRequest) (*AllowAllPermissionSetResult, error) { +// Returns: Indicates whether the operation succeeded. +func (a *PermissionsAPI) SetApproveAll(ctx context.Context, params *PermissionsSetApproveAllRequest) (*PermissionsSetApproveAllResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { - if params.Enabled != nil { - req["enabled"] = *params.Enabled - } - if params.Mode != nil { - req["mode"] = *params.Mode - } - if params.Model != nil { - req["model"] = *params.Model - } + req["enabled"] = params.Enabled if params.Source != nil { req["source"] = *params.Source } } - raw, err := a.client.Request(ctx, "session.permissions.setAllowAll", req) + raw, err := a.client.Request(ctx, "session.permissions.setApproveAll", req) if err != nil { return nil, err } - var result AllowAllPermissionSetResult + var result PermissionsSetApproveAllResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -// SetApproveAll enables or disables automatic approval of tool permission requests for the -// session. +// SetMode sets the permission mode for the session. `manual` follows the normal approval +// flow, `assisted` attaches LLM safety recommendations, and `allow-all` automatically +// approves permission requests. The result returns the authoritative post-mutation mode so +// callers can update local state without racing the `session.permissions_changed` +// notification. // -// RPC method: session.permissions.setApproveAll. +// RPC method: session.permissions.setMode. // -// Parameters: Allow-all toggle for tool permission requests, with an optional telemetry -// source. +// Parameters: Permission mode to apply for the session. // -// Returns: Indicates whether the operation succeeded. -func (a *PermissionsAPI) SetApproveAll(ctx context.Context, params *PermissionsSetApproveAllRequest) (*PermissionsSetApproveAllResult, error) { +// Returns: Indicates whether the requested permission mode was applied and reports the +// authoritative post-mutation mode. +func (a *PermissionsAPI) SetMode(ctx context.Context, params *PermissionsSetModeRequest) (*PermissionsSetModeResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { - req["enabled"] = params.Enabled + if params.AssistedApprovalModel != nil { + req["assistedApprovalModel"] = *params.AssistedApprovalModel + } + req["mode"] = params.Mode if params.Source != nil { req["source"] = *params.Source } } - raw, err := a.client.Request(ctx, "session.permissions.setApproveAll", req) + raw, err := a.client.Request(ctx, "session.permissions.setMode", req) if err != nil { return nil, err } - var result PermissionsSetApproveAllResult + var result PermissionsSetModeResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } @@ -20315,7 +24673,8 @@ func (s *PermissionsAPI) Locations() *PermissionsLocationsAPI { // removed. type PermissionsPathsAPI sessionAPI -// Adds a directory to the session's allow-list. +// Adds a directory to the session's allow-list and activates conventional skill and agent +// definitions under it. // // RPC method: session.permissions.paths.add. // @@ -20995,6 +25354,28 @@ func (a *RemoteAPI) NotifySteerableChanged(ctx context.Context, params *RemoteNo return &result, nil } +// Experimental: SandboxAPI contains experimental APIs that may change or be removed. +type SandboxAPI sessionAPI + +// GetEnforcementStatus returns whether managed policy requires sandbox enforcement and +// whether an enforcement failure has permanently blocked the session. +// +// RPC method: session.sandbox.getEnforcementStatus. +// +// Returns: Managed sandbox enforcement state for a session. +func (a *SandboxAPI) GetEnforcementStatus(ctx context.Context) (*SandboxEnforcementStatus, error) { + req := map[string]any{"sessionId": a.sessionID} + raw, err := a.client.Request(ctx, "session.sandbox.getEnforcementStatus", req) + if err != nil { + return nil, err + } + var result SandboxEnforcementStatus + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // Experimental: ScheduleAPI contains experimental APIs that may change or be removed. type ScheduleAPI sessionAPI @@ -21580,7 +25961,80 @@ func (a *TelemetryAPI) SetFeatureOverrides(ctx context.Context, params *Telemetr // Experimental: ToolsAPI contains experimental APIs that may change or be removed. type ToolsAPI sessionAPI -// GetCurrentMetadata returns lightweight metadata for the session's currently initialized +// Executes one tool from the session's currently offered tool set through the native +// invocation pipeline. +// +// RPC method: session.tools.execute. +// +// Parameters: A tool name and arguments to execute through the session's native invocation +// pipeline. +// +// Returns: Canonical result returned by a session tool. +func (a *ToolsAPI) Execute(ctx context.Context, params *ToolsExecuteRequest) (ToolResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["arguments"] = params.Arguments + req["name"] = params.Name + if params.ToolCallID != nil { + req["toolCallId"] = *params.ToolCallID + } + } + raw, err := a.client.Request(ctx, "session.tools.execute", req) + if err != nil { + return nil, err + } + result, err := unmarshalToolResult(raw) + if err != nil { + return nil, err + } + return result, nil +} + +// GetBuiltinDescriptors returns the Rust-owned built-in tool descriptors used to construct +// the session's offered tool set. +// +// RPC method: session.tools.getBuiltinDescriptors. +// +// Parameters: Options controlling how Rust-owned built-in tool descriptors are materialized. +// +// Returns: Rust-owned built-in tool descriptors for the session. +func (a *ToolsAPI) GetBuiltinDescriptors(ctx context.Context, params *ToolsGetBuiltinDescriptorsRequest) (*ToolsGetBuiltinDescriptorsResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + if params.BackgroundTaskNotificationsEnabled != nil { + req["backgroundTaskNotificationsEnabled"] = *params.BackgroundTaskNotificationsEnabled + } + if params.IncludeAuthor != nil { + req["includeAuthor"] = *params.IncludeAuthor + } + if params.ReduceUserIntervention != nil { + req["reduceUserIntervention"] = *params.ReduceUserIntervention + } + if params.ShellConfig != nil { + req["shellConfig"] = *params.ShellConfig + } + if params.ShellSupportsPowerShell7Syntax != nil { + req["shellSupportsPowerShell7Syntax"] = *params.ShellSupportsPowerShell7Syntax + } + if params.ShellTimeoutMs != nil { + req["shellTimeoutMs"] = *params.ShellTimeoutMs + } + if params.SkillEmbeddingEnabled != nil { + req["skillEmbeddingEnabled"] = *params.SkillEmbeddingEnabled + } + } + raw, err := a.client.Request(ctx, "session.tools.getBuiltinDescriptors", req) + if err != nil { + return nil, err + } + var result ToolsGetBuiltinDescriptorsResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// GetCurrentMetadata returns lightweight metadata for the session's currently initialized // tools. // // RPC method: session.tools.getCurrentMetadata. @@ -21651,6 +26105,59 @@ func (a *ToolsAPI) InitializeAndValidate(ctx context.Context) (*ToolsInitializeA return &result, nil } +// Set atomically replaces the complete externally implemented tool list supplied by the +// calling connection. Built-in, MCP/plugin, extension-discovered, subagent, and tools +// supplied by other connections remain unchanged. +// +// RPC method: session.tools.set. +// +// Parameters: Complete externally implemented tool list for the calling connection. An +// empty list removes every tool previously supplied by that connection. +// +// Returns: Empty result after replacing the calling connection's externally implemented +// tools. +func (a *ToolsAPI) Set(ctx context.Context, params *ToolsSetRequest) (*ToolsSetResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["tools"] = params.Tools + } + raw, err := a.client.Request(ctx, "session.tools.set", req) + if err != nil { + return nil, err + } + var result ToolsSetResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// TaskCompleteEventData projects a completed task_complete tool call into its label-safe +// session event payload. +// +// RPC method: session.tools.taskCompleteEventData. +// +// Parameters: Task-completion tool arguments and final result used to build a label-safe +// session event payload. +// +// Returns: Task completion notification with summary from the agent +func (a *ToolsAPI) TaskCompleteEventData(ctx context.Context, params *ToolsTaskCompleteEventDataRequest) (*TaskCompleteData, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["finalResult"] = params.FinalResult + req["toolArgs"] = params.ToolArgs + } + raw, err := a.client.Request(ctx, "session.tools.taskCompleteEventData", req) + if err != nil { + return nil, err + } + var result TaskCompleteData + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // UpdateSubagentSettings updates the current session's live subagent settings after user // settings change. The persisted user settings remain the source of truth for future // sessions. @@ -21693,7 +26200,16 @@ func (a *UIAPI) Elicitation(ctx context.Context, params *UIElicitationRequest) ( req := map[string]any{"sessionId": a.sessionID} if params != nil { req["message"] = params.Message + if params.Meta != nil { + req["_meta"] = params.Meta + } + if params.Mode != nil { + req["mode"] = *params.Mode + } req["requestedSchema"] = params.RequestedSchema + if params.Task != nil { + req["task"] = *params.Task + } } raw, err := a.client.Request(ctx, "session.ui.elicitation", req) if err != nil { @@ -21713,7 +26229,8 @@ func (a *UIAPI) Elicitation(ctx context.Context, params *UIElicitationRequest) ( // // Parameters: Transient question to answer without adding it to conversation history. // -// Returns: Transient answer generated from current conversation context. +// Returns: Completed transient query. Ordered chunks and the terminal outcome are also +// delivered through `ui.ephemeral_query` session events while it runs. func (a *UIAPI) EphemeralQuery(ctx context.Context, params *UIEphemeralQueryRequest) (*UIEphemeralQueryResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { @@ -22408,6 +26925,7 @@ type SessionRPC struct { Provider *ProviderAPI Queue *QueueAPI Remote *RemoteAPI + Sandbox *SandboxAPI Schedule *ScheduleAPI Shell *ShellAPI Skills *SkillsAPI @@ -22532,221 +27050,568 @@ func (a *SessionRPC) Log(ctx context.Context, params *LogRequest) (*LogResult, e if err != nil { return nil, err } - var result LogResult + var result LogResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Sends a user message to the session and returns its message ID. +// +// RPC method: session.send. +// +// Parameters: Parameters for sending a user message to the session +// +// Returns: Result of sending a user message +// Experimental: Send is an experimental API and may change or be removed in future versions. +func (a *SessionRPC) Send(ctx context.Context, params *SendRequest) (*SendResult, error) { + req := map[string]any{"sessionId": a.common.sessionID} + if params != nil { + if params.AgentMode != nil { + req["agentMode"] = *params.AgentMode + } + if params.Attachments != nil { + req["attachments"] = params.Attachments + } + if params.Billable != nil { + req["billable"] = *params.Billable + } + if params.DisplayPrompt != nil { + req["displayPrompt"] = *params.DisplayPrompt + } + if params.Mode != nil { + req["mode"] = *params.Mode + } + if params.Prepend != nil { + req["prepend"] = *params.Prepend + } + req["prompt"] = params.Prompt + if params.RequestHeaders != nil { + req["requestHeaders"] = params.RequestHeaders + } + if params.RequiredTool != nil { + req["requiredTool"] = *params.RequiredTool + } + if params.Source != nil { + req["source"] = *params.Source + } + if params.Traceparent != nil { + req["traceparent"] = *params.Traceparent + } + if params.Tracestate != nil { + req["tracestate"] = *params.Tracestate + } + if params.Wait != nil { + req["wait"] = *params.Wait + } + } + raw, err := a.common.client.Request(ctx, "session.send", req) + if err != nil { + return nil, err + } + var result SendResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// SendMessages sends zero or more user messages to the session in a single turn and returns +// their message IDs. All provided messages are appended to the conversation in order, then +// exactly one agent turn runs over the resulting history. When the list is empty, one turn +// runs over the existing history with no new user message. Remote-backed (Mission Control) +// sessions do not support this method and will return an error. +// +// RPC method: session.sendMessages. +// +// Parameters: 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. +// +// Returns: Result of sending zero or more user messages +// Experimental: SendMessages is an experimental API and may change or be removed in future +// versions. +func (a *SessionRPC) SendMessages(ctx context.Context, params *SendMessagesRequest) (*SendMessagesResult, error) { + req := map[string]any{"sessionId": a.common.sessionID} + if params != nil { + if params.AgentMode != nil { + req["agentMode"] = *params.AgentMode + } + req["messages"] = params.Messages + if params.Mode != nil { + req["mode"] = *params.Mode + } + if params.Prepend != nil { + req["prepend"] = *params.Prepend + } + if params.RequestHeaders != nil { + req["requestHeaders"] = params.RequestHeaders + } + if params.Traceparent != nil { + req["traceparent"] = *params.Traceparent + } + if params.Tracestate != nil { + req["tracestate"] = *params.Tracestate + } + if params.Wait != nil { + req["wait"] = *params.Wait + } + } + raw, err := a.common.client.Request(ctx, "session.sendMessages", req) + if err != nil { + return nil, err + } + var result SendMessagesResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Shutdown 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. +// +// RPC method: session.shutdown. +// +// Parameters: Parameters for shutting down the session +// Experimental: Shutdown is an experimental API and may change or be removed in future +// versions. +func (a *SessionRPC) Shutdown(ctx context.Context, params *ShutdownRequest) (*SessionShutdownResult, error) { + req := map[string]any{"sessionId": a.common.sessionID} + if params != nil { + if params.Reason != nil { + req["reason"] = *params.Reason + } + if params.Type != nil { + req["type"] = *params.Type + } + } + raw, err := a.common.client.Request(ctx, "session.shutdown", req) + if err != nil { + return nil, err + } + var result SessionShutdownResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Suspends the session while preserving persisted state for later resume. +// +// RPC method: session.suspend. +// Experimental: Suspend is an experimental API and may change or be removed in future +// versions. +func (a *SessionRPC) Suspend(ctx context.Context) (*SessionSuspendResult, error) { + req := map[string]any{"sessionId": a.common.sessionID} + raw, err := a.common.client.Request(ctx, "session.suspend", req) + if err != nil { + return nil, err + } + var result SessionSuspendResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +func NewSessionRPC(client *jsonrpc2.Client, sessionID string) *SessionRPC { + r := &SessionRPC{} + r.common = sessionAPI{client: client, sessionID: sessionID} + r.Agent = (*AgentAPI)(&r.common) + 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) + r.Factory = (*FactoryAPI)(&r.common) + r.Fleet = (*FleetAPI)(&r.common) + 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) + r.Mode = (*ModeAPI)(&r.common) + r.Model = (*ModelAPI)(&r.common) + r.Name = (*NameAPI)(&r.common) + r.Options = (*OptionsAPI)(&r.common) + r.Permissions = (*PermissionsAPI)(&r.common) + r.Plan = (*PlanAPI)(&r.common) + r.Plugins = (*PluginsAPI)(&r.common) + r.Provider = (*ProviderAPI)(&r.common) + r.Queue = (*QueueAPI)(&r.common) + r.Remote = (*RemoteAPI)(&r.common) + r.Sandbox = (*SandboxAPI)(&r.common) + r.Schedule = (*ScheduleAPI)(&r.common) + r.Shell = (*ShellAPI)(&r.common) + r.Skills = (*SkillsAPI)(&r.common) + r.Tasks = (*TasksAPI)(&r.common) + r.Telemetry = (*TelemetryAPI)(&r.common) + r.Tools = (*ToolsAPI)(&r.common) + r.UI = (*UIAPI)(&r.common) + r.Usage = (*UsageAPI)(&r.common) + r.Visibility = (*VisibilityAPI)(&r.common) + r.Workspaces = (*WorkspacesAPI)(&r.common) + return r +} + +type internalSessionAPI struct { + client *jsonrpc2.Client + sessionID string +} + +// Experimental: InternalCanvasAPI contains experimental APIs that may change or be removed. +type InternalCanvasAPI internalSessionAPI + +// Experimental: InternalCanvasProviderAPI contains experimental APIs that may change or be +// removed. +type InternalCanvasProviderAPI internalSessionAPI + +// Registers an internal canvas provider connection and its contributions. +// +// RPC method: session.canvas.provider.register. +// +// Parameters: Internal canvas provider registration parameters. +// Internal: Register is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalCanvasProviderAPI) Register(ctx context.Context, params *CanvasProviderRegisterRequest) (*SessionCanvasProviderRegisterResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["canvases"] = params.Canvases + req["connectionId"] = params.ConnectionID + req["info"] = params.Info + } + raw, err := a.client.Request(ctx, "session.canvas.provider.register", req) + if err != nil { + return nil, err + } + var result SessionCanvasProviderRegisterResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Unregisters an internal canvas provider connection. +// +// RPC method: session.canvas.provider.unregister. +// +// Parameters: Internal canvas provider unregistration parameters. +// Internal: Unregister is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalCanvasProviderAPI) Unregister(ctx context.Context, params *CanvasProviderUnregisterRequest) (*SessionCanvasProviderUnregisterResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["connectionId"] = params.ConnectionID + } + raw, err := a.client.Request(ctx, "session.canvas.provider.unregister", req) + if err != nil { + return nil, err + } + var result SessionCanvasProviderUnregisterResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Experimental: Provider returns experimental APIs that may change or be removed. +func (s *InternalCanvasAPI) Provider() *InternalCanvasProviderAPI { + return (*InternalCanvasProviderAPI)(s) +} + +// Experimental: InternalCommandsAPI contains experimental APIs that may change or be +// removed. +type InternalCommandsAPI internalSessionAPI + +// FinalizeInvocationEffect finalizes persistence associated with a client-applied +// slash-command effect. +// +// RPC method: session.commands.finalizeInvocationEffect. +// +// Parameters: The pending slash-command invocation effect to finalize, plus whether the +// host applied or cancelled it. +// +// Returns: Whether finalizing the invocation effect succeeded, and the failure reason when +// it did not. +// Internal: FinalizeInvocationEffect is part of the SDK's internal handshake/plumbing; +// external callers should not use it. +func (a *InternalCommandsAPI) FinalizeInvocationEffect(ctx context.Context, params *CommandsFinalizeInvocationEffectRequest) (*CommandsFinalizeInvocationEffectResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["effect"] = params.Effect + req["outcome"] = params.Outcome + } + raw, err := a.client.Request(ctx, "session.commands.finalizeInvocationEffect", req) + if err != nil { + return nil, err + } + var result CommandsFinalizeInvocationEffectResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Experimental: InternalFactoryAPI contains experimental APIs that may change or be removed. +type InternalFactoryAPI internalSessionAPI + +// ResumeFromTool internal tool-originated factory resume. +// +// RPC method: session.factory.resumeFromTool. +// +// Parameters: Internal parameters for resuming a factory run from a tool. +// +// Returns: Resolved persisted factory identity and resumed run envelope. +// Internal: ResumeFromTool is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalFactoryAPI) ResumeFromTool(ctx context.Context, params *FactoryToolResumeRequest) (*FactoryResumeResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + if params.Limits != nil { + req["limits"] = *params.Limits + } + req["runId"] = params.RunID + if params.ToolCallID != nil { + req["toolCallId"] = *params.ToolCallID + } + } + raw, err := a.client.Request(ctx, "session.factory.resumeFromTool", req) + if err != nil { + return nil, err + } + var result FactoryResumeResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// RunFromTool internal tool-originated factory invocation. +// +// RPC method: session.factory.runFromTool. +// +// Parameters: Internal parameters for invoking a registered factory from a tool. +// +// Returns: Complete current or terminal factory run envelope. +// Internal: RunFromTool is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalFactoryAPI) RunFromTool(ctx context.Context, params *FactoryToolRunRequest) (*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 + } + if params.ToolCallID != nil { + req["toolCallId"] = *params.ToolCallID + } + } + raw, err := a.client.Request(ctx, "session.factory.runFromTool", 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: InternalGitHubAuthAPI contains experimental APIs that may change or be +// removed. +type InternalGitHubAuthAPI internalSessionAPI + +// GetAllAuthAvailable gets all authentication accounts available to the internal session +// host. +// +// RPC method: session.gitHubAuth.getAllAuthAvailable. +// +// Returns: Authentication accounts available to the internal session host. +// Internal: GetAllAuthAvailable is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalGitHubAuthAPI) GetAllAuthAvailable(ctx context.Context) (*SessionGitHubAuthGetAllAuthAvailableResult, error) { + req := map[string]any{"sessionId": a.sessionID} + raw, err := a.client.Request(ctx, "session.gitHubAuth.getAllAuthAvailable", req) + if err != nil { + return nil, err + } + var result SessionGitHubAuthGetAllAuthAvailableResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// GetCurrentAuthInfo gets the current authentication information for internal session hosts. +// +// RPC method: session.gitHubAuth.getCurrentAuthInfo. +// +// Returns: Current authentication information, or null when no authentication is active. +// Internal: GetCurrentAuthInfo is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalGitHubAuthAPI) GetCurrentAuthInfo(ctx context.Context) (*AuthIdentity, error) { + req := map[string]any{"sessionId": a.sessionID} + raw, err := a.client.Request(ctx, "session.gitHubAuth.getCurrentAuthInfo", req) + if err != nil { + return nil, err + } + var result AuthIdentity + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// LastAuthErrors gets validation errors from the most recent authentication attempt. +// +// RPC method: session.gitHubAuth.lastAuthErrors. +// +// Returns: Validation errors from the most recent authentication attempt. +// Internal: LastAuthErrors is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalGitHubAuthAPI) LastAuthErrors(ctx context.Context) (*AuthValidationErrors, error) { + req := map[string]any{"sessionId": a.sessionID} + raw, err := a.client.Request(ctx, "session.gitHubAuth.lastAuthErrors", req) + if err != nil { + return nil, err + } + var result AuthValidationErrors if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -// Sends a user message to the session and returns its message ID. +// Login logs in a GitHub user through the internal session host. // -// RPC method: session.send. +// RPC method: session.gitHubAuth.login. // -// Parameters: Parameters for sending a user message to the session +// Parameters: Internal GitHub login parameters. // -// Returns: Result of sending a user message -// Experimental: Send is an experimental API and may change or be removed in future versions. -func (a *SessionRPC) Send(ctx context.Context, params *SendRequest) (*SendResult, error) { - req := map[string]any{"sessionId": a.common.sessionID} +// Returns: Authentication credentials accepted only at native protocol ingress. Runtime +// outputs use credential-free `AuthIdentity` metadata. +// Internal: Login is part of the SDK's internal handshake/plumbing; external callers should +// not use it. +func (a *InternalGitHubAuthAPI) Login(ctx context.Context, params *SessionAuthLoginRequest) (AuthInfo, error) { + req := map[string]any{"sessionId": a.sessionID} if params != nil { - if params.AgentMode != nil { - req["agentMode"] = *params.AgentMode - } - if params.Attachments != nil { - req["attachments"] = params.Attachments - } - if params.Billable != nil { - req["billable"] = *params.Billable - } - if params.DisplayPrompt != nil { - req["displayPrompt"] = *params.DisplayPrompt - } - if params.Mode != nil { - req["mode"] = *params.Mode - } - if params.Prepend != nil { - req["prepend"] = *params.Prepend - } - req["prompt"] = params.Prompt - if params.RequestHeaders != nil { - req["requestHeaders"] = params.RequestHeaders - } - if params.RequiredTool != nil { - req["requiredTool"] = *params.RequiredTool - } - if params.Source != nil { - req["source"] = *params.Source - } - if params.Traceparent != nil { - req["traceparent"] = *params.Traceparent - } - if params.Tracestate != nil { - req["tracestate"] = *params.Tracestate - } - if params.Wait != nil { - req["wait"] = *params.Wait + req["host"] = params.Host + req["login"] = params.Login + if params.Persist != nil { + req["persist"] = *params.Persist } + req["token"] = params.Token } - raw, err := a.common.client.Request(ctx, "session.send", req) + raw, err := a.client.Request(ctx, "session.gitHubAuth.login", req) if err != nil { return nil, err } - var result SendResult + result, err := unmarshalAuthInfo(raw) + if err != nil { + return nil, err + } + return result, nil +} + +// Logout logs out the session's current GitHub authentication. +// +// RPC method: session.gitHubAuth.logout. +// +// Returns: Whether the current authentication was logged out. +// Internal: Logout is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalGitHubAuthAPI) Logout(ctx context.Context) (*SessionGitHubAuthLogoutResult, error) { + req := map[string]any{"sessionId": a.sessionID} + raw, err := a.client.Request(ctx, "session.gitHubAuth.logout", req) + if err != nil { + return nil, err + } + var result SessionGitHubAuthLogoutResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -// SendMessages sends zero or more user messages to the session in a single turn and returns -// their message IDs. All provided messages are appended to the conversation in order, then -// exactly one agent turn runs over the resulting history. When the list is empty, one turn -// runs over the existing history with no new user message. Remote-backed (Mission Control) -// sessions do not support this method and will return an error. +// LogoutUser logs out a specific GitHub authentication. // -// RPC method: session.sendMessages. +// RPC method: session.gitHubAuth.logoutUser. // -// Parameters: 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: Parameters identifying a GitHub authentication to log out. // -// Returns: Result of sending zero or more user messages -// Experimental: SendMessages is an experimental API and may change or be removed in future -// versions. -func (a *SessionRPC) SendMessages(ctx context.Context, params *SendMessagesRequest) (*SendMessagesResult, error) { - req := map[string]any{"sessionId": a.common.sessionID} +// Returns: Whether the requested authentication was logged out. +// Internal: LogoutUser is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalGitHubAuthAPI) LogoutUser(ctx context.Context, params *SessionAuthLogoutUserRequest) (*SessionGitHubAuthLogoutUserResult, error) { + req := map[string]any{"sessionId": a.sessionID} if params != nil { - if params.AgentMode != nil { - req["agentMode"] = *params.AgentMode - } - req["messages"] = params.Messages - if params.Mode != nil { - req["mode"] = *params.Mode - } - if params.Prepend != nil { - req["prepend"] = *params.Prepend - } - if params.RequestHeaders != nil { - req["requestHeaders"] = params.RequestHeaders - } - if params.Traceparent != nil { - req["traceparent"] = *params.Traceparent - } - if params.Tracestate != nil { - req["tracestate"] = *params.Tracestate - } - if params.Wait != nil { - req["wait"] = *params.Wait - } + req["authInfo"] = params.AuthInfo } - raw, err := a.common.client.Request(ctx, "session.sendMessages", req) + raw, err := a.client.Request(ctx, "session.gitHubAuth.logoutUser", req) if err != nil { return nil, err } - var result SendMessagesResult + var result SessionGitHubAuthLogoutUserResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -// Shutdown 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. +// RefreshCopilotUser refreshes Copilot account metadata for the current authentication. // -// RPC method: session.shutdown. +// RPC method: session.gitHubAuth.refreshCopilotUser. // -// Parameters: Parameters for shutting down the session -// Experimental: Shutdown is an experimental API and may change or be removed in future -// versions. -func (a *SessionRPC) Shutdown(ctx context.Context, params *ShutdownRequest) (*SessionShutdownResult, error) { - req := map[string]any{"sessionId": a.common.sessionID} - if params != nil { - if params.Reason != nil { - req["reason"] = *params.Reason - } - if params.Type != nil { - req["type"] = *params.Type - } - } - raw, err := a.common.client.Request(ctx, "session.shutdown", req) +// Returns: Current authentication information, or null when no authentication is active. +// Internal: RefreshCopilotUser is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalGitHubAuthAPI) RefreshCopilotUser(ctx context.Context) (*AuthIdentity, error) { + req := map[string]any{"sessionId": a.sessionID} + raw, err := a.client.Request(ctx, "session.gitHubAuth.refreshCopilotUser", req) if err != nil { return nil, err } - var result SessionShutdownResult + var result AuthIdentity if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -// Suspends the session while preserving persisted state for later resume. +// SwitchToAuth switches the session to another available authentication. // -// RPC method: session.suspend. -// Experimental: Suspend is an experimental API and may change or be removed in future -// versions. -func (a *SessionRPC) Suspend(ctx context.Context) (*SessionSuspendResult, error) { - req := map[string]any{"sessionId": a.common.sessionID} - raw, err := a.common.client.Request(ctx, "session.suspend", req) +// RPC method: session.gitHubAuth.switchToAuth. +// +// Parameters: Parameters for switching the session's active authentication. +// Internal: SwitchToAuth is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalGitHubAuthAPI) SwitchToAuth(ctx context.Context, params *SessionAuthSwitchRequest) (*SessionGitHubAuthSwitchToAuthResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["authInfo"] = params.AuthInfo + if params.Token != nil { + req["token"] = *params.Token + } + } + raw, err := a.client.Request(ctx, "session.gitHubAuth.switchToAuth", req) if err != nil { return nil, err } - var result SessionSuspendResult + var result SessionGitHubAuthSwitchToAuthResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -func NewSessionRPC(client *jsonrpc2.Client, sessionID string) *SessionRPC { - r := &SessionRPC{} - r.common = sessionAPI{client: client, sessionID: sessionID} - r.Agent = (*AgentAPI)(&r.common) - 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) - r.Factory = (*FactoryAPI)(&r.common) - r.Fleet = (*FleetAPI)(&r.common) - 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) - r.Mode = (*ModeAPI)(&r.common) - r.Model = (*ModelAPI)(&r.common) - r.Name = (*NameAPI)(&r.common) - r.Options = (*OptionsAPI)(&r.common) - r.Permissions = (*PermissionsAPI)(&r.common) - r.Plan = (*PlanAPI)(&r.common) - r.Plugins = (*PluginsAPI)(&r.common) - r.Provider = (*ProviderAPI)(&r.common) - r.Queue = (*QueueAPI)(&r.common) - r.Remote = (*RemoteAPI)(&r.common) - r.Schedule = (*ScheduleAPI)(&r.common) - r.Shell = (*ShellAPI)(&r.common) - r.Skills = (*SkillsAPI)(&r.common) - r.Tasks = (*TasksAPI)(&r.common) - r.Telemetry = (*TelemetryAPI)(&r.common) - r.Tools = (*ToolsAPI)(&r.common) - r.UI = (*UIAPI)(&r.common) - r.Usage = (*UsageAPI)(&r.common) - r.Visibility = (*VisibilityAPI)(&r.common) - r.Workspaces = (*WorkspacesAPI)(&r.common) - return r -} - -type internalSessionAPI struct { - client *jsonrpc2.Client - sessionID string -} - // Experimental: InternalMCPAPI contains experimental APIs that may change or be removed. type InternalMCPAPI internalSessionAPI @@ -22755,7 +27620,7 @@ type InternalMCPAPI internalSessionAPI // // RPC method: session.mcp.configureGitHub. // -// Parameters: Opaque auth info used to configure GitHub MCP. +// Parameters: Credential-free authentication identity used to configure GitHub MCP. // // Returns: Result of configuring GitHub MCP. // Internal: ConfigureGitHub is part of the SDK's internal handshake/plumbing; external @@ -22861,6 +27726,59 @@ func (a *InternalMCPAPI) UnregisterExternalClient(ctx context.Context, params *M return &result, nil } +// Experimental: InternalModelAPI contains experimental APIs that may change or be removed. +type InternalModelAPI internalSessionAPI + +// ApplyStartupOverlay resolves and applies organization-managed and repository model +// overlays. +// +// RPC method: session.model.applyStartupOverlay. +// +// Parameters: Managed, repository, and CLI model overrides to overlay onto the session at +// startup. +// +// Returns: The model identifier active on the session after the switch. +// Internal: ApplyStartupOverlay is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalModelAPI) ApplyStartupOverlay(ctx context.Context, params *ModelApplyStartupOverlayRequest) (*ModelSwitchToResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + if params.CLIModel != nil { + req["cliModel"] = *params.CLIModel + } + if params.DeferredResume != nil { + req["deferredResume"] = *params.DeferredResume + } + if params.DeviceManagedModel != nil { + req["deviceManagedModel"] = *params.DeviceManagedModel + } + if params.PolicyHelperModel != nil { + req["policyHelperModel"] = *params.PolicyHelperModel + } + if params.RepoContextTier != nil { + req["repoContextTier"] = *params.RepoContextTier + } + if params.RepoModel != nil { + req["repoModel"] = *params.RepoModel + } + if params.RepoReasoningEffort != nil { + req["repoReasoningEffort"] = *params.RepoReasoningEffort + } + if params.ServerManagedModel != nil { + req["serverManagedModel"] = *params.ServerManagedModel + } + } + raw, err := a.client.Request(ctx, "session.model.applyStartupOverlay", req) + if err != nil { + return nil, err + } + var result ModelSwitchToResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // Experimental: InternalQueueAPI contains experimental APIs that may change or be removed. type InternalQueueAPI internalSessionAPI @@ -23306,10 +28224,15 @@ type InternalSessionRPC struct { // Reuse a single struct instead of allocating one for each service on the heap. common internalSessionAPI - MCP *InternalMCPAPI - Queue *InternalQueueAPI - Schedule *InternalScheduleAPI - Settings *InternalSettingsAPI + Canvas *InternalCanvasAPI + Commands *InternalCommandsAPI + Factory *InternalFactoryAPI + GitHubAuth *InternalGitHubAuthAPI + MCP *InternalMCPAPI + Model *InternalModelAPI + Queue *InternalQueueAPI + Schedule *InternalScheduleAPI + Settings *InternalSettingsAPI } // SendSystemNotification queues or sends an internal system notification to the session @@ -23347,7 +28270,12 @@ func (a *InternalSessionRPC) SendSystemNotification(ctx context.Context, params func NewInternalSessionRPC(client *jsonrpc2.Client, sessionID string) *InternalSessionRPC { r := &InternalSessionRPC{} r.common = internalSessionAPI{client: client, sessionID: sessionID} + r.Canvas = (*InternalCanvasAPI)(&r.common) + r.Commands = (*InternalCommandsAPI)(&r.common) + r.Factory = (*InternalFactoryAPI)(&r.common) + r.GitHubAuth = (*InternalGitHubAuthAPI)(&r.common) r.MCP = (*InternalMCPAPI)(&r.common) + r.Model = (*InternalModelAPI)(&r.common) r.Queue = (*InternalQueueAPI)(&r.common) r.Schedule = (*InternalScheduleAPI)(&r.common) r.Settings = (*InternalSettingsAPI)(&r.common) @@ -23936,6 +28864,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 { @@ -23952,6 +28897,21 @@ type GitHubTelemetryHandler interface { Event(request *GitHubTelemetryNotification) error } +// Experimental: GitHubTokenHandler contains experimental APIs that may change or be removed. +type GitHubTokenHandler interface { + // GetToken asks the SDK client to mint a GitHub access token for a session whose + // configuration supplied a GitHub token provider. The runtime acquires the initial token + // during bootstrap and refreshes it during expiry preflight when one hour or less remains. + // + // RPC method: gitHubToken.getToken. + // + // Parameters: Asks the SDK client to acquire a GitHub access token from an opaque callback + // registration. + // + // Returns: SDK host response to a GitHub credential request. + GetToken(request *GitHubTokenAcquireRequest) (GitHubTokenAcquireResult, error) +} + // Experimental: HooksHandler contains experimental APIs that may change or be removed. type HooksHandler interface { // Invoke dispatches one SDK callback hook from the runtime to the connection that @@ -24002,9 +28962,11 @@ 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 + GitHubToken GitHubTokenHandler + Hooks HooksHandler + LlmInference LlmInferenceHandler } func clientGlobalHandlerError(err error) *jsonrpc2.Error { @@ -24021,6 +28983,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 { @@ -24034,6 +29014,24 @@ func RegisterClientGlobalAPIHandlers(client *jsonrpc2.Client, handlers *ClientGl } return nil, nil }) + client.SetRequestHandler("gitHubToken.getToken", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { + var request GitHubTokenAcquireRequest + 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.GitHubToken == nil { + return nil, &jsonrpc2.Error{Code: -32603, Message: "No gitHubToken client-global handler registered"} + } + result, err := handlers.GitHubToken.GetToken(&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("hooks.invoke", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { var request HookInvokeRequest if err := json.Unmarshal(params, &request); err != nil { diff --git a/go/rpc/zrpc_encoding.go b/go/rpc/zrpc_encoding.go index fdc76729b5..9242a889a8 100644 --- a/go/rpc/zrpc_encoding.go +++ b/go/rpc/zrpc_encoding.go @@ -57,6 +57,12 @@ func unmarshalAuthInfo(data []byte) (AuthInfo, error) { return nil, err } return &d, nil + case AuthInfoTypeTokenProvider: + var d TokenProviderAuthInfo + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil case AuthInfoTypeUser: var d UserAuthInfo if err := json.Unmarshal(data, &d); err != nil { @@ -145,6 +151,17 @@ func (r TokenAuthInfo) MarshalJSON() ([]byte, error) { }) } +func (r TokenProviderAuthInfo) MarshalJSON() ([]byte, error) { + type alias TokenProviderAuthInfo + return json.Marshal(struct { + Type AuthInfoType `json:"type"` + alias + }{ + Type: r.Type(), + alias: alias(r), + }) +} + func (r UserAuthInfo) MarshalJSON() ([]byte, error) { type alias UserAuthInfo return json.Marshal(struct { @@ -158,8 +175,9 @@ func (r UserAuthInfo) MarshalJSON() ([]byte, error) { func (r *AccountAllUsers) UnmarshalJSON(data []byte) error { type rawAccountAllUsers struct { - AuthInfo json.RawMessage `json:"authInfo"` - Token *string `json:"token,omitempty"` + AuthInfo json.RawMessage `json:"authInfo"` + SelectionID *string `json:"selectionId,omitempty"` + Token *string `json:"token,omitempty"` } var raw rawAccountAllUsers if err := json.Unmarshal(data, &raw); err != nil { @@ -172,6 +190,7 @@ func (r *AccountAllUsers) UnmarshalJSON(data []byte) error { } r.AuthInfo = value } + r.SelectionID = raw.SelectionID r.Token = raw.Token return nil } @@ -198,7 +217,8 @@ func (r *AccountGetCurrentAuthResult) UnmarshalJSON(data []byte) error { func (r *AccountLogoutRequest) UnmarshalJSON(data []byte) error { type rawAccountLogoutRequest struct { - AuthInfo json.RawMessage `json:"authInfo"` + AuthInfo json.RawMessage `json:"authInfo,omitempty"` + SelectionID *string `json:"selectionId,omitempty"` } var raw rawAccountLogoutRequest if err := json.Unmarshal(data, &raw); err != nil { @@ -211,6 +231,7 @@ func (r *AccountLogoutRequest) UnmarshalJSON(data []byte) error { } r.AuthInfo = value } + r.SelectionID = raw.SelectionID return nil } @@ -595,86 +616,108 @@ func (r AttachmentSelection) MarshalJSON() ([]byte, error) { }) } -func unmarshalQueuedCommandResult(data []byte) (QueuedCommandResult, error) { +func unmarshalBuiltinToolSafeForTelemetry(data []byte) (BuiltinToolSafeForTelemetry, error) { + if string(data) == "null" { + return nil, nil + } + { + var value bool + if err := json.Unmarshal(data, &value); err == nil { + return BuiltinToolSafeForTelemetryBoolean(value), nil + } + } + { + var value BuiltinToolSafeTelemetryFields + if err := json.Unmarshal(data, &value); err == nil { + return &value, nil + } + } + return nil, errors.New("data did not match any union variant for BuiltinToolSafeForTelemetry") +} + +func (r *BuiltinToolDescriptor) UnmarshalJSON(data []byte) error { + type rawBuiltinToolDescriptor struct { + Description string `json:"description"` + Format *BuiltinToolFormat `json:"format"` + HasSummariseIntention bool `json:"hasSummariseIntention"` + InputSchema *BuiltinToolInputSchema `json:"inputSchema"` + Instructions *string `json:"instructions"` + IsTerminal bool `json:"isTerminal"` + Name string `json:"name"` + SafeForTelemetry json.RawMessage `json:"safeForTelemetry"` + Title *string `json:"title"` + Type *string `json:"type"` + } + var raw rawBuiltinToolDescriptor + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + r.Description = raw.Description + r.Format = raw.Format + r.HasSummariseIntention = raw.HasSummariseIntention + r.InputSchema = raw.InputSchema + r.Instructions = raw.Instructions + r.IsTerminal = raw.IsTerminal + r.Name = raw.Name + if raw.SafeForTelemetry != nil { + value, err := unmarshalBuiltinToolSafeForTelemetry(raw.SafeForTelemetry) + if err != nil { + return err + } + r.SafeForTelemetry = value + } + r.Title = raw.Title + r.Type = raw.Type + return nil +} + +func unmarshalCatalogCandidate(data []byte) (CatalogCandidate, error) { if string(data) == "null" { return nil, nil } type rawUnion struct { - Handled *bool `json:"handled"` + Kind CatalogCandidateKind `json:"kind"` } var raw rawUnion if err := json.Unmarshal(data, &raw); err != nil { return nil, err } - if raw.Handled == nil { - return nil, errors.New("data did not match any union variant for QueuedCommandResult") - } - switch *raw.Handled { - case false: - var d QueuedCommandNotHandled + switch raw.Kind { + case CatalogCandidateKindAiSkill: + var d CatalogAiSkillCandidate if err := json.Unmarshal(data, &d); err != nil { return nil, err } return &d, nil - case true: - var d QueuedCommandHandled + case CatalogCandidateKindMCPServer: + var d CatalogMCPServerCandidate if err := json.Unmarshal(data, &d); err != nil { return nil, err } return &d, nil + default: + return &RawCatalogCandidateData{Discriminator: raw.Kind, Raw: data}, nil } - return nil, errors.New("data did not match any union variant for QueuedCommandResult") -} - -func (r QueuedCommandHandled) MarshalJSON() ([]byte, error) { - type alias QueuedCommandHandled - return json.Marshal(struct { - Handled bool `json:"handled"` - alias - }{ - Handled: r.Handled(), - alias: alias(r), - }) } -func (r QueuedCommandNotHandled) MarshalJSON() ([]byte, error) { - type alias QueuedCommandNotHandled +func (r RawCatalogCandidateData) MarshalJSON() ([]byte, error) { + if r.Raw != nil { + return r.Raw, nil + } return json.Marshal(struct { - Handled bool `json:"handled"` - alias + Kind CatalogCandidateKind `json:"kind"` }{ - Handled: r.Handled(), - alias: alias(r), + Kind: r.Discriminator, }) } -func (r *CommandsRespondToQueuedCommandRequest) UnmarshalJSON(data []byte) error { - type rawCommandsRespondToQueuedCommandRequest struct { - RequestID string `json:"requestId"` - Result json.RawMessage `json:"result"` - } - var raw rawCommandsRespondToQueuedCommandRequest - if err := json.Unmarshal(data, &raw); err != nil { - return err - } - r.RequestID = raw.RequestID - if raw.Result != nil { - value, err := unmarshalQueuedCommandResult(raw.Result) - if err != nil { - return err - } - r.Result = value - } - return nil -} - -func unmarshalDebugCollectLogsDestination(data []byte) (DebugCollectLogsDestination, error) { +func unmarshalCatalogCandidateSource(data []byte) (CatalogCandidateSource, error) { if string(data) == "null" { return nil, nil } type rawUnion struct { - Kind DebugCollectLogsDestinationKind `json:"kind"` + Kind CatalogCandidateSourceKind `json:"kind"` } var raw rawUnion if err := json.Unmarshal(data, &raw); err != nil { @@ -682,38 +725,38 @@ func unmarshalDebugCollectLogsDestination(data []byte) (DebugCollectLogsDestinat } switch raw.Kind { - case DebugCollectLogsDestinationKindArchive: - var d DebugCollectLogsDestinationArchive + case CatalogCandidateSourceKindEmbedded: + var d CatalogCandidateSourceEmbedded if err := json.Unmarshal(data, &d); err != nil { return nil, err } return &d, nil - case DebugCollectLogsDestinationKindDirectory: - var d DebugCollectLogsDestinationDirectory + case CatalogCandidateSourceKindURL: + var d CatalogCandidateSourceURL if err := json.Unmarshal(data, &d); err != nil { return nil, err } return &d, nil default: - return &RawDebugCollectLogsDestinationData{Discriminator: raw.Kind, Raw: data}, nil + return &RawCatalogCandidateSourceData{Discriminator: raw.Kind, Raw: data}, nil } } -func (r RawDebugCollectLogsDestinationData) MarshalJSON() ([]byte, error) { +func (r RawCatalogCandidateSourceData) MarshalJSON() ([]byte, error) { if r.Raw != nil { return r.Raw, nil } return json.Marshal(struct { - Kind DebugCollectLogsDestinationKind `json:"kind"` + Kind CatalogCandidateSourceKind `json:"kind"` }{ Kind: r.Discriminator, }) } -func (r DebugCollectLogsDestinationArchive) MarshalJSON() ([]byte, error) { - type alias DebugCollectLogsDestinationArchive +func (r CatalogCandidateSourceEmbedded) MarshalJSON() ([]byte, error) { + type alias CatalogCandidateSourceEmbedded return json.Marshal(struct { - Kind DebugCollectLogsDestinationKind `json:"kind"` + Kind CatalogCandidateSourceKind `json:"kind"` alias }{ Kind: r.Kind(), @@ -721,10 +764,10 @@ func (r DebugCollectLogsDestinationArchive) MarshalJSON() ([]byte, error) { }) } -func (r DebugCollectLogsDestinationDirectory) MarshalJSON() ([]byte, error) { - type alias DebugCollectLogsDestinationDirectory +func (r CatalogCandidateSourceURL) MarshalJSON() ([]byte, error) { + type alias CatalogCandidateSourceURL return json.Marshal(struct { - Kind DebugCollectLogsDestinationKind `json:"kind"` + Kind CatalogCandidateSourceKind `json:"kind"` alias }{ Kind: r.Kind(), @@ -732,668 +775,1364 @@ func (r DebugCollectLogsDestinationDirectory) MarshalJSON() ([]byte, error) { }) } -func (r *DebugCollectLogsRequest) UnmarshalJSON(data []byte) error { - type rawDebugCollectLogsRequest struct { - AdditionalEntries []DebugCollectLogsEntry `json:"additionalEntries,omitzero"` - Destination json.RawMessage `json:"destination"` - Include *DebugCollectLogsInclude `json:"include,omitempty"` +func (r *CatalogAiSkillCandidate) UnmarshalJSON(data []byte) error { + type rawCatalogAiSkillCandidate struct { + Description *string `json:"description,omitempty"` + DisplayName string `json:"displayName"` + Handle string `json:"handle"` + HandleExpiresAt string `json:"handleExpiresAt"` + Installability CatalogAiSkillCandidateInstallability `json:"installability"` + MediaType CatalogAiSkillCandidateMediaType `json:"mediaType"` + Provenance CatalogAiSkillCandidateProvenance `json:"provenance"` + Publisher *string `json:"publisher,omitempty"` + Source json.RawMessage `json:"source"` } - var raw rawDebugCollectLogsRequest + var raw rawCatalogAiSkillCandidate if err := json.Unmarshal(data, &raw); err != nil { return err } - r.AdditionalEntries = raw.AdditionalEntries - if raw.Destination != nil { - value, err := unmarshalDebugCollectLogsDestination(raw.Destination) + r.Description = raw.Description + r.DisplayName = raw.DisplayName + r.Handle = raw.Handle + r.HandleExpiresAt = raw.HandleExpiresAt + r.Installability = raw.Installability + r.MediaType = raw.MediaType + r.Provenance = raw.Provenance + r.Publisher = raw.Publisher + if raw.Source != nil { + value, err := unmarshalCatalogCandidateSource(raw.Source) if err != nil { return err } - r.Destination = value + r.Source = value } - r.Include = raw.Include return nil } -func (r EventLogTypes) MarshalJSON() ([]byte, error) { - if r.String != nil { - return json.Marshal(r.String) - } - if r.StringArray != nil { - return json.Marshal(r.StringArray) - } - return []byte("null"), nil +func (r CatalogAiSkillCandidate) MarshalJSON() ([]byte, error) { + type alias CatalogAiSkillCandidate + return json.Marshal(struct { + Kind CatalogCandidateKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) } -func (r *EventLogTypes) UnmarshalJSON(data []byte) error { - if string(data) == "null" { - *r = EventLogTypes{} - return nil +func (r *CatalogMCPServerCandidate) UnmarshalJSON(data []byte) error { + type rawCatalogMCPServerCandidate struct { + Description *string `json:"description,omitempty"` + DisplayName string `json:"displayName"` + Handle string `json:"handle"` + HandleExpiresAt string `json:"handleExpiresAt"` + Installability CatalogMCPServerInstallability `json:"installability"` + MediaType MCPServerCardMediaType `json:"mediaType"` + Provenance CatalogMCPServerCandidateProvenance `json:"provenance"` + Publisher *string `json:"publisher,omitempty"` + Source json.RawMessage `json:"source"` } - { - var value EventLogTypesString - if err := json.Unmarshal(data, &value); err == nil { - *r = EventLogTypes{String: &value} - return nil - } + var raw rawCatalogMCPServerCandidate + if err := json.Unmarshal(data, &raw); err != nil { + return err } - { - var value []string - if err := json.Unmarshal(data, &value); err == nil { - *r = EventLogTypes{StringArray: value} - return nil + r.Description = raw.Description + r.DisplayName = raw.DisplayName + r.Handle = raw.Handle + r.HandleExpiresAt = raw.HandleExpiresAt + r.Installability = raw.Installability + r.MediaType = raw.MediaType + r.Provenance = raw.Provenance + r.Publisher = raw.Publisher + if raw.Source != nil { + value, err := unmarshalCatalogCandidateSource(raw.Source) + if err != nil { + return err } + r.Source = value } - return errors.New("data did not match any union variant for EventLogTypes") + return nil } -func unmarshalExternalToolTextResultForLlmContent(data []byte) (ExternalToolTextResultForLlmContent, error) { +func (r CatalogMCPServerCandidate) MarshalJSON() ([]byte, error) { + type alias CatalogMCPServerCandidate + return json.Marshal(struct { + Kind CatalogCandidateKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + +func unmarshalCatalogSearchResult(data []byte) (CatalogSearchResult, error) { if string(data) == "null" { return nil, nil } type rawUnion struct { - Type ExternalToolTextResultForLlmContentType `json:"type"` + Kind CatalogSearchResultKind `json:"kind"` } var raw rawUnion if err := json.Unmarshal(data, &raw); err != nil { return nil, err } - switch raw.Type { - case ExternalToolTextResultForLlmContentTypeAudio: - var d ExternalToolTextResultForLlmContentAudio + switch raw.Kind { + case CatalogSearchResultKindAuthenticationRequired: + var d CatalogAuthenticationRequiredError if err := json.Unmarshal(data, &d); err != nil { return nil, err } return &d, nil - case ExternalToolTextResultForLlmContentTypeImage: - var d ExternalToolTextResultForLlmContentImage + case CatalogSearchResultKindContractViolation: + var d CatalogContractViolationError if err := json.Unmarshal(data, &d); err != nil { return nil, err } return &d, nil - case ExternalToolTextResultForLlmContentTypeResource: - var d ExternalToolTextResultForLlmContentResource + case CatalogSearchResultKindInvalidRequest: + var d CatalogInvalidRequestError if err := json.Unmarshal(data, &d); err != nil { return nil, err } return &d, nil - case ExternalToolTextResultForLlmContentTypeResourceLink: - var d ExternalToolTextResultForLlmContentResourceLink + case CatalogSearchResultKindMalformedCard: + var d CatalogMalformedCardError if err := json.Unmarshal(data, &d); err != nil { return nil, err } return &d, nil - case ExternalToolTextResultForLlmContentTypeShellExit: - var d ExternalToolTextResultForLlmContentShellExit + case CatalogSearchResultKindNegotiationRefused: + var d CatalogNegotiationRefusedError if err := json.Unmarshal(data, &d); err != nil { return nil, err } return &d, nil - case ExternalToolTextResultForLlmContentTypeTerminal: - var d ExternalToolTextResultForLlmContentTerminal + case CatalogSearchResultKindNetworkFailure: + var d CatalogNetworkFailureError if err := json.Unmarshal(data, &d); err != nil { return nil, err } return &d, nil - case ExternalToolTextResultForLlmContentTypeText: - var d ExternalToolTextResultForLlmContentText + case CatalogSearchResultKindPolicyRejected: + var d CatalogPolicyRejectedError + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case CatalogSearchResultKindSucceeded: + var d CatalogSearchSucceeded + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case CatalogSearchResultKindUnavailable: + var d CatalogUnavailableError + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case CatalogSearchResultKindUnsafeRetrieval: + var d CatalogUnsafeRetrievalError + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case CatalogSearchResultKindUnsupportedKind: + var d CatalogUnsupportedKindError if err := json.Unmarshal(data, &d); err != nil { return nil, err } return &d, nil default: - return &RawExternalToolTextResultForLlmContentData{Discriminator: raw.Type, Raw: data}, nil + return &RawCatalogSearchResultData{Discriminator: raw.Kind, Raw: data}, nil } } -func (r RawExternalToolTextResultForLlmContentData) MarshalJSON() ([]byte, error) { +func (r RawCatalogSearchResultData) MarshalJSON() ([]byte, error) { if r.Raw != nil { return r.Raw, nil } return json.Marshal(struct { - Type ExternalToolTextResultForLlmContentType `json:"type"` + Kind CatalogSearchResultKind `json:"kind"` }{ - Type: r.Discriminator, + Kind: r.Discriminator, }) } -func (r ExternalToolTextResultForLlmContentAudio) MarshalJSON() ([]byte, error) { - type alias ExternalToolTextResultForLlmContentAudio +func (r CatalogAuthenticationRequiredError) MarshalJSON() ([]byte, error) { + type alias CatalogAuthenticationRequiredError return json.Marshal(struct { - Type ExternalToolTextResultForLlmContentType `json:"type"` + Kind CatalogSearchResultKind `json:"kind"` alias }{ - Type: r.Type(), + Kind: r.Kind(), alias: alias(r), }) } -func (r ExternalToolTextResultForLlmContentImage) MarshalJSON() ([]byte, error) { - type alias ExternalToolTextResultForLlmContentImage +func (r CatalogContractViolationError) MarshalJSON() ([]byte, error) { + type alias CatalogContractViolationError return json.Marshal(struct { - Type ExternalToolTextResultForLlmContentType `json:"type"` + Kind CatalogSearchResultKind `json:"kind"` alias }{ - Type: r.Type(), + Kind: r.Kind(), alias: alias(r), }) } -func matchesEmbeddedBlobResourceContents(data []byte) bool { - var rawGroup0 struct { - Blob json.RawMessage `json:"blob"` - Text json.RawMessage `json:"text"` - } - if err := json.Unmarshal(data, &rawGroup0); err != nil { - return false - } - if rawGroup0.Blob == nil { - return false - } - return rawGroup0.Text == nil +func (r CatalogInvalidRequestError) MarshalJSON() ([]byte, error) { + type alias CatalogInvalidRequestError + return json.Marshal(struct { + Kind CatalogSearchResultKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) } -func matchesEmbeddedTextResourceContents(data []byte) bool { - var rawGroup0 struct { - Blob json.RawMessage `json:"blob"` - Text json.RawMessage `json:"text"` - } - if err := json.Unmarshal(data, &rawGroup0); err != nil { - return false - } - if rawGroup0.Text == nil { - return false - } - return rawGroup0.Blob == nil +func (r CatalogMalformedCardError) MarshalJSON() ([]byte, error) { + type alias CatalogMalformedCardError + return json.Marshal(struct { + Kind CatalogSearchResultKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) } -func unmarshalExternalToolTextResultForLlmContentResourceDetails(data []byte) (ExternalToolTextResultForLlmContentResourceDetails, error) { - if string(data) == "null" { - return nil, nil - } - if matchesEmbeddedBlobResourceContents(data) { - var d EmbeddedBlobResourceContents - if err := json.Unmarshal(data, &d); err != nil { - return nil, err - } - return &d, nil - } - if matchesEmbeddedTextResourceContents(data) { - var d EmbeddedTextResourceContents - if err := json.Unmarshal(data, &d); err != nil { - return nil, err - } - return &d, nil - } - return &RawExternalToolTextResultForLlmContentResourceDetailsData{Raw: data}, nil +func (r CatalogNegotiationRefusedError) MarshalJSON() ([]byte, error) { + type alias CatalogNegotiationRefusedError + return json.Marshal(struct { + Kind CatalogSearchResultKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) } -func (r RawExternalToolTextResultForLlmContentResourceDetailsData) MarshalJSON() ([]byte, error) { - if r.Raw != nil { - return r.Raw, nil - } - return []byte("null"), nil +func (r CatalogNetworkFailureError) MarshalJSON() ([]byte, error) { + type alias CatalogNetworkFailureError + return json.Marshal(struct { + Kind CatalogSearchResultKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) } -func (r *ExternalToolTextResultForLlmContentResource) UnmarshalJSON(data []byte) error { - type rawExternalToolTextResultForLlmContentResource struct { - Resource json.RawMessage `json:"resource"` +func (r CatalogPolicyRejectedError) MarshalJSON() ([]byte, error) { + type alias CatalogPolicyRejectedError + return json.Marshal(struct { + Kind CatalogSearchResultKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + +func (r *CatalogSearchSucceeded) UnmarshalJSON(data []byte) error { + type rawCatalogSearchSucceeded struct { + Candidates []json.RawMessage `json:"candidates"` + Negotiated CatalogNegotiatedContract `json:"negotiated"` + SearchID string `json:"searchId"` + Truncated bool `json:"truncated"` } - var raw rawExternalToolTextResultForLlmContentResource + var raw rawCatalogSearchSucceeded if err := json.Unmarshal(data, &raw); err != nil { return err } - if raw.Resource != nil { - value, err := unmarshalExternalToolTextResultForLlmContentResourceDetails(raw.Resource) - if err != nil { - return err + if raw.Candidates != nil { + r.Candidates = make([]CatalogCandidate, 0, len(raw.Candidates)) + for _, rawItem := range raw.Candidates { + value, err := unmarshalCatalogCandidate(rawItem) + if err != nil { + return err + } + r.Candidates = append(r.Candidates, value) } - r.Resource = value } + r.Negotiated = raw.Negotiated + r.SearchID = raw.SearchID + r.Truncated = raw.Truncated return nil } -func (r ExternalToolTextResultForLlmContentResource) MarshalJSON() ([]byte, error) { - type alias ExternalToolTextResultForLlmContentResource +func (r CatalogSearchSucceeded) MarshalJSON() ([]byte, error) { + type alias CatalogSearchSucceeded return json.Marshal(struct { - Type ExternalToolTextResultForLlmContentType `json:"type"` + Kind CatalogSearchResultKind `json:"kind"` alias }{ - Type: r.Type(), + Kind: r.Kind(), alias: alias(r), }) } -func (r ExternalToolTextResultForLlmContentResourceLink) MarshalJSON() ([]byte, error) { - type alias ExternalToolTextResultForLlmContentResourceLink +func (r CatalogUnavailableError) MarshalJSON() ([]byte, error) { + type alias CatalogUnavailableError return json.Marshal(struct { - Type ExternalToolTextResultForLlmContentType `json:"type"` + Kind CatalogSearchResultKind `json:"kind"` alias }{ - Type: r.Type(), + Kind: r.Kind(), alias: alias(r), }) } -func (r ExternalToolTextResultForLlmContentShellExit) MarshalJSON() ([]byte, error) { - type alias ExternalToolTextResultForLlmContentShellExit +func (r CatalogUnsafeRetrievalError) MarshalJSON() ([]byte, error) { + type alias CatalogUnsafeRetrievalError return json.Marshal(struct { - Type ExternalToolTextResultForLlmContentType `json:"type"` + Kind CatalogSearchResultKind `json:"kind"` alias }{ - Type: r.Type(), + Kind: r.Kind(), alias: alias(r), }) } -func (r ExternalToolTextResultForLlmContentTerminal) MarshalJSON() ([]byte, error) { - type alias ExternalToolTextResultForLlmContentTerminal +func (r CatalogUnsupportedKindError) MarshalJSON() ([]byte, error) { + type alias CatalogUnsupportedKindError return json.Marshal(struct { - Type ExternalToolTextResultForLlmContentType `json:"type"` + Kind CatalogSearchResultKind `json:"kind"` alias }{ - Type: r.Type(), + Kind: r.Kind(), alias: alias(r), }) } -func (r ExternalToolTextResultForLlmContentText) MarshalJSON() ([]byte, error) { - type alias ExternalToolTextResultForLlmContentText +func unmarshalQueuedCommandResult(data []byte) (QueuedCommandResult, error) { + if string(data) == "null" { + return nil, nil + } + type rawUnion struct { + Handled *bool `json:"handled"` + } + var raw rawUnion + if err := json.Unmarshal(data, &raw); err != nil { + return nil, err + } + if raw.Handled == nil { + return nil, errors.New("data did not match any union variant for QueuedCommandResult") + } + + switch *raw.Handled { + case false: + var d QueuedCommandNotHandled + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case true: + var d QueuedCommandHandled + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + } + return nil, errors.New("data did not match any union variant for QueuedCommandResult") +} + +func (r QueuedCommandHandled) MarshalJSON() ([]byte, error) { + type alias QueuedCommandHandled return json.Marshal(struct { - Type ExternalToolTextResultForLlmContentType `json:"type"` + Handled bool `json:"handled"` alias }{ - Type: r.Type(), - alias: alias(r), + Handled: r.Handled(), + alias: alias(r), }) } -func (r *ExternalToolTextResultForLlm) UnmarshalJSON(data []byte) error { - type rawExternalToolTextResultForLlm struct { - BinaryResultsForLlm []ExternalToolTextResultForLlmBinaryResultsForLlm `json:"binaryResultsForLlm,omitzero"` - Contents []json.RawMessage `json:"contents,omitzero"` - Error *string `json:"error,omitempty"` - ResultType *string `json:"resultType,omitempty"` - SessionLog *string `json:"sessionLog,omitempty"` - TextResultForLlm string `json:"textResultForLlm"` - ToolReferences []string `json:"toolReferences,omitzero"` - ToolTelemetry map[string]any `json:"toolTelemetry,omitzero"` +func (r QueuedCommandNotHandled) MarshalJSON() ([]byte, error) { + type alias QueuedCommandNotHandled + return json.Marshal(struct { + Handled bool `json:"handled"` + alias + }{ + Handled: r.Handled(), + alias: alias(r), + }) +} + +func (r *CommandsRespondToQueuedCommandRequest) UnmarshalJSON(data []byte) error { + type rawCommandsRespondToQueuedCommandRequest struct { + RequestID string `json:"requestId"` + Result json.RawMessage `json:"result"` } - var raw rawExternalToolTextResultForLlm + var raw rawCommandsRespondToQueuedCommandRequest if err := json.Unmarshal(data, &raw); err != nil { return err } - r.BinaryResultsForLlm = raw.BinaryResultsForLlm - if raw.Contents != nil { - r.Contents = make([]ExternalToolTextResultForLlmContent, 0, len(raw.Contents)) - for _, rawItem := range raw.Contents { - value, err := unmarshalExternalToolTextResultForLlmContent(rawItem) - if err != nil { - return err - } - r.Contents = append(r.Contents, value) + r.RequestID = raw.RequestID + if raw.Result != nil { + value, err := unmarshalQueuedCommandResult(raw.Result) + if err != nil { + return err } + r.Result = value } - r.Error = raw.Error - r.ResultType = raw.ResultType - r.SessionLog = raw.SessionLog - r.TextResultForLlm = raw.TextResultForLlm - r.ToolReferences = raw.ToolReferences - r.ToolTelemetry = raw.ToolTelemetry return nil } -func unmarshalExternalToolResult(data []byte) (ExternalToolResult, error) { - if string(data) == "null" { - return nil, nil - } - { - var value string - if err := json.Unmarshal(data, &value); err == nil { - return ExternalToolStringResult(value), nil - } - } - { - var value ExternalToolTextResultForLlm - if err := json.Unmarshal(data, &value); err == nil { - return &value, nil - } - } - return nil, errors.New("data did not match any union variant for ExternalToolResult") -} - -func unmarshalFactoryRunFailure(data []byte) (FactoryRunFailure, error) { +func unmarshalDebugCollectLogsDestination(data []byte) (DebugCollectLogsDestination, error) { if string(data) == "null" { return nil, nil } type rawUnion struct { - Type FactoryRunFailureType `json:"type"` + Kind DebugCollectLogsDestinationKind `json:"kind"` } var raw rawUnion if err := json.Unmarshal(data, &raw); err != nil { return nil, err } - switch raw.Type { - case FactoryRunFailureTypeFactoryDurableFailure: - var d FactoryRunFailureFactoryDurableFailure - if err := json.Unmarshal(data, &d); err != nil { - return nil, err - } - return &d, nil - case FactoryRunFailureTypeFactoryLimitReached: - var d FactoryRunFailureFactoryLimitReached + switch raw.Kind { + case DebugCollectLogsDestinationKindArchive: + var d DebugCollectLogsDestinationArchive if err := json.Unmarshal(data, &d); err != nil { return nil, err } return &d, nil - case FactoryRunFailureTypeFactoryResumeDeclined: - var d FactoryRunFailureFactoryResumeDeclined + case DebugCollectLogsDestinationKindDirectory: + var d DebugCollectLogsDestinationDirectory if err := json.Unmarshal(data, &d); err != nil { return nil, err } return &d, nil default: - return &RawFactoryRunFailureData{Discriminator: raw.Type, Raw: data}, nil + return &RawDebugCollectLogsDestinationData{Discriminator: raw.Kind, Raw: data}, nil } } -func (r RawFactoryRunFailureData) MarshalJSON() ([]byte, error) { +func (r RawDebugCollectLogsDestinationData) MarshalJSON() ([]byte, error) { if r.Raw != nil { return r.Raw, nil } return json.Marshal(struct { - Type FactoryRunFailureType `json:"type"` - }{ - Type: r.Discriminator, - }) -} - -func (r FactoryRunFailureFactoryDurableFailure) MarshalJSON() ([]byte, error) { - type alias FactoryRunFailureFactoryDurableFailure - return json.Marshal(struct { - Type FactoryRunFailureType `json:"type"` - alias + Kind DebugCollectLogsDestinationKind `json:"kind"` }{ - Type: r.Type(), - alias: alias(r), + Kind: r.Discriminator, }) } -func (r FactoryRunFailureFactoryLimitReached) MarshalJSON() ([]byte, error) { - type alias FactoryRunFailureFactoryLimitReached +func (r DebugCollectLogsDestinationArchive) MarshalJSON() ([]byte, error) { + type alias DebugCollectLogsDestinationArchive return json.Marshal(struct { - Type FactoryRunFailureType `json:"type"` + Kind DebugCollectLogsDestinationKind `json:"kind"` alias }{ - Type: r.Type(), + Kind: r.Kind(), alias: alias(r), }) } -func (r FactoryRunFailureFactoryResumeDeclined) MarshalJSON() ([]byte, error) { - type alias FactoryRunFailureFactoryResumeDeclined +func (r DebugCollectLogsDestinationDirectory) MarshalJSON() ([]byte, error) { + type alias DebugCollectLogsDestinationDirectory return json.Marshal(struct { - Type FactoryRunFailureType `json:"type"` + Kind DebugCollectLogsDestinationKind `json:"kind"` alias }{ - Type: r.Type(), + Kind: r.Kind(), alias: alias(r), }) } -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"` +func (r *DebugCollectLogsRequest) UnmarshalJSON(data []byte) error { + type rawDebugCollectLogsRequest struct { + AdditionalEntries []DebugCollectLogsEntry `json:"additionalEntries,omitzero"` + Destination json.RawMessage `json:"destination"` + Include *DebugCollectLogsInclude `json:"include,omitempty"` } - var raw rawFactoryRunTerminal + var raw rawDebugCollectLogsRequest if err := json.Unmarshal(data, &raw); err != nil { return err } - r.Error = raw.Error - if raw.Failure != nil { - value, err := unmarshalFactoryRunFailure(raw.Failure) + r.AdditionalEntries = raw.AdditionalEntries + if raw.Destination != nil { + value, err := unmarshalDebugCollectLogsDestination(raw.Destination) if err != nil { return err } - r.Failure = value + r.Destination = value } - r.Reason = raw.Reason - r.ResultPreview = raw.ResultPreview + r.Include = raw.Include return nil } -func (r *FactoryRunResult) UnmarshalJSON(data []byte) error { - type rawFactoryRunResult struct { - Error *string `json:"error,omitempty"` - Failure json.RawMessage `json:"failure,omitempty"` - Reason *string `json:"reason,omitempty"` - Result any `json:"result,omitempty"` - RunID string `json:"runId"` - Snapshot any `json:"snapshot,omitempty"` - Status FactoryRunStatus `json:"status"` - } - var raw rawFactoryRunResult - if err := json.Unmarshal(data, &raw); err != nil { - return err +func (r EventLogTypes) MarshalJSON() ([]byte, error) { + if r.String != nil { + return json.Marshal(r.String) } - r.Error = raw.Error - if raw.Failure != nil { - value, err := unmarshalFactoryRunFailure(raw.Failure) - if err != nil { - return err - } - r.Failure = value + if r.StringArray != nil { + return json.Marshal(r.StringArray) } - r.Reason = raw.Reason - r.Result = raw.Result - r.RunID = raw.RunID - r.Snapshot = raw.Snapshot - r.Status = raw.Status - return nil + return []byte("null"), nil } -func unmarshalFilterMapping(data []byte) (FilterMapping, error) { +func (r *EventLogTypes) UnmarshalJSON(data []byte) error { if string(data) == "null" { - return nil, nil + *r = EventLogTypes{} + return nil } { - var value FilterMappingEnumMap + var value EventLogTypesString if err := json.Unmarshal(data, &value); err == nil { - return value, nil + *r = EventLogTypes{String: &value} + return nil } } { - var value ContentFilterMode + var value []string if err := json.Unmarshal(data, &value); err == nil { - return value, nil + *r = EventLogTypes{StringArray: value} + return nil } } - return nil, errors.New("data did not match any union variant for FilterMapping") + return errors.New("data did not match any union variant for EventLogTypes") } -func (r *HandlePendingToolCallRequest) UnmarshalJSON(data []byte) error { - type rawHandlePendingToolCallRequest struct { - Error *string `json:"error,omitempty"` - RequestID string `json:"requestId"` - Result json.RawMessage `json:"result,omitempty"` +func unmarshalExternalToolTextResultForLlmContent(data []byte) (ExternalToolTextResultForLlmContent, error) { + if string(data) == "null" { + return nil, nil } - var raw rawHandlePendingToolCallRequest + type rawUnion struct { + Type ExternalToolTextResultForLlmContentType `json:"type"` + } + var raw rawUnion if err := json.Unmarshal(data, &raw); err != nil { - return err + return nil, err } - r.Error = raw.Error - r.RequestID = raw.RequestID - if raw.Result != nil { - value, err := unmarshalExternalToolResult(raw.Result) - if err != nil { - return err + + switch raw.Type { + case ExternalToolTextResultForLlmContentTypeAudio: + var d ExternalToolTextResultForLlmContentAudio + if err := json.Unmarshal(data, &d); err != nil { + return nil, err } - r.Result = value + return &d, nil + case ExternalToolTextResultForLlmContentTypeImage: + var d ExternalToolTextResultForLlmContentImage + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case ExternalToolTextResultForLlmContentTypeResource: + var d ExternalToolTextResultForLlmContentResource + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case ExternalToolTextResultForLlmContentTypeResourceLink: + var d ExternalToolTextResultForLlmContentResourceLink + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case ExternalToolTextResultForLlmContentTypeShellExit: + var d ExternalToolTextResultForLlmContentShellExit + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case ExternalToolTextResultForLlmContentTypeTerminal: + var d ExternalToolTextResultForLlmContentTerminal + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case ExternalToolTextResultForLlmContentTypeText: + var d ExternalToolTextResultForLlmContentText + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + default: + return &RawExternalToolTextResultForLlmContentData{Discriminator: raw.Type, Raw: data}, nil } - return nil } -func (r InstalledPluginSource) MarshalJSON() ([]byte, error) { - if r.InstalledPluginSourceGitHub != nil { - return json.Marshal(r.InstalledPluginSourceGitHub) - } - if r.InstalledPluginSourceLocal != nil { - return json.Marshal(r.InstalledPluginSourceLocal) - } - if r.InstalledPluginSourceURL != nil { - return json.Marshal(r.InstalledPluginSourceURL) - } - if r.String != nil { - return json.Marshal(r.String) +func (r RawExternalToolTextResultForLlmContentData) MarshalJSON() ([]byte, error) { + if r.Raw != nil { + return r.Raw, nil } - return []byte("null"), nil + return json.Marshal(struct { + Type ExternalToolTextResultForLlmContentType `json:"type"` + }{ + Type: r.Discriminator, + }) } -func (r *InstalledPluginSource) UnmarshalJSON(data []byte) error { - if string(data) == "null" { - *r = InstalledPluginSource{} - return nil - } - { - var value InstalledPluginSourceGitHub - if err := json.Unmarshal(data, &value); err == nil { - *r = InstalledPluginSource{InstalledPluginSourceGitHub: &value} - return nil - } - } - { - var value InstalledPluginSourceLocal - if err := json.Unmarshal(data, &value); err == nil { - *r = InstalledPluginSource{InstalledPluginSourceLocal: &value} - return nil - } - } - { - var value InstalledPluginSourceURL - if err := json.Unmarshal(data, &value); err == nil { - *r = InstalledPluginSource{InstalledPluginSourceURL: &value} - return nil - } - } - { - var value string - if err := json.Unmarshal(data, &value); err == nil { - *r = InstalledPluginSource{String: &value} - return nil - } - } - return errors.New("data did not match any union variant for InstalledPluginSource") +func (r ExternalToolTextResultForLlmContentAudio) MarshalJSON() ([]byte, error) { + type alias ExternalToolTextResultForLlmContentAudio + return json.Marshal(struct { + Type ExternalToolTextResultForLlmContentType `json:"type"` + alias + }{ + Type: r.Type(), + alias: alias(r), + }) } -func matchesMCPServerConfigHTTP(data []byte) bool { +func (r ExternalToolTextResultForLlmContentImage) MarshalJSON() ([]byte, error) { + type alias ExternalToolTextResultForLlmContentImage + return json.Marshal(struct { + Type ExternalToolTextResultForLlmContentType `json:"type"` + alias + }{ + Type: r.Type(), + alias: alias(r), + }) +} + +func matchesExternalToolTextResultForLlmContentResourceDetailsEmbeddedBlobResourceContents(data []byte) bool { var rawGroup0 struct { - Command json.RawMessage `json:"command"` - URL json.RawMessage `json:"url"` + Blob json.RawMessage `json:"blob"` + Text json.RawMessage `json:"text"` } if err := json.Unmarshal(data, &rawGroup0); err != nil { return false } - if rawGroup0.URL == nil { + if rawGroup0.Blob == nil { return false } - return rawGroup0.Command == nil + return rawGroup0.Text == nil } -func matchesMCPServerConfigStdio(data []byte) bool { +func matchesExternalToolTextResultForLlmContentResourceDetailsEmbeddedTextResourceContents(data []byte) bool { var rawGroup0 struct { - Command json.RawMessage `json:"command"` - URL json.RawMessage `json:"url"` + Blob json.RawMessage `json:"blob"` + Text json.RawMessage `json:"text"` } if err := json.Unmarshal(data, &rawGroup0); err != nil { return false } - if rawGroup0.Command == nil { + if rawGroup0.Text == nil { return false } - return rawGroup0.URL == nil + return rawGroup0.Blob == nil } -func unmarshalMCPServerConfig(data []byte) (MCPServerConfig, error) { +func unmarshalExternalToolTextResultForLlmContentResourceDetails(data []byte) (ExternalToolTextResultForLlmContentResourceDetails, error) { if string(data) == "null" { return nil, nil } - if matchesMCPServerConfigHTTP(data) { - var d MCPServerConfigHTTP + if matchesExternalToolTextResultForLlmContentResourceDetailsEmbeddedBlobResourceContents(data) { + var d EmbeddedBlobResourceContents if err := json.Unmarshal(data, &d); err != nil { return nil, err } return &d, nil } - if matchesMCPServerConfigStdio(data) { - var d MCPServerConfigStdio + if matchesExternalToolTextResultForLlmContentResourceDetailsEmbeddedTextResourceContents(data) { + var d EmbeddedTextResourceContents if err := json.Unmarshal(data, &d); err != nil { return nil, err } return &d, nil } - return &RawMCPServerConfigData{Raw: data}, nil + return &RawExternalToolTextResultForLlmContentResourceDetailsData{Raw: data}, nil } -func (r RawMCPServerConfigData) MarshalJSON() ([]byte, error) { +func (r RawExternalToolTextResultForLlmContentResourceDetailsData) MarshalJSON() ([]byte, error) { if r.Raw != nil { return r.Raw, nil } return []byte("null"), nil } -func unmarshalMCPServerAuthConfig(data []byte) (MCPServerAuthConfig, error) { - if string(data) == "null" { - return nil, nil - } - { - var value bool - if err := json.Unmarshal(data, &value); err == nil { - return MCPServerAuthConfigBoolean(value), nil - } - } - { - var value MCPServerAuthConfigRedirectPort - if err := json.Unmarshal(data, &value); err == nil { - return &value, nil - } - } - return nil, errors.New("data did not match any union variant for MCPServerAuthConfig") -} - -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"` - OauthClientID *string `json:"oauthClientId,omitempty"` - OauthGrantType *MCPServerConfigHTTPOauthGrantType `json:"oauthGrantType,omitempty"` - OauthPublicClient *bool `json:"oauthPublicClient,omitempty"` - Oidc json.RawMessage `json:"oidc,omitempty"` - Timeout *int64 `json:"timeout,omitempty"` - Tools []string `json:"tools,omitzero"` - Type *MCPServerConfigHTTPType `json:"type,omitempty"` - URL string `json:"url"` +func (r *ExternalToolTextResultForLlmContentResource) UnmarshalJSON(data []byte) error { + type rawExternalToolTextResultForLlmContentResource struct { + Resource json.RawMessage `json:"resource"` } - var raw rawMCPServerConfigHTTP + var raw rawExternalToolTextResultForLlmContentResource if err := json.Unmarshal(data, &raw); err != nil { return err } - if raw.Auth != nil { - value, err := unmarshalMCPServerAuthConfig(raw.Auth) + if raw.Resource != nil { + value, err := unmarshalExternalToolTextResultForLlmContentResourceDetails(raw.Resource) if err != nil { return err } - r.Auth = value + r.Resource = value } - r.DeferTools = raw.DeferTools - r.DisableToolCache = raw.DisableToolCache + return nil +} + +func (r ExternalToolTextResultForLlmContentResource) MarshalJSON() ([]byte, error) { + type alias ExternalToolTextResultForLlmContentResource + return json.Marshal(struct { + Type ExternalToolTextResultForLlmContentType `json:"type"` + alias + }{ + Type: r.Type(), + alias: alias(r), + }) +} + +func (r ExternalToolTextResultForLlmContentResourceLink) MarshalJSON() ([]byte, error) { + type alias ExternalToolTextResultForLlmContentResourceLink + return json.Marshal(struct { + Type ExternalToolTextResultForLlmContentType `json:"type"` + alias + }{ + Type: r.Type(), + alias: alias(r), + }) +} + +func (r ExternalToolTextResultForLlmContentShellExit) MarshalJSON() ([]byte, error) { + type alias ExternalToolTextResultForLlmContentShellExit + return json.Marshal(struct { + Type ExternalToolTextResultForLlmContentType `json:"type"` + alias + }{ + Type: r.Type(), + alias: alias(r), + }) +} + +func (r ExternalToolTextResultForLlmContentTerminal) MarshalJSON() ([]byte, error) { + type alias ExternalToolTextResultForLlmContentTerminal + return json.Marshal(struct { + Type ExternalToolTextResultForLlmContentType `json:"type"` + alias + }{ + Type: r.Type(), + alias: alias(r), + }) +} + +func (r ExternalToolTextResultForLlmContentText) MarshalJSON() ([]byte, error) { + type alias ExternalToolTextResultForLlmContentText + return json.Marshal(struct { + Type ExternalToolTextResultForLlmContentType `json:"type"` + alias + }{ + Type: r.Type(), + alias: alias(r), + }) +} + +func (r *ExternalToolTextResultForLlm) UnmarshalJSON(data []byte) error { + type rawExternalToolTextResultForLlm struct { + BinaryResultsForLlm []ExternalToolTextResultForLlmBinaryResultsForLlm `json:"binaryResultsForLlm,omitzero"` + Contents []json.RawMessage `json:"contents,omitzero"` + Error *string `json:"error,omitempty"` + ResultType *string `json:"resultType,omitempty"` + SessionLog *string `json:"sessionLog,omitempty"` + TextResultForLlm string `json:"textResultForLlm"` + ToolReferences []string `json:"toolReferences,omitzero"` + ToolTelemetry map[string]any `json:"toolTelemetry,omitzero"` + } + var raw rawExternalToolTextResultForLlm + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + r.BinaryResultsForLlm = raw.BinaryResultsForLlm + if raw.Contents != nil { + r.Contents = make([]ExternalToolTextResultForLlmContent, 0, len(raw.Contents)) + for _, rawItem := range raw.Contents { + value, err := unmarshalExternalToolTextResultForLlmContent(rawItem) + if err != nil { + return err + } + r.Contents = append(r.Contents, value) + } + } + r.Error = raw.Error + r.ResultType = raw.ResultType + r.SessionLog = raw.SessionLog + r.TextResultForLlm = raw.TextResultForLlm + r.ToolReferences = raw.ToolReferences + r.ToolTelemetry = raw.ToolTelemetry + return nil +} + +func unmarshalExternalToolResult(data []byte) (ExternalToolResult, error) { + if string(data) == "null" { + return nil, nil + } + { + var value string + if err := json.Unmarshal(data, &value); err == nil { + return ExternalToolStringResult(value), nil + } + } + { + var value ExternalToolTextResultForLlm + if err := json.Unmarshal(data, &value); err == nil { + return &value, nil + } + } + return nil, errors.New("data did not match any union variant for ExternalToolResult") +} + +func unmarshalFactoryRunFailure(data []byte) (FactoryRunFailure, error) { + if string(data) == "null" { + return nil, nil + } + type rawUnion struct { + Type FactoryRunFailureType `json:"type"` + } + var raw rawUnion + if err := json.Unmarshal(data, &raw); err != nil { + return nil, err + } + + 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 { + return nil, err + } + return &d, nil + case FactoryRunFailureTypeFactoryProviderDisconnected: + var d FactoryRunFailureFactoryProviderDisconnected + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case FactoryRunFailureTypeFactoryResumeDeclined: + var d FactoryRunFailureFactoryResumeDeclined + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + default: + return &RawFactoryRunFailureData{Discriminator: raw.Type, Raw: data}, nil + } +} + +func (r RawFactoryRunFailureData) MarshalJSON() ([]byte, error) { + if r.Raw != nil { + return r.Raw, nil + } + return json.Marshal(struct { + Type FactoryRunFailureType `json:"type"` + }{ + Type: r.Discriminator, + }) +} + +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 { + Type FactoryRunFailureType `json:"type"` + alias + }{ + Type: r.Type(), + alias: alias(r), + }) +} + +func (r FactoryRunFailureFactoryProviderDisconnected) MarshalJSON() ([]byte, error) { + type alias FactoryRunFailureFactoryProviderDisconnected + return json.Marshal(struct { + Type FactoryRunFailureType `json:"type"` + alias + }{ + Type: r.Type(), + alias: alias(r), + }) +} + +func (r FactoryRunFailureFactoryResumeDeclined) MarshalJSON() ([]byte, error) { + type alias FactoryRunFailureFactoryResumeDeclined + return json.Marshal(struct { + Type FactoryRunFailureType `json:"type"` + alias + }{ + Type: r.Type(), + alias: alias(r), + }) +} + +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 { + Attempt *int64 `json:"attempt,omitempty"` + Error *string `json:"error,omitempty"` + Failure json.RawMessage `json:"failure,omitempty"` + Reason *string `json:"reason,omitempty"` + Result any `json:"result,omitempty"` + RunID string `json:"runId"` + Snapshot any `json:"snapshot,omitempty"` + Status FactoryRunStatus `json:"status"` + } + var raw rawFactoryRunResult + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + r.Attempt = raw.Attempt + 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.Result = raw.Result + r.RunID = raw.RunID + r.Snapshot = raw.Snapshot + r.Status = raw.Status + return nil +} + +func unmarshalFilterMapping(data []byte) (FilterMapping, error) { + if string(data) == "null" { + return nil, nil + } + { + var value FilterMappingEnumMap + if err := json.Unmarshal(data, &value); err == nil { + return value, nil + } + } + { + var value ContentFilterMode + if err := json.Unmarshal(data, &value); err == nil { + return value, nil + } + } + return nil, errors.New("data did not match any union variant for FilterMapping") +} + +func unmarshalGitHubTokenAcquireResult(data []byte) (GitHubTokenAcquireResult, error) { + if string(data) == "null" { + return nil, nil + } + type rawUnion struct { + Kind GitHubTokenAcquireResultKind `json:"kind"` + } + var raw rawUnion + if err := json.Unmarshal(data, &raw); err != nil { + return nil, err + } + + switch raw.Kind { + case GitHubTokenAcquireResultKindCancelled: + var d GitHubTokenAcquireResultCancelled + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case GitHubTokenAcquireResultKindToken: + var d GitHubTokenAcquireResultToken + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + default: + return &RawGitHubTokenAcquireResultData{Discriminator: raw.Kind, Raw: data}, nil + } +} + +func (r RawGitHubTokenAcquireResultData) MarshalJSON() ([]byte, error) { + if r.Raw != nil { + return r.Raw, nil + } + return json.Marshal(struct { + Kind GitHubTokenAcquireResultKind `json:"kind"` + }{ + Kind: r.Discriminator, + }) +} + +func (r GitHubTokenAcquireResultCancelled) MarshalJSON() ([]byte, error) { + type alias GitHubTokenAcquireResultCancelled + return json.Marshal(struct { + Kind GitHubTokenAcquireResultKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + +func (r GitHubTokenAcquireResultToken) MarshalJSON() ([]byte, error) { + type alias GitHubTokenAcquireResultToken + return json.Marshal(struct { + Kind GitHubTokenAcquireResultKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + +func (r *HandlePendingToolCallRequest) UnmarshalJSON(data []byte) error { + type rawHandlePendingToolCallRequest struct { + Error *string `json:"error,omitempty"` + RequestID string `json:"requestId"` + Result json.RawMessage `json:"result,omitempty"` + } + var raw rawHandlePendingToolCallRequest + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + r.Error = raw.Error + r.RequestID = raw.RequestID + if raw.Result != nil { + value, err := unmarshalExternalToolResult(raw.Result) + if err != nil { + return err + } + r.Result = value + } + return nil +} + +func (r InstalledPluginSource) MarshalJSON() ([]byte, error) { + if r.InstalledPluginSourceGitHub != nil { + return json.Marshal(r.InstalledPluginSourceGitHub) + } + if r.InstalledPluginSourceLocal != nil { + return json.Marshal(r.InstalledPluginSourceLocal) + } + if r.InstalledPluginSourceURL != nil { + return json.Marshal(r.InstalledPluginSourceURL) + } + if r.String != nil { + return json.Marshal(r.String) + } + return []byte("null"), nil +} + +func (r *InstalledPluginSource) UnmarshalJSON(data []byte) error { + if string(data) == "null" { + *r = InstalledPluginSource{} + return nil + } + { + var value InstalledPluginSourceGitHub + if err := json.Unmarshal(data, &value); err == nil { + *r = InstalledPluginSource{InstalledPluginSourceGitHub: &value} + return nil + } + } + { + var value InstalledPluginSourceLocal + if err := json.Unmarshal(data, &value); err == nil { + *r = InstalledPluginSource{InstalledPluginSourceLocal: &value} + return nil + } + } + { + var value InstalledPluginSourceURL + if err := json.Unmarshal(data, &value); err == nil { + *r = InstalledPluginSource{InstalledPluginSourceURL: &value} + return nil + } + } + { + var value string + if err := json.Unmarshal(data, &value); err == nil { + *r = InstalledPluginSource{String: &value} + return nil + } + } + return errors.New("data did not match any union variant for InstalledPluginSource") +} + +func matchesMCPSerializableServerConfigMCPServerConfigHTTP(data []byte) bool { + var rawGroup0 struct { + Command json.RawMessage `json:"command"` + URL json.RawMessage `json:"url"` + } + if err := json.Unmarshal(data, &rawGroup0); err != nil { + return false + } + if rawGroup0.URL == nil { + return false + } + return rawGroup0.Command == nil +} + +func matchesMCPSerializableServerConfigMCPServerConfigStdio(data []byte) bool { + var rawGroup0 struct { + Command json.RawMessage `json:"command"` + URL json.RawMessage `json:"url"` + } + if err := json.Unmarshal(data, &rawGroup0); err != nil { + return false + } + if rawGroup0.Command == nil { + return false + } + return rawGroup0.URL == nil +} + +func unmarshalMCPSerializableServerConfig(data []byte) (MCPSerializableServerConfig, error) { + if string(data) == "null" { + return nil, nil + } + if matchesMCPSerializableServerConfigMCPServerConfigHTTP(data) { + var d MCPServerConfigHTTP + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + } + if matchesMCPSerializableServerConfigMCPServerConfigStdio(data) { + var d MCPServerConfigStdio + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + } + return &RawMCPSerializableServerConfigData{Raw: data}, nil +} + +func (r RawMCPSerializableServerConfigData) MarshalJSON() ([]byte, error) { + if r.Raw != nil { + return r.Raw, nil + } + return []byte("null"), nil +} + +func unmarshalMCPServerAuthConfig(data []byte) (MCPServerAuthConfig, error) { + if string(data) == "null" { + return nil, nil + } + { + var value bool + if err := json.Unmarshal(data, &value); err == nil { + return MCPServerAuthConfigBoolean(value), nil + } + } + { + var value MCPServerAuthConfigRedirectPort + if err := json.Unmarshal(data, &value); err == nil { + return &value, nil + } + } + return nil, errors.New("data did not match any union variant for MCPServerAuthConfig") +} + +func unmarshalMCPSafeForTelemetry(data []byte) (MCPSafeForTelemetry, error) { + if string(data) == "null" { + return nil, nil + } + { + var value bool + if err := json.Unmarshal(data, &value); err == nil { + return MCPSafeForTelemetryBoolean(value), nil + } + } + { + var value MCPSafeForTelemetryFields + if err := json.Unmarshal(data, &value); err == nil { + return &value, nil + } + } + return nil, errors.New("data did not match any union variant for MCPSafeForTelemetry") +} + +func (r *MCPServerConfigHTTP) UnmarshalJSON(data []byte) error { + type rawMCPServerConfigHTTP struct { + Auth json.RawMessage `json:"auth,omitempty"` + ConfigWarnings []string `json:"configWarnings,omitzero"` + DeferTools *MCPServerConfigDeferTools `json:"deferTools,omitempty"` + DisableSecretMasking *bool `json:"disableSecretMasking,omitempty"` + DisableToolCache *bool `json:"disableToolCache,omitempty"` + DisplayName *string `json:"displayName,omitempty"` + Events []string `json:"events,omitzero"` + ExcludeTools []string `json:"excludeTools,omitzero"` + FilterMapping json.RawMessage `json:"filterMapping,omitempty"` + Headers map[string]string `json:"headers,omitzero"` + HeadersRefreshTtlMs *int64 `json:"headersRefreshTtlMs,omitempty"` + IsDefaultServer *bool `json:"isDefaultServer,omitempty"` + Notifications []string `json:"notifications,omitzero"` + OauthClientID *string `json:"oauthClientId,omitempty"` + OauthGrantType *MCPServerConfigHTTPOauthGrantType `json:"oauthGrantType,omitempty"` + OauthPublicClient *bool `json:"oauthPublicClient,omitempty"` + Oidc json.RawMessage `json:"oidc,omitempty"` + SafeForTelemetry json.RawMessage `json:"safeForTelemetry,omitempty"` + Source *MCPServerSource `json:"source,omitempty"` + SourcePath *string `json:"sourcePath,omitempty"` + SourcePlugin *string `json:"sourcePlugin,omitempty"` + SourcePluginSpec *bool `json:"sourcePluginSpec,omitempty"` + SourcePluginVersion *string `json:"sourcePluginVersion,omitempty"` + Timeout *int64 `json:"timeout,omitempty"` + Tools []string `json:"tools,omitzero"` + Type *MCPServerConfigHTTPType `json:"type,omitempty"` + URL string `json:"url"` + } + var raw rawMCPServerConfigHTTP + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + if raw.Auth != nil { + value, err := unmarshalMCPServerAuthConfig(raw.Auth) + if err != nil { + return err + } + r.Auth = value + } + r.ConfigWarnings = raw.ConfigWarnings + r.DeferTools = raw.DeferTools + r.DisableSecretMasking = raw.DisableSecretMasking + r.DisableToolCache = raw.DisableToolCache + r.DisplayName = raw.DisplayName + r.Events = raw.Events + r.ExcludeTools = raw.ExcludeTools + if raw.FilterMapping != nil { + value, err := unmarshalFilterMapping(raw.FilterMapping) + if err != nil { + return err + } + r.FilterMapping = value + } + r.Headers = raw.Headers + r.HeadersRefreshTtlMs = raw.HeadersRefreshTtlMs + r.IsDefaultServer = raw.IsDefaultServer + r.Notifications = raw.Notifications + r.OauthClientID = raw.OauthClientID + r.OauthGrantType = raw.OauthGrantType + r.OauthPublicClient = raw.OauthPublicClient + if raw.Oidc != nil { + value, err := unmarshalMCPServerAuthConfig(raw.Oidc) + if err != nil { + return err + } + r.Oidc = value + } + if raw.SafeForTelemetry != nil { + value, err := unmarshalMCPSafeForTelemetry(raw.SafeForTelemetry) + if err != nil { + return err + } + r.SafeForTelemetry = value + } + r.Source = raw.Source + r.SourcePath = raw.SourcePath + r.SourcePlugin = raw.SourcePlugin + r.SourcePluginSpec = raw.SourcePluginSpec + r.SourcePluginVersion = raw.SourcePluginVersion + r.Timeout = raw.Timeout + r.Tools = raw.Tools + r.Type = raw.Type + r.URL = raw.URL + return nil +} + +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"` + ConfigWarnings []string `json:"configWarnings,omitzero"` + Cwd *string `json:"cwd,omitempty"` + DeferTools *MCPServerConfigDeferTools `json:"deferTools,omitempty"` + DisableSecretMasking *bool `json:"disableSecretMasking,omitempty"` + DisableToolCache *bool `json:"disableToolCache,omitempty"` + DisplayName *string `json:"displayName,omitempty"` + Env map[string]string `json:"env,omitzero"` + Events []string `json:"events,omitzero"` + ExcludeTools []string `json:"excludeTools,omitzero"` + FilterMapping json.RawMessage `json:"filterMapping,omitempty"` + IsDefaultServer *bool `json:"isDefaultServer,omitempty"` + Notifications []string `json:"notifications,omitzero"` + Oidc json.RawMessage `json:"oidc,omitempty"` + SafeForTelemetry json.RawMessage `json:"safeForTelemetry,omitempty"` + Source *MCPServerSource `json:"source,omitempty"` + SourcePath *string `json:"sourcePath,omitempty"` + SourcePlugin *string `json:"sourcePlugin,omitempty"` + SourcePluginSpec *bool `json:"sourcePluginSpec,omitempty"` + SourcePluginVersion *string `json:"sourcePluginVersion,omitempty"` + Timeout *int64 `json:"timeout,omitempty"` + Tools []string `json:"tools,omitzero"` + Type *MCPServerConfigStdioType `json:"type,omitempty"` + } + var raw rawMCPServerConfigStdio + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + r.Args = raw.Args + if raw.Auth != nil { + value, err := unmarshalMCPServerAuthConfig(raw.Auth) + if err != nil { + return err + } + r.Auth = value + } + r.Command = raw.Command + r.ConfigWarnings = raw.ConfigWarnings + r.Cwd = raw.Cwd + r.DeferTools = raw.DeferTools + r.DisableSecretMasking = raw.DisableSecretMasking + r.DisableToolCache = raw.DisableToolCache + r.DisplayName = raw.DisplayName + r.Env = raw.Env + r.Events = raw.Events + r.ExcludeTools = raw.ExcludeTools if raw.FilterMapping != nil { value, err := unmarshalFilterMapping(raw.FilterMapping) if err != nil { @@ -1401,11 +2140,8 @@ func (r *MCPServerConfigHTTP) UnmarshalJSON(data []byte) error { } r.FilterMapping = value } - r.Headers = raw.Headers r.IsDefaultServer = raw.IsDefaultServer - r.OauthClientID = raw.OauthClientID - r.OauthGrantType = raw.OauthGrantType - r.OauthPublicClient = raw.OauthPublicClient + r.Notifications = raw.Notifications if raw.Oidc != nil { value, err := unmarshalMCPServerAuthConfig(raw.Oidc) if err != nil { @@ -1413,132 +2149,647 @@ func (r *MCPServerConfigHTTP) UnmarshalJSON(data []byte) error { } r.Oidc = value } - r.Timeout = raw.Timeout - r.Tools = raw.Tools - r.Type = raw.Type - r.URL = raw.URL - return nil + if raw.SafeForTelemetry != nil { + value, err := unmarshalMCPSafeForTelemetry(raw.SafeForTelemetry) + if err != nil { + return err + } + r.SafeForTelemetry = value + } + r.Source = raw.Source + r.SourcePath = raw.SourcePath + r.SourcePlugin = raw.SourcePlugin + r.SourcePluginSpec = raw.SourcePluginSpec + r.SourcePluginVersion = raw.SourcePluginVersion + r.Timeout = raw.Timeout + r.Tools = raw.Tools + r.Type = raw.Type + return nil +} + +func (r *MCPConfigAddRequest) UnmarshalJSON(data []byte) error { + type rawMCPConfigAddRequest struct { + Config json.RawMessage `json:"config"` + Name string `json:"name"` + } + var raw rawMCPConfigAddRequest + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + if raw.Config != nil { + value, err := unmarshalMCPSerializableServerConfig(raw.Config) + if err != nil { + return err + } + r.Config = value + } + r.Name = raw.Name + return nil +} + +func (r *MCPConfigList) UnmarshalJSON(data []byte) error { + type rawMCPConfigList struct { + Servers map[string]json.RawMessage `json:"servers"` + } + var raw rawMCPConfigList + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + if raw.Servers != nil { + r.Servers = make(map[string]MCPSerializableServerConfig, len(raw.Servers)) + for key, rawValue := range raw.Servers { + value, err := unmarshalMCPSerializableServerConfig(rawValue) + if err != nil { + return err + } + r.Servers[key] = value + } + } + return nil +} + +func (r *MCPConfigUpdateRequest) UnmarshalJSON(data []byte) error { + type rawMCPConfigUpdateRequest struct { + Config json.RawMessage `json:"config"` + Name string `json:"name"` + } + var raw rawMCPConfigUpdateRequest + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + if raw.Config != nil { + value, err := unmarshalMCPSerializableServerConfig(raw.Config) + if err != nil { + return err + } + r.Config = value + } + r.Name = raw.Name + return nil +} + +func unmarshalMCPHeadersHandlePendingHeadersRefreshRequest(data []byte) (MCPHeadersHandlePendingHeadersRefreshRequest, error) { + if string(data) == "null" { + return nil, nil + } + type rawUnion struct { + Kind MCPHeadersHandlePendingHeadersRefreshRequestKind `json:"kind"` + } + var raw rawUnion + if err := json.Unmarshal(data, &raw); err != nil { + return nil, err + } + + switch raw.Kind { + case MCPHeadersHandlePendingHeadersRefreshRequestKindHeaders: + var d MCPHeadersHandlePendingHeadersRefreshRequestHeaders + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case MCPHeadersHandlePendingHeadersRefreshRequestKindNone: + var d MCPHeadersHandlePendingHeadersRefreshRequestNone + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + default: + return &RawMCPHeadersHandlePendingHeadersRefreshRequestData{Discriminator: raw.Kind, Raw: data}, nil + } +} + +func (r RawMCPHeadersHandlePendingHeadersRefreshRequestData) MarshalJSON() ([]byte, error) { + if r.Raw != nil { + return r.Raw, nil + } + return json.Marshal(struct { + Kind MCPHeadersHandlePendingHeadersRefreshRequestKind `json:"kind"` + }{ + Kind: r.Discriminator, + }) +} + +func (r MCPHeadersHandlePendingHeadersRefreshRequestHeaders) MarshalJSON() ([]byte, error) { + type alias MCPHeadersHandlePendingHeadersRefreshRequestHeaders + return json.Marshal(struct { + Kind MCPHeadersHandlePendingHeadersRefreshRequestKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + +func (r MCPHeadersHandlePendingHeadersRefreshRequestNone) MarshalJSON() ([]byte, error) { + type alias MCPHeadersHandlePendingHeadersRefreshRequestNone + return json.Marshal(struct { + Kind MCPHeadersHandlePendingHeadersRefreshRequestKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + +func (r *MCPHeadersHandlePendingHeadersRefreshRequestRequest) UnmarshalJSON(data []byte) error { + type rawMCPHeadersHandlePendingHeadersRefreshRequestRequest struct { + RequestID string `json:"requestId"` + Result json.RawMessage `json:"result"` + } + var raw rawMCPHeadersHandlePendingHeadersRefreshRequestRequest + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + r.RequestID = raw.RequestID + if raw.Result != nil { + value, err := unmarshalMCPHeadersHandlePendingHeadersRefreshRequest(raw.Result) + if err != nil { + return err + } + r.Result = value + } + return nil +} + +func unmarshalMCPPlanTransportChoice(data []byte) (MCPPlanTransportChoice, error) { + if string(data) == "null" { + return nil, nil + } + type rawUnion struct { + Transport MCPPlanTransportChoiceTransport `json:"transport"` + } + var raw rawUnion + if err := json.Unmarshal(data, &raw); err != nil { + return nil, err + } + + switch raw.Transport { + case MCPPlanTransportChoiceTransportHTTP: + var d MCPPlanTransportChoiceRemote + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case MCPPlanTransportChoiceTransportSSE: + var d MCPPlanTransportChoiceRemote + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case MCPPlanTransportChoiceTransportStdio: + var d MCPPlanTransportChoicePackage + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case MCPPlanTransportChoiceTransportStreamableHTTP: + var d MCPPlanTransportChoiceRemote + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + default: + return &RawMCPPlanTransportChoiceData{Discriminator: raw.Transport, Raw: data}, nil + } +} + +func (r RawMCPPlanTransportChoiceData) MarshalJSON() ([]byte, error) { + if r.Raw != nil { + return r.Raw, nil + } + return json.Marshal(struct { + Transport MCPPlanTransportChoiceTransport `json:"transport"` + }{ + Transport: r.Discriminator, + }) +} + +func unmarshalMCPPlanRequiredValue(data []byte) (MCPPlanRequiredValue, error) { + if string(data) == "null" { + return nil, nil + } + type rawUnion struct { + Kind MCPPlanRequiredValueKind `json:"kind"` + } + var raw rawUnion + if err := json.Unmarshal(data, &raw); err != nil { + return nil, err + } + + switch raw.Kind { + case MCPPlanRequiredValueKindEnum: + var d MCPPlanRequiredValueEnum + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case MCPPlanRequiredValueKindScalar: + var d MCPPlanRequiredValueScalar + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + default: + return &RawMCPPlanRequiredValueData{Discriminator: raw.Kind, Raw: data}, nil + } +} + +func (r RawMCPPlanRequiredValueData) MarshalJSON() ([]byte, error) { + if r.Raw != nil { + return r.Raw, nil + } + return json.Marshal(struct { + Kind MCPPlanRequiredValueKind `json:"kind"` + }{ + Kind: r.Discriminator, + }) } -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"` - 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"` +func (r MCPPlanRequiredValueEnum) MarshalJSON() ([]byte, error) { + type alias MCPPlanRequiredValueEnum + return json.Marshal(struct { + Kind MCPPlanRequiredValueKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + +func (r MCPPlanRequiredValueScalar) MarshalJSON() ([]byte, error) { + type alias MCPPlanRequiredValueScalar + return json.Marshal(struct { + Kind MCPPlanRequiredValueKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + +func (r *MCPPlanTransportChoicePackage) UnmarshalJSON(data []byte) error { + type rawMCPPlanTransportChoicePackage struct { + ChoiceID string `json:"choiceId"` + InstallMethod MCPPlanPackageInstallMethod `json:"installMethod"` + PackageIdentifier string `json:"packageIdentifier"` + PackageType string `json:"packageType"` + RequiredValues []json.RawMessage `json:"requiredValues"` + SecretPlaceholders []MCPPlanSecretPlaceholder `json:"secretPlaceholders"` } - var raw rawMCPServerConfigStdio + var raw rawMCPPlanTransportChoicePackage if err := json.Unmarshal(data, &raw); err != nil { return err } - r.Args = raw.Args - if raw.Auth != nil { - value, err := unmarshalMCPServerAuthConfig(raw.Auth) - if err != nil { - return err - } - r.Auth = value - } - 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) - if err != nil { - return err - } - r.FilterMapping = value - } - r.IsDefaultServer = raw.IsDefaultServer - if raw.Oidc != nil { - value, err := unmarshalMCPServerAuthConfig(raw.Oidc) - if err != nil { - return err + r.ChoiceID = raw.ChoiceID + r.InstallMethod = raw.InstallMethod + r.PackageIdentifier = raw.PackageIdentifier + r.PackageType = raw.PackageType + if raw.RequiredValues != nil { + r.RequiredValues = make([]MCPPlanRequiredValue, 0, len(raw.RequiredValues)) + for _, rawItem := range raw.RequiredValues { + value, err := unmarshalMCPPlanRequiredValue(rawItem) + if err != nil { + return err + } + r.RequiredValues = append(r.RequiredValues, value) } - r.Oidc = value } - r.Timeout = raw.Timeout - r.Tools = raw.Tools + r.SecretPlaceholders = raw.SecretPlaceholders return nil } -func (r *MCPConfigAddRequest) UnmarshalJSON(data []byte) error { - type rawMCPConfigAddRequest struct { - Config json.RawMessage `json:"config"` - Name string `json:"name"` +func (r MCPPlanTransportChoicePackage) MarshalJSON() ([]byte, error) { + type alias MCPPlanTransportChoicePackage + return json.Marshal(struct { + Transport MCPPlanTransportChoiceTransport `json:"transport"` + alias + }{ + Transport: r.Transport(), + alias: alias(r), + }) +} + +func (r *MCPPlanTransportChoiceRemote) UnmarshalJSON(data []byte) error { + type rawMCPPlanTransportChoiceRemote struct { + ChoiceID string `json:"choiceId"` + Endpoint string `json:"endpoint"` + InstallMethod MCPPlanRemoteInstallMethod `json:"installMethod"` + RequiredValues []json.RawMessage `json:"requiredValues"` + SecretPlaceholders []MCPPlanSecretPlaceholder `json:"secretPlaceholders"` + Discriminator MCPPlanRemoteTransport `json:"transport,omitempty"` } - var raw rawMCPConfigAddRequest + var raw rawMCPPlanTransportChoiceRemote if err := json.Unmarshal(data, &raw); err != nil { return err } - if raw.Config != nil { - value, err := unmarshalMCPServerConfig(raw.Config) - if err != nil { - return err + r.ChoiceID = raw.ChoiceID + r.Endpoint = raw.Endpoint + r.InstallMethod = raw.InstallMethod + if raw.RequiredValues != nil { + r.RequiredValues = make([]MCPPlanRequiredValue, 0, len(raw.RequiredValues)) + for _, rawItem := range raw.RequiredValues { + value, err := unmarshalMCPPlanRequiredValue(rawItem) + if err != nil { + return err + } + r.RequiredValues = append(r.RequiredValues, value) } - r.Config = value } - r.Name = raw.Name + r.SecretPlaceholders = raw.SecretPlaceholders + r.Discriminator = raw.Discriminator return nil } -func (r *MCPConfigList) UnmarshalJSON(data []byte) error { - type rawMCPConfigList struct { - Servers map[string]json.RawMessage `json:"servers"` +func (r MCPPlanTransportChoiceRemote) MarshalJSON() ([]byte, error) { + type alias MCPPlanTransportChoiceRemote + return json.Marshal(struct { + Transport MCPPlanTransportChoiceTransport `json:"transport"` + alias + }{ + Transport: r.Transport(), + alias: alias(r), + }) +} + +func (r *MCPInstallPlan) UnmarshalJSON(data []byte) error { + type rawMCPInstallPlan struct { + ConfigurationChanges []MCPPlanConfigurationChange `json:"configurationChanges"` + Identity MCPPlanResourceIdentity `json:"identity"` + PlanHandle string `json:"planHandle"` + PlanHandleExpiresAt string `json:"planHandleExpiresAt"` + Policy MCPPlanPolicyResult `json:"policy"` + Provenance MCPPlanProvenance `json:"provenance"` + RecommendedTransportChoiceID *string `json:"recommendedTransportChoiceId,omitempty"` + ReloadRequired bool `json:"reloadRequired"` + RequiresInteractiveConfiguration bool `json:"requiresInteractiveConfiguration"` + Target MCPPlanTarget `json:"target"` + TransportChoices []json.RawMessage `json:"transportChoices"` } - var raw rawMCPConfigList + var raw rawMCPInstallPlan if err := json.Unmarshal(data, &raw); err != nil { return err } - if raw.Servers != nil { - r.Servers = make(map[string]MCPServerConfig, len(raw.Servers)) - for key, rawValue := range raw.Servers { - value, err := unmarshalMCPServerConfig(rawValue) + r.ConfigurationChanges = raw.ConfigurationChanges + r.Identity = raw.Identity + r.PlanHandle = raw.PlanHandle + r.PlanHandleExpiresAt = raw.PlanHandleExpiresAt + r.Policy = raw.Policy + r.Provenance = raw.Provenance + r.RecommendedTransportChoiceID = raw.RecommendedTransportChoiceID + r.ReloadRequired = raw.ReloadRequired + r.RequiresInteractiveConfiguration = raw.RequiresInteractiveConfiguration + r.Target = raw.Target + if raw.TransportChoices != nil { + r.TransportChoices = make([]MCPPlanTransportChoice, 0, len(raw.TransportChoices)) + for _, rawItem := range raw.TransportChoices { + value, err := unmarshalMCPPlanTransportChoice(rawItem) if err != nil { return err } - r.Servers[key] = value + r.TransportChoices = append(r.TransportChoices, value) } } return nil } -func (r *MCPConfigUpdateRequest) UnmarshalJSON(data []byte) error { - type rawMCPConfigUpdateRequest struct { - Config json.RawMessage `json:"config"` - Name string `json:"name"` +func unmarshalMCPOauthPendingRequestResponse(data []byte) (MCPOauthPendingRequestResponse, error) { + if string(data) == "null" { + return nil, nil } - var raw rawMCPConfigUpdateRequest + type rawUnion struct { + Kind MCPOauthPendingRequestResponseKind `json:"kind"` + } + var raw rawUnion + if err := json.Unmarshal(data, &raw); err != nil { + return nil, err + } + + switch raw.Kind { + case MCPOauthPendingRequestResponseKindCancelled: + var d MCPOauthPendingRequestResponseCancelled + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case MCPOauthPendingRequestResponseKindToken: + var d MCPOauthPendingRequestResponseToken + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + default: + return &RawMCPOauthPendingRequestResponseData{Discriminator: raw.Kind, Raw: data}, nil + } +} + +func (r RawMCPOauthPendingRequestResponseData) MarshalJSON() ([]byte, error) { + if r.Raw != nil { + return r.Raw, nil + } + return json.Marshal(struct { + Kind MCPOauthPendingRequestResponseKind `json:"kind"` + }{ + Kind: r.Discriminator, + }) +} + +func (r MCPOauthPendingRequestResponseCancelled) MarshalJSON() ([]byte, error) { + type alias MCPOauthPendingRequestResponseCancelled + return json.Marshal(struct { + Kind MCPOauthPendingRequestResponseKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + +func (r MCPOauthPendingRequestResponseToken) MarshalJSON() ([]byte, error) { + type alias MCPOauthPendingRequestResponseToken + return json.Marshal(struct { + Kind MCPOauthPendingRequestResponseKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + +func (r *MCPOauthHandlePendingRequest) UnmarshalJSON(data []byte) error { + type rawMCPOauthHandlePendingRequest struct { + RequestID string `json:"requestId"` + Result json.RawMessage `json:"result"` + } + var raw rawMCPOauthHandlePendingRequest if err := json.Unmarshal(data, &raw); err != nil { return err } - if raw.Config != nil { - value, err := unmarshalMCPServerConfig(raw.Config) + r.RequestID = raw.RequestID + if raw.Result != nil { + value, err := unmarshalMCPOauthPendingRequestResponse(raw.Result) if err != nil { return err } - r.Config = value + r.Result = value + } + return nil +} + +func unmarshalMCPOauthProbeResult(data []byte) (MCPOauthProbeResult, error) { + if string(data) == "null" { + return nil, nil + } + type rawUnion struct { + Status MCPOauthProbeResultStatus `json:"status"` + } + var raw rawUnion + if err := json.Unmarshal(data, &raw); err != nil { + return nil, err + } + + switch raw.Status { + case MCPOauthProbeResultStatusAuthenticated: + var d MCPOauthProbeResultAuthenticated + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case MCPOauthProbeResultStatusFailed: + var d MCPOauthProbeResultFailed + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case MCPOauthProbeResultStatusNeedsAuth: + var d MCPOauthProbeResultNeedsAuth + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case MCPOauthProbeResultStatusNoAuthRequired: + var d MCPOauthProbeResultNoAuthRequired + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + default: + return &RawMCPOauthProbeResultData{Discriminator: raw.Status, Raw: data}, nil + } +} + +func (r RawMCPOauthProbeResultData) MarshalJSON() ([]byte, error) { + if r.Raw != nil { + return r.Raw, nil + } + return json.Marshal(struct { + Status MCPOauthProbeResultStatus `json:"status"` + }{ + Status: r.Discriminator, + }) +} + +func (r MCPOauthProbeResultAuthenticated) MarshalJSON() ([]byte, error) { + type alias MCPOauthProbeResultAuthenticated + return json.Marshal(struct { + Status MCPOauthProbeResultStatus `json:"status"` + alias + }{ + Status: r.Status(), + alias: alias(r), + }) +} + +func (r MCPOauthProbeResultFailed) MarshalJSON() ([]byte, error) { + type alias MCPOauthProbeResultFailed + return json.Marshal(struct { + Status MCPOauthProbeResultStatus `json:"status"` + alias + }{ + Status: r.Status(), + alias: alias(r), + }) +} + +func (r MCPOauthProbeResultNeedsAuth) MarshalJSON() ([]byte, error) { + type alias MCPOauthProbeResultNeedsAuth + return json.Marshal(struct { + Status MCPOauthProbeResultStatus `json:"status"` + alias + }{ + Status: r.Status(), + alias: alias(r), + }) +} + +func (r MCPOauthProbeResultNoAuthRequired) MarshalJSON() ([]byte, error) { + type alias MCPOauthProbeResultNoAuthRequired + return json.Marshal(struct { + Status MCPOauthProbeResultStatus `json:"status"` + alias + }{ + Status: r.Status(), + alias: alias(r), + }) +} + +func unmarshalMCPPlanInstallSource(data []byte) (MCPPlanInstallSource, error) { + if string(data) == "null" { + return nil, nil + } + type rawUnion struct { + Kind MCPPlanInstallSourceKind `json:"kind"` + } + var raw rawUnion + if err := json.Unmarshal(data, &raw); err != nil { + return nil, err + } + + switch raw.Kind { + case MCPPlanInstallSourceKindCandidate: + var d MCPPlanInstallSourceCandidate + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case MCPPlanInstallSourceKindCard: + var d MCPPlanInstallSourceCard + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + default: + return &RawMCPPlanInstallSourceData{Discriminator: raw.Kind, Raw: data}, nil } - r.Name = raw.Name - return nil } -func unmarshalMCPHeadersHandlePendingHeadersRefreshRequest(data []byte) (MCPHeadersHandlePendingHeadersRefreshRequest, error) { +func (r RawMCPPlanInstallSourceData) MarshalJSON() ([]byte, error) { + if r.Raw != nil { + return r.Raw, nil + } + return json.Marshal(struct { + Kind MCPPlanInstallSourceKind `json:"kind"` + }{ + Kind: r.Discriminator, + }) +} + +func (r MCPPlanInstallSourceCandidate) MarshalJSON() ([]byte, error) { + type alias MCPPlanInstallSourceCandidate + return json.Marshal(struct { + Kind MCPPlanInstallSourceKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + +func unmarshalMCPServerCardReference(data []byte) (MCPServerCardReference, error) { if string(data) == "null" { return nil, nil } type rawUnion struct { - Kind MCPHeadersHandlePendingHeadersRefreshRequestKind `json:"kind"` + Kind MCPServerCardReferenceKind `json:"kind"` } var raw rawUnion if err := json.Unmarshal(data, &raw); err != nil { @@ -1546,38 +2797,38 @@ func unmarshalMCPHeadersHandlePendingHeadersRefreshRequest(data []byte) (MCPHead } switch raw.Kind { - case MCPHeadersHandlePendingHeadersRefreshRequestKindHeaders: - var d MCPHeadersHandlePendingHeadersRefreshRequestHeaders + case MCPServerCardReferenceKindEmbedded: + var d MCPServerCardEmbedded if err := json.Unmarshal(data, &d); err != nil { return nil, err } return &d, nil - case MCPHeadersHandlePendingHeadersRefreshRequestKindNone: - var d MCPHeadersHandlePendingHeadersRefreshRequestNone + case MCPServerCardReferenceKindURL: + var d MCPServerCardURL if err := json.Unmarshal(data, &d); err != nil { return nil, err } return &d, nil default: - return &RawMCPHeadersHandlePendingHeadersRefreshRequestData{Discriminator: raw.Kind, Raw: data}, nil + return &RawMCPServerCardReferenceData{Discriminator: raw.Kind, Raw: data}, nil } } -func (r RawMCPHeadersHandlePendingHeadersRefreshRequestData) MarshalJSON() ([]byte, error) { +func (r RawMCPServerCardReferenceData) MarshalJSON() ([]byte, error) { if r.Raw != nil { return r.Raw, nil } return json.Marshal(struct { - Kind MCPHeadersHandlePendingHeadersRefreshRequestKind `json:"kind"` + Kind MCPServerCardReferenceKind `json:"kind"` }{ Kind: r.Discriminator, }) } -func (r MCPHeadersHandlePendingHeadersRefreshRequestHeaders) MarshalJSON() ([]byte, error) { - type alias MCPHeadersHandlePendingHeadersRefreshRequestHeaders +func (r MCPServerCardEmbedded) MarshalJSON() ([]byte, error) { + type alias MCPServerCardEmbedded return json.Marshal(struct { - Kind MCPHeadersHandlePendingHeadersRefreshRequestKind `json:"kind"` + Kind MCPServerCardReferenceKind `json:"kind"` alias }{ Kind: r.Kind(), @@ -1585,10 +2836,10 @@ func (r MCPHeadersHandlePendingHeadersRefreshRequestHeaders) MarshalJSON() ([]by }) } -func (r MCPHeadersHandlePendingHeadersRefreshRequestNone) MarshalJSON() ([]byte, error) { - type alias MCPHeadersHandlePendingHeadersRefreshRequestNone +func (r MCPServerCardURL) MarshalJSON() ([]byte, error) { + type alias MCPServerCardURL return json.Marshal(struct { - Kind MCPHeadersHandlePendingHeadersRefreshRequestKind `json:"kind"` + Kind MCPServerCardReferenceKind `json:"kind"` alias }{ Kind: r.Kind(), @@ -1596,32 +2847,63 @@ func (r MCPHeadersHandlePendingHeadersRefreshRequestNone) MarshalJSON() ([]byte, }) } -func (r *MCPHeadersHandlePendingHeadersRefreshRequestRequest) UnmarshalJSON(data []byte) error { - type rawMCPHeadersHandlePendingHeadersRefreshRequestRequest struct { - RequestID string `json:"requestId"` - Result json.RawMessage `json:"result"` +func (r *MCPPlanInstallSourceCard) UnmarshalJSON(data []byte) error { + type rawMCPPlanInstallSourceCard struct { + Card json.RawMessage `json:"card"` } - var raw rawMCPHeadersHandlePendingHeadersRefreshRequestRequest + var raw rawMCPPlanInstallSourceCard if err := json.Unmarshal(data, &raw); err != nil { return err } - r.RequestID = raw.RequestID - if raw.Result != nil { - value, err := unmarshalMCPHeadersHandlePendingHeadersRefreshRequest(raw.Result) + if raw.Card != nil { + value, err := unmarshalMCPServerCardReference(raw.Card) if err != nil { return err } - r.Result = value + r.Card = value } return nil } -func unmarshalMCPOauthPendingRequestResponse(data []byte) (MCPOauthPendingRequestResponse, error) { +func (r MCPPlanInstallSourceCard) MarshalJSON() ([]byte, error) { + type alias MCPPlanInstallSourceCard + return json.Marshal(struct { + Kind MCPPlanInstallSourceKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + +func (r *MCPPlanInstallRequest) UnmarshalJSON(data []byte) error { + type rawMCPPlanInstallRequest struct { + Contract CatalogClientContract `json:"contract"` + Scope *MCPPlanScope `json:"scope,omitempty"` + Source json.RawMessage `json:"source"` + } + var raw rawMCPPlanInstallRequest + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + r.Contract = raw.Contract + r.Scope = raw.Scope + if raw.Source != nil { + value, err := unmarshalMCPPlanInstallSource(raw.Source) + if err != nil { + return err + } + r.Source = value + } + return nil +} + +func unmarshalMCPPlanInstallResult(data []byte) (MCPPlanInstallResult, error) { if string(data) == "null" { return nil, nil } type rawUnion struct { - Kind MCPOauthPendingRequestResponseKind `json:"kind"` + Kind MCPPlanInstallResultKind `json:"kind"` } var raw rawUnion if err := json.Unmarshal(data, &raw); err != nil { @@ -1629,73 +2911,366 @@ func unmarshalMCPOauthPendingRequestResponse(data []byte) (MCPOauthPendingReques } switch raw.Kind { - case MCPOauthPendingRequestResponseKindCancelled: - var d MCPOauthPendingRequestResponseCancelled + case MCPPlanInstallResultKindAuthenticationRequired: + var d CatalogAuthenticationRequiredError if err := json.Unmarshal(data, &d); err != nil { return nil, err } return &d, nil - case MCPOauthPendingRequestResponseKindToken: - var d MCPOauthPendingRequestResponseToken + case MCPPlanInstallResultKindContractViolation: + var d CatalogContractViolationError + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case MCPPlanInstallResultKindHandleRejected: + var d CatalogHandleRejectedError + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case MCPPlanInstallResultKindInvalidRequest: + var d CatalogInvalidRequestError + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case MCPPlanInstallResultKindMalformedCard: + var d CatalogMalformedCardError + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case MCPPlanInstallResultKindNegotiationRefused: + var d CatalogNegotiationRefusedError + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case MCPPlanInstallResultKindNetworkFailure: + var d CatalogNetworkFailureError + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case MCPPlanInstallResultKindNotInstallable: + var d CatalogNotInstallableError + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case MCPPlanInstallResultKindPlanned: + var d MCPPlanInstallPlanned + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case MCPPlanInstallResultKindPolicyRejected: + var d CatalogPolicyRejectedError + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case MCPPlanInstallResultKindUnavailable: + var d CatalogUnavailableError + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case MCPPlanInstallResultKindUnavailableTransport: + var d CatalogUnavailableTransportError + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case MCPPlanInstallResultKindUnsafeRetrieval: + var d CatalogUnsafeRetrievalError if err := json.Unmarshal(data, &d); err != nil { return nil, err } return &d, nil default: - return &RawMCPOauthPendingRequestResponseData{Discriminator: raw.Kind, Raw: data}, nil + return &RawMCPPlanInstallResultData{Discriminator: raw.Kind, Raw: data}, nil } } -func (r RawMCPOauthPendingRequestResponseData) MarshalJSON() ([]byte, error) { +func (r RawMCPPlanInstallResultData) MarshalJSON() ([]byte, error) { if r.Raw != nil { return r.Raw, nil } return json.Marshal(struct { - Kind MCPOauthPendingRequestResponseKind `json:"kind"` + Kind MCPPlanInstallResultKind `json:"kind"` }{ Kind: r.Discriminator, }) } -func (r MCPOauthPendingRequestResponseCancelled) MarshalJSON() ([]byte, error) { - type alias MCPOauthPendingRequestResponseCancelled +func (r CatalogHandleRejectedError) MarshalJSON() ([]byte, error) { + type alias CatalogHandleRejectedError return json.Marshal(struct { - Kind MCPOauthPendingRequestResponseKind `json:"kind"` + Kind MCPPlanInstallResultKind `json:"kind"` alias }{ - Kind: r.Kind(), + Kind: r.mcpPlanInstallResultKind(), alias: alias(r), }) } -func (r MCPOauthPendingRequestResponseToken) MarshalJSON() ([]byte, error) { - type alias MCPOauthPendingRequestResponseToken +func (r CatalogNotInstallableError) MarshalJSON() ([]byte, error) { + type alias CatalogNotInstallableError return json.Marshal(struct { - Kind MCPOauthPendingRequestResponseKind `json:"kind"` + Kind MCPPlanInstallResultKind `json:"kind"` alias }{ - Kind: r.Kind(), + Kind: r.mcpPlanInstallResultKind(), alias: alias(r), }) } -func (r *MCPOauthHandlePendingRequest) UnmarshalJSON(data []byte) error { - type rawMCPOauthHandlePendingRequest struct { - RequestID string `json:"requestId"` - Result json.RawMessage `json:"result"` +func (r CatalogUnavailableTransportError) MarshalJSON() ([]byte, error) { + type alias CatalogUnavailableTransportError + return json.Marshal(struct { + Kind MCPPlanInstallResultKind `json:"kind"` + alias + }{ + Kind: r.mcpPlanInstallResultKind(), + alias: alias(r), + }) +} + +func (r MCPPlanInstallPlanned) MarshalJSON() ([]byte, error) { + type alias MCPPlanInstallPlanned + return json.Marshal(struct { + Kind MCPPlanInstallResultKind `json:"kind"` + alias + }{ + Kind: r.mcpPlanInstallResultKind(), + alias: alias(r), + }) +} + +func matchesMCPServerConfigHTTP(data []byte) bool { + var rawGroup0 struct { + Command json.RawMessage `json:"command"` + ServerInstance json.RawMessage `json:"serverInstance"` + Type json.RawMessage `json:"type"` + URL json.RawMessage `json:"url"` } - var raw rawMCPOauthHandlePendingRequest + if err := json.Unmarshal(data, &rawGroup0); err != nil { + return false + } + if rawGroup0.URL == nil { + return false + } + if rawGroup0.Command != nil { + return false + } + if rawGroup0.ServerInstance != nil { + return false + } + return rawGroup0.Type == nil +} + +func matchesMCPServerConfigMemory(data []byte) bool { + var rawGroup0 struct { + Command json.RawMessage `json:"command"` + ServerInstance json.RawMessage `json:"serverInstance"` + Type json.RawMessage `json:"type"` + URL json.RawMessage `json:"url"` + } + if err := json.Unmarshal(data, &rawGroup0); err != nil { + return false + } + if rawGroup0.ServerInstance == nil { + return false + } + if rawGroup0.Type == nil { + return false + } + var rawGroup0String string + if err := json.Unmarshal(rawGroup0.Type, &rawGroup0String); err != nil { + return false + } + switch rawGroup0String { + case "memory": + default: + return false + } + if rawGroup0.Command != nil { + return false + } + return rawGroup0.URL == nil +} + +func matchesMCPServerConfigStdio(data []byte) bool { + var rawGroup0 struct { + Command json.RawMessage `json:"command"` + ServerInstance json.RawMessage `json:"serverInstance"` + Type json.RawMessage `json:"type"` + URL json.RawMessage `json:"url"` + } + if err := json.Unmarshal(data, &rawGroup0); err != nil { + return false + } + if rawGroup0.Command == nil { + return false + } + if rawGroup0.ServerInstance != nil { + return false + } + if rawGroup0.Type != nil { + return false + } + return rawGroup0.URL == nil +} + +func unmarshalMCPServerConfig(data []byte) (MCPServerConfig, error) { + if string(data) == "null" { + return nil, nil + } + if matchesMCPServerConfigHTTP(data) { + var d MCPServerConfigHTTP + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + } + if matchesMCPServerConfigMemory(data) { + var d MCPServerConfigMemory + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + } + if matchesMCPServerConfigStdio(data) { + var d MCPServerConfigStdio + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + } + return &RawMCPServerConfigData{Raw: data}, nil +} + +func (r RawMCPServerConfigData) MarshalJSON() ([]byte, error) { + if r.Raw != nil { + return r.Raw, nil + } + return []byte("null"), nil +} + +func (r *MCPServerConfigMemory) UnmarshalJSON(data []byte) error { + type rawMCPServerConfigMemory struct { + ConfigWarnings []string `json:"configWarnings,omitzero"` + DeferTools *MCPServerConfigDeferTools `json:"deferTools,omitempty"` + DisableSecretMasking *bool `json:"disableSecretMasking,omitempty"` + DisableToolCache *bool `json:"disableToolCache,omitempty"` + DisplayName *string `json:"displayName,omitempty"` + Events []string `json:"events,omitzero"` + ExcludeTools []string `json:"excludeTools,omitzero"` + FilterMapping json.RawMessage `json:"filterMapping,omitempty"` + IsDefaultServer *bool `json:"isDefaultServer,omitempty"` + Notifications []string `json:"notifications,omitzero"` + Oidc json.RawMessage `json:"oidc,omitempty"` + SafeForTelemetry json.RawMessage `json:"safeForTelemetry,omitempty"` + ServerInstance any `json:"serverInstance"` + Source *MCPServerSource `json:"source,omitempty"` + SourcePath *string `json:"sourcePath,omitempty"` + SourcePlugin *string `json:"sourcePlugin,omitempty"` + SourcePluginSpec *bool `json:"sourcePluginSpec,omitempty"` + SourcePluginVersion *string `json:"sourcePluginVersion,omitempty"` + Timeout *int64 `json:"timeout,omitempty"` + Tools []string `json:"tools,omitzero"` + Type MCPServerConfigMemoryType `json:"type"` + } + var raw rawMCPServerConfigMemory + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + r.ConfigWarnings = raw.ConfigWarnings + r.DeferTools = raw.DeferTools + r.DisableSecretMasking = raw.DisableSecretMasking + r.DisableToolCache = raw.DisableToolCache + r.DisplayName = raw.DisplayName + r.Events = raw.Events + r.ExcludeTools = raw.ExcludeTools + if raw.FilterMapping != nil { + value, err := unmarshalFilterMapping(raw.FilterMapping) + if err != nil { + return err + } + r.FilterMapping = value + } + r.IsDefaultServer = raw.IsDefaultServer + r.Notifications = raw.Notifications + if raw.Oidc != nil { + value, err := unmarshalMCPServerAuthConfig(raw.Oidc) + if err != nil { + return err + } + r.Oidc = value + } + if raw.SafeForTelemetry != nil { + value, err := unmarshalMCPSafeForTelemetry(raw.SafeForTelemetry) + if err != nil { + return err + } + r.SafeForTelemetry = value + } + r.ServerInstance = raw.ServerInstance + r.Source = raw.Source + r.SourcePath = raw.SourcePath + r.SourcePlugin = raw.SourcePlugin + r.SourcePluginSpec = raw.SourcePluginSpec + r.SourcePluginVersion = raw.SourcePluginVersion + r.Timeout = raw.Timeout + r.Tools = raw.Tools + r.Type = raw.Type + return nil +} + +func (r *MCPReloadConfig) UnmarshalJSON(data []byte) error { + type rawMCPReloadConfig struct { + ActiveGitHubToken *string `json:"activeGitHubToken,omitempty"` + CLIEnabledServers []string `json:"cliEnabledServers,omitzero"` + ConfigFilter any `json:"configFilter,omitempty"` + DisabledServers []string `json:"disabledServers,omitzero"` + EnabledServers []string `json:"enabledServers,omitzero"` + ForceRestart *bool `json:"forceRestart,omitempty"` + GitHubMCPToolOptions any `json:"githubMcpToolOptions,omitempty"` + GitHubMCPUserOverride *bool `json:"githubMcpUserOverride,omitempty"` + IncludeWorkspaceSources *bool `json:"includeWorkspaceSources,omitempty"` + Mcp3pEnabled *bool `json:"mcp3pEnabled,omitempty"` + MCPServers map[string]json.RawMessage `json:"mcpServers"` + SecretStore any `json:"secretStore,omitempty"` + UseCachedToolSnapshots *bool `json:"useCachedToolSnapshots,omitempty"` + } + var raw rawMCPReloadConfig if err := json.Unmarshal(data, &raw); err != nil { return err } - r.RequestID = raw.RequestID - if raw.Result != nil { - value, err := unmarshalMCPOauthPendingRequestResponse(raw.Result) - if err != nil { - return err + r.ActiveGitHubToken = raw.ActiveGitHubToken + r.CLIEnabledServers = raw.CLIEnabledServers + r.ConfigFilter = raw.ConfigFilter + r.DisabledServers = raw.DisabledServers + r.EnabledServers = raw.EnabledServers + r.ForceRestart = raw.ForceRestart + r.GitHubMCPToolOptions = raw.GitHubMCPToolOptions + r.GitHubMCPUserOverride = raw.GitHubMCPUserOverride + r.IncludeWorkspaceSources = raw.IncludeWorkspaceSources + r.Mcp3pEnabled = raw.Mcp3pEnabled + if raw.MCPServers != nil { + r.MCPServers = make(map[string]MCPServerConfig, len(raw.MCPServers)) + for key, rawValue := range raw.MCPServers { + value, err := unmarshalMCPServerConfig(rawValue) + if err != nil { + return err + } + r.MCPServers[key] = value } - r.Result = value } + r.SecretStore = raw.SecretStore + r.UseCachedToolSnapshots = raw.UseCachedToolSnapshots return nil } @@ -1709,7 +3284,7 @@ func (r *MCPRestartServerRequest) UnmarshalJSON(data []byte) error { return err } if raw.Config != nil { - value, err := unmarshalMCPServerConfig(raw.Config) + value, err := unmarshalMCPSerializableServerConfig(raw.Config) if err != nil { return err } @@ -1729,7 +3304,7 @@ func (r *MCPStartServerRequest) UnmarshalJSON(data []byte) error { return err } if raw.Config != nil { - value, err := unmarshalMCPServerConfig(raw.Config) + value, err := unmarshalMCPSerializableServerConfig(raw.Config) if err != nil { return err } @@ -1894,6 +3469,12 @@ func unmarshalUserToolSessionApproval(data []byte) (UserToolSessionApproval, err return nil, err } return &d, nil + case UserToolSessionApprovalKindExtensionEnvAccess: + var d UserToolSessionApprovalExtensionEnvAccess + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil case UserToolSessionApprovalKindExtensionManagement: var d UserToolSessionApprovalExtensionManagement if err := json.Unmarshal(data, &d); err != nil { @@ -1974,6 +3555,17 @@ func (r UserToolSessionApprovalCustomTool) MarshalJSON() ([]byte, error) { }) } +func (r UserToolSessionApprovalExtensionEnvAccess) MarshalJSON() ([]byte, error) { + type alias UserToolSessionApprovalExtensionEnvAccess + return json.Marshal(struct { + Kind UserToolSessionApprovalKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + func (r UserToolSessionApprovalExtensionManagement) MarshalJSON() ([]byte, error) { type alias UserToolSessionApprovalExtensionManagement return json.Marshal(struct { @@ -2136,6 +3728,12 @@ func unmarshalPermissionDecisionApproveForLocationApproval(data []byte) (Permiss return nil, err } return &d, nil + case PermissionDecisionApproveForLocationApprovalKindExtensionEnvAccess: + var d PermissionDecisionApproveForLocationApprovalExtensionEnvAccess + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil case PermissionDecisionApproveForLocationApprovalKindExtensionManagement: var d PermissionDecisionApproveForLocationApprovalExtensionManagement if err := json.Unmarshal(data, &d); err != nil { @@ -2222,6 +3820,17 @@ func (r PermissionDecisionApproveForLocationApprovalCustomTool) MarshalJSON() ([ }) } +func (r PermissionDecisionApproveForLocationApprovalExtensionEnvAccess) MarshalJSON() ([]byte, error) { + type alias PermissionDecisionApproveForLocationApprovalExtensionEnvAccess + return json.Marshal(struct { + Kind PermissionDecisionApproveForLocationApprovalKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + func (r PermissionDecisionApproveForLocationApprovalExtensionManagement) MarshalJSON() ([]byte, error) { type alias PermissionDecisionApproveForLocationApprovalExtensionManagement return json.Marshal(struct { @@ -2366,6 +3975,12 @@ func unmarshalPermissionDecisionApproveForSessionApproval(data []byte) (Permissi return nil, err } return &d, nil + case PermissionDecisionApproveForSessionApprovalKindExtensionEnvAccess: + var d PermissionDecisionApproveForSessionApprovalExtensionEnvAccess + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil case PermissionDecisionApproveForSessionApprovalKindExtensionManagement: var d PermissionDecisionApproveForSessionApprovalExtensionManagement if err := json.Unmarshal(data, &d); err != nil { @@ -2452,6 +4067,17 @@ func (r PermissionDecisionApproveForSessionApprovalCustomTool) MarshalJSON() ([] }) } +func (r PermissionDecisionApproveForSessionApprovalExtensionEnvAccess) MarshalJSON() ([]byte, error) { + type alias PermissionDecisionApproveForSessionApprovalExtensionEnvAccess + return json.Marshal(struct { + Kind PermissionDecisionApproveForSessionApprovalKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + func (r PermissionDecisionApproveForSessionApprovalExtensionManagement) MarshalJSON() ([]byte, error) { type alias PermissionDecisionApproveForSessionApprovalExtensionManagement return json.Marshal(struct { @@ -2683,13 +4309,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) @@ -2726,6 +4354,12 @@ func unmarshalPermissionsLocationsAddToolApprovalDetails(data []byte) (Permissio return nil, err } return &d, nil + case PermissionsLocationsAddToolApprovalDetailsKindExtensionEnvAccess: + var d PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil case PermissionsLocationsAddToolApprovalDetailsKindExtensionManagement: var d PermissionsLocationsAddToolApprovalDetailsExtensionManagement if err := json.Unmarshal(data, &d); err != nil { @@ -2812,6 +4446,17 @@ func (r PermissionsLocationsAddToolApprovalDetailsCustomTool) MarshalJSON() ([]b }) } +func (r PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess) MarshalJSON() ([]byte, error) { + type alias PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess + return json.Marshal(struct { + Kind PermissionsLocationsAddToolApprovalDetailsKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + func (r PermissionsLocationsAddToolApprovalDetailsExtensionManagement) MarshalJSON() ([]byte, error) { type alias PermissionsLocationsAddToolApprovalDetailsExtensionManagement return json.Marshal(struct { @@ -3501,6 +5146,44 @@ func (r *SendRequest) UnmarshalJSON(data []byte) error { return nil } +func (r *SessionAuthLogoutUserRequest) UnmarshalJSON(data []byte) error { + type rawSessionAuthLogoutUserRequest struct { + AuthInfo json.RawMessage `json:"authInfo"` + } + var raw rawSessionAuthLogoutUserRequest + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + if raw.AuthInfo != nil { + value, err := unmarshalAuthInfo(raw.AuthInfo) + if err != nil { + return err + } + r.AuthInfo = value + } + return nil +} + +func (r *SessionAuthSwitchRequest) UnmarshalJSON(data []byte) error { + type rawSessionAuthSwitchRequest struct { + AuthInfo json.RawMessage `json:"authInfo"` + Token *string `json:"token,omitempty"` + } + var raw rawSessionAuthSwitchRequest + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + if raw.AuthInfo != nil { + value, err := unmarshalAuthInfo(raw.AuthInfo) + if err != nil { + return err + } + r.AuthInfo = value + } + r.Token = raw.Token + return nil +} + func (r SessionInstalledPluginSource) MarshalJSON() ([]byte, error) { if r.SessionInstalledPluginSourceGitHub != nil { return json.Marshal(r.SessionInstalledPluginSourceGitHub) @@ -3711,12 +5394,14 @@ 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"` + EnableSkills *bool `json:"enableSkills,omitempty"` EnableStreaming *bool `json:"enableStreaming,omitempty"` EnvValueMode *SessionOpenOptionsEnvValueMode `json:"envValueMode,omitempty"` EventsLogDirectory *string `json:"eventsLogDirectory,omitempty"` @@ -3725,12 +5410,15 @@ func (r *SessionOpenOptions) UnmarshalJSON(data []byte) error { ExcludedTools []string `json:"excludedTools,omitzero"` ExpAssignments any `json:"expAssignments,omitempty"` FeatureFlags map[string]bool `json:"featureFlags,omitzero"` + HasSkillProvider *bool `json:"hasSkillProvider,omitempty"` IncludedBuiltinAgents []string `json:"includedBuiltinAgents,omitzero"` + IncludedBuiltinSkills []string `json:"includedBuiltinSkills,omitzero"` InstalledPlugins []InstalledPlugin `json:"installedPlugins,omitzero"` IntegrationID *string `json:"integrationId,omitempty"` 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"` @@ -3746,6 +5434,7 @@ func (r *SessionOpenOptions) UnmarshalJSON(data []byte) error { RemoteSteerable *bool `json:"remoteSteerable,omitempty"` RunningInInteractiveMode *bool `json:"runningInInteractiveMode,omitempty"` SandboxConfig *SandboxConfig `json:"sandboxConfig,omitempty"` + SandboxConfigSource *SandboxConfigSource `json:"sandboxConfigSource,omitempty"` SessionCapabilities []SessionCapability `json:"sessionCapabilities,omitzero"` SessionID *string `json:"sessionId,omitempty"` SessionLimits *SessionLimitsConfig `json:"sessionLimits,omitempty"` @@ -3787,12 +5476,14 @@ 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.EnableSkills = raw.EnableSkills r.EnableStreaming = raw.EnableStreaming r.EnvValueMode = raw.EnvValueMode r.EventsLogDirectory = raw.EventsLogDirectory @@ -3801,12 +5492,15 @@ func (r *SessionOpenOptions) UnmarshalJSON(data []byte) error { r.ExcludedTools = raw.ExcludedTools r.ExpAssignments = raw.ExpAssignments r.FeatureFlags = raw.FeatureFlags + r.HasSkillProvider = raw.HasSkillProvider r.IncludedBuiltinAgents = raw.IncludedBuiltinAgents + r.IncludedBuiltinSkills = raw.IncludedBuiltinSkills r.InstalledPlugins = raw.InstalledPlugins r.IntegrationID = raw.IntegrationID 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 @@ -3822,6 +5516,7 @@ func (r *SessionOpenOptions) UnmarshalJSON(data []byte) error { r.RemoteSteerable = raw.RemoteSteerable r.RunningInInteractiveMode = raw.RunningInInteractiveMode r.SandboxConfig = raw.SandboxConfig + r.SandboxConfigSource = raw.SandboxConfigSource r.SessionCapabilities = raw.SessionCapabilities r.SessionID = raw.SessionID r.SessionLimits = raw.SessionLimits @@ -3985,6 +5680,88 @@ func (r SessionsOpenResumeLast) MarshalJSON() ([]byte, error) { }) } +func unmarshalSettableAuthInfo(data []byte) (SettableAuthInfo, error) { + if string(data) == "null" { + return nil, nil + } + type rawUnion struct { + Type SettableAuthInfoType `json:"type"` + } + var raw rawUnion + if err := json.Unmarshal(data, &raw); err != nil { + return nil, err + } + + switch raw.Type { + case SettableAuthInfoTypeAPIKey: + var d APIKeyAuthInfo + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case SettableAuthInfoTypeCopilotAPIToken: + var d CopilotAPITokenAuthInfo + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case SettableAuthInfoTypeEnv: + var d EnvAuthInfo + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case SettableAuthInfoTypeGhCLI: + var d GhCLIAuthInfo + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case SettableAuthInfoTypeHMAC: + var d HMACAuthInfo + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case SettableAuthInfoTypeToken: + var d SettableTokenAuthInfo + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case SettableAuthInfoTypeUser: + var d UserAuthInfo + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + default: + return &RawSettableAuthInfoData{Discriminator: raw.Type, Raw: data}, nil + } +} + +func (r RawSettableAuthInfoData) MarshalJSON() ([]byte, error) { + if r.Raw != nil { + return r.Raw, nil + } + return json.Marshal(struct { + Type SettableAuthInfoType `json:"type"` + }{ + Type: r.Discriminator, + }) +} + +func (r SettableTokenAuthInfo) MarshalJSON() ([]byte, error) { + type alias SettableTokenAuthInfo + return json.Marshal(struct { + Type SettableAuthInfoType `json:"type"` + alias + }{ + Type: r.settableAuthInfoType(), + alias: alias(r), + }) +} + func (r *SessionSetCredentialsParams) UnmarshalJSON(data []byte) error { type rawSessionSetCredentialsParams struct { Credentials json.RawMessage `json:"credentials,omitempty"` @@ -3994,7 +5771,7 @@ func (r *SessionSetCredentialsParams) UnmarshalJSON(data []byte) error { return err } if raw.Credentials != nil { - value, err := unmarshalAuthInfo(raw.Credentials) + value, err := unmarshalSettableAuthInfo(raw.Credentials) if err != nil { return err } @@ -4016,6 +5793,12 @@ func unmarshalSlashCommandInvocationResult(data []byte) (SlashCommandInvocationR } switch raw.Kind { + case SlashCommandInvocationResultKindAddTimelineEntry: + var d SlashCommandAddTimelineEntryResult + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil case SlashCommandInvocationResultKindAgentPrompt: var d SlashCommandAgentPromptResult if err := json.Unmarshal(data, &d); err != nil { @@ -4034,6 +5817,24 @@ func unmarshalSlashCommandInvocationResult(data []byte) (SlashCommandInvocationR return nil, err } return &d, nil + case SlashCommandInvocationResultKindSetModel: + var d SlashCommandSetModelResult + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case SlashCommandInvocationResultKindSetPlanModel: + var d SlashCommandSetPlanModelResult + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case SlashCommandInvocationResultKindShowDialog: + var d SlashCommandShowDialogResult + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil case SlashCommandInvocationResultKindText: var d SlashCommandTextResult if err := json.Unmarshal(data, &d); err != nil { @@ -4056,6 +5857,17 @@ func (r RawSlashCommandInvocationResultData) MarshalJSON() ([]byte, error) { }) } +func (r SlashCommandAddTimelineEntryResult) MarshalJSON() ([]byte, error) { + type alias SlashCommandAddTimelineEntryResult + return json.Marshal(struct { + Kind SlashCommandInvocationResultKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + func (r SlashCommandAgentPromptResult) MarshalJSON() ([]byte, error) { type alias SlashCommandAgentPromptResult return json.Marshal(struct { @@ -4089,6 +5901,39 @@ func (r SlashCommandSelectSubcommandResult) MarshalJSON() ([]byte, error) { }) } +func (r SlashCommandSetModelResult) MarshalJSON() ([]byte, error) { + type alias SlashCommandSetModelResult + return json.Marshal(struct { + Kind SlashCommandInvocationResultKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + +func (r SlashCommandSetPlanModelResult) MarshalJSON() ([]byte, error) { + type alias SlashCommandSetPlanModelResult + return json.Marshal(struct { + Kind SlashCommandInvocationResultKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + +func (r SlashCommandShowDialogResult) MarshalJSON() ([]byte, error) { + type alias SlashCommandShowDialogResult + return json.Marshal(struct { + Kind SlashCommandInvocationResultKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + func (r SlashCommandTextResult) MarshalJSON() ([]byte, error) { type alias SlashCommandTextResult return json.Marshal(struct { @@ -4301,6 +6146,78 @@ func (r *TasksPromoteCurrentToBackgroundResult) UnmarshalJSON(data []byte) error return nil } +func (r *ToolResultExpanded) UnmarshalJSON(data []byte) error { + type rawToolResultExpanded struct { + BinaryResultsForLlm []ExternalToolTextResultForLlmBinaryResultsForLlm `json:"binaryResultsForLlm,omitzero"` + CitableSources []any `json:"citableSources,omitzero"` + Contents []json.RawMessage `json:"contents,omitzero"` + Error *string `json:"error,omitempty"` + MCPMeta map[string]any `json:"mcpMeta,omitzero"` + NewMessages []ToolResultNewMessage `json:"newMessages,omitzero"` + PostToolUseFailureHooksProcessed *bool `json:"postToolUseFailureHooksProcessed,omitempty"` + ResultType ToolResultType `json:"resultType"` + SessionLog *string `json:"sessionLog,omitempty"` + SkillInvocation any `json:"skillInvocation,omitempty"` + SkipLargeOutputProcessing *bool `json:"skipLargeOutputProcessing,omitempty"` + StructuredContent any `json:"structuredContent,omitempty"` + TaskCompletionDecision *TaskCompletionDecision `json:"taskCompletionDecision,omitempty"` + TextResultForLlm string `json:"textResultForLlm"` + ToolReferences []string `json:"toolReferences,omitzero"` + ToolTelemetry any `json:"toolTelemetry,omitempty"` + UIResource any `json:"uiResource,omitempty"` + } + var raw rawToolResultExpanded + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + r.BinaryResultsForLlm = raw.BinaryResultsForLlm + r.CitableSources = raw.CitableSources + if raw.Contents != nil { + r.Contents = make([]ExternalToolTextResultForLlmContent, 0, len(raw.Contents)) + for _, rawItem := range raw.Contents { + value, err := unmarshalExternalToolTextResultForLlmContent(rawItem) + if err != nil { + return err + } + r.Contents = append(r.Contents, value) + } + } + r.Error = raw.Error + r.MCPMeta = raw.MCPMeta + r.NewMessages = raw.NewMessages + r.PostToolUseFailureHooksProcessed = raw.PostToolUseFailureHooksProcessed + r.ResultType = raw.ResultType + r.SessionLog = raw.SessionLog + r.SkillInvocation = raw.SkillInvocation + r.SkipLargeOutputProcessing = raw.SkipLargeOutputProcessing + r.StructuredContent = raw.StructuredContent + r.TaskCompletionDecision = raw.TaskCompletionDecision + r.TextResultForLlm = raw.TextResultForLlm + r.ToolReferences = raw.ToolReferences + r.ToolTelemetry = raw.ToolTelemetry + r.UIResource = raw.UIResource + return nil +} + +func unmarshalToolResult(data []byte) (ToolResult, error) { + if string(data) == "null" { + return nil, nil + } + { + var value string + if err := json.Unmarshal(data, &value); err == nil { + return ToolStringResult(value), nil + } + } + { + var value ToolResultExpanded + if err := json.Unmarshal(data, &value); err == nil { + return &value, nil + } + } + return nil, errors.New("data did not match any union variant for ToolResult") +} + func unmarshalUIElicitationFieldValue(data []byte) (UIElicitationFieldValue, error) { if string(data) == "null" { return nil, nil @@ -4332,7 +6249,7 @@ func unmarshalUIElicitationFieldValue(data []byte) (UIElicitationFieldValue, err return nil, errors.New("data did not match any union variant for UIElicitationFieldValue") } -func matchesUIElicitationArrayAnyOfField(data []byte) bool { +func matchesUIElicitationSchemaPropertyUIElicitationArrayAnyOfField(data []byte) bool { var rawGroup0 struct { Items json.RawMessage `json:"items"` } @@ -4359,7 +6276,7 @@ func matchesUIElicitationArrayAnyOfField(data []byte) bool { return rawGroup0Items.Type == nil } -func matchesUIElicitationArrayEnumField(data []byte) bool { +func matchesUIElicitationSchemaPropertyUIElicitationArrayEnumField(data []byte) bool { var rawGroup0 struct { Items json.RawMessage `json:"items"` } @@ -4409,7 +6326,7 @@ func matchesUIElicitationSchemaPropertyString(data []byte) bool { return rawGroup0.OneOf == nil } -func matchesUIElicitationStringEnumField(data []byte) bool { +func matchesUIElicitationSchemaPropertyUIElicitationStringEnumField(data []byte) bool { var rawGroup0 struct { Enum json.RawMessage `json:"enum"` OneOf json.RawMessage `json:"oneOf"` @@ -4423,7 +6340,7 @@ func matchesUIElicitationStringEnumField(data []byte) bool { return rawGroup0.OneOf == nil } -func matchesUIElicitationStringOneOfField(data []byte) bool { +func matchesUIElicitationSchemaPropertyUIElicitationStringOneOfField(data []byte) bool { var rawGroup0 struct { Enum json.RawMessage `json:"enum"` OneOf json.RawMessage `json:"oneOf"` @@ -4451,14 +6368,14 @@ func unmarshalUIElicitationSchemaProperty(data []byte) (UIElicitationSchemaPrope switch raw.Type { case UIElicitationSchemaPropertyTypeArray: - if matchesUIElicitationArrayAnyOfField(data) { + if matchesUIElicitationSchemaPropertyUIElicitationArrayAnyOfField(data) { var d UIElicitationArrayAnyOfField if err := json.Unmarshal(data, &d); err != nil { return nil, err } return &d, nil } - if matchesUIElicitationArrayEnumField(data) { + if matchesUIElicitationSchemaPropertyUIElicitationArrayEnumField(data) { var d UIElicitationArrayEnumField if err := json.Unmarshal(data, &d); err != nil { return nil, err @@ -4492,14 +6409,14 @@ func unmarshalUIElicitationSchemaProperty(data []byte) (UIElicitationSchemaPrope } return &d, nil } - if matchesUIElicitationStringEnumField(data) { + if matchesUIElicitationSchemaPropertyUIElicitationStringEnumField(data) { var d UIElicitationStringEnumField if err := json.Unmarshal(data, &d); err != nil { return nil, err } return &d, nil } - if matchesUIElicitationStringOneOfField(data) { + if matchesUIElicitationSchemaPropertyUIElicitationStringOneOfField(data) { var d UIElicitationStringOneOfField if err := json.Unmarshal(data, &d); err != nil { return nil, err @@ -4629,6 +6546,7 @@ func (r *UIElicitationResponse) UnmarshalJSON(data []byte) error { type rawUIElicitationResponse struct { Action UIElicitationResponseAction `json:"action"` Content map[string]json.RawMessage `json:"content,omitzero"` + Meta map[string]any `json:"_meta,omitzero"` } var raw rawUIElicitationResponse if err := json.Unmarshal(data, &raw); err != nil { @@ -4645,5 +6563,6 @@ func (r *UIElicitationResponse) UnmarshalJSON(data []byte) error { r.Content[key] = value } } + r.Meta = raw.Meta return nil } diff --git a/go/rpc/zsession_encoding.go b/go/rpc/zsession_encoding.go index 05e466012c..bbe9fbc74b 100644 --- a/go/rpc/zsession_encoding.go +++ b/go/rpc/zsession_encoding.go @@ -41,6 +41,36 @@ func (e *SessionEvent) UnmarshalJSON(data []byte) error { return err } e.Data = &d + case SessionEventTypeAgentInterrupted: + var d AgentInterruptedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeAssistantFusionPhaseActivity: + var d AssistantFusionPhaseActivityData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeAssistantFusionPhaseCompleted: + var d AssistantFusionPhaseCompletedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeAssistantFusionPhaseFailed: + var d AssistantFusionPhaseFailedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeAssistantFusionPhaseStarted: + var d AssistantFusionPhaseStartedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d case SessionEventTypeAssistantIdle: var d AssistantIdleData if err := json.Unmarshal(raw.Data, &d); err != nil { @@ -203,6 +233,18 @@ func (e *SessionEvent) UnmarshalJSON(data []byte) error { return err } e.Data = &d + case SessionEventTypeFactoryRunSettled: + var d FactoryRunSettledData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeFactoryRunStarted: + var d FactoryRunStartedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d case SessionEventTypeFactoryRunUpdated: var d FactoryRunUpdatedData if err := json.Unmarshal(raw.Data, &d); err != nil { @@ -281,6 +323,12 @@ func (e *SessionEvent) UnmarshalJSON(data []byte) error { return err } e.Data = &d + case SessionEventTypeModelCallFinished: + var d ModelCallFinishedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d case SessionEventTypeModelCallStart: var d ModelCallStartData if err := json.Unmarshal(raw.Data, &d); err != nil { @@ -305,6 +353,12 @@ func (e *SessionEvent) UnmarshalJSON(data []byte) error { return err } e.Data = &d + case SessionEventTypePromptCacheBreak: + var d PromptCacheBreakData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d case SessionEventTypeSamplingCompleted: var d SamplingCompletedData if err := json.Unmarshal(raw.Data, &d); err != nil { @@ -317,6 +371,12 @@ func (e *SessionEvent) UnmarshalJSON(data []byte) error { return err } e.Data = &d + case SessionEventTypeSandboxDecision: + var d SandboxDecisionData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d case SessionEventTypeSessionAutoModeResolved: var d SessionAutoModeResolvedData if err := json.Unmarshal(raw.Data, &d); err != nil { @@ -389,6 +449,12 @@ func (e *SessionEvent) UnmarshalJSON(data []byte) error { return err } e.Data = &d + case SessionEventTypeSessionCompletionReceipt: + var d SessionCompletionReceiptData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d case SessionEventTypeSessionContextChanged: var d SessionContextChangedData if err := json.Unmarshal(raw.Data, &d); err != nil { @@ -431,6 +497,30 @@ func (e *SessionEvent) UnmarshalJSON(data []byte) error { return err } e.Data = &d + case SessionEventTypeSessionFusionCompleted: + var d SessionFusionCompletedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionFusionResolved: + var d SessionFusionResolvedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionFusionRouteFailed: + var d SessionFusionRouteFailedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionFusionRouteStarted: + var d SessionFusionRouteStartedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d case SessionEventTypeSessionHandoff: var d SessionHandoffData if err := json.Unmarshal(raw.Data, &d); err != nil { @@ -497,6 +587,12 @@ func (e *SessionEvent) UnmarshalJSON(data []byte) error { return err } e.Data = &d + case SessionEventTypeSessionModeNoticeDelivered: + var d SessionModeNoticeDeliveredData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d case SessionEventTypeSessionPermissionsChanged: var d SessionPermissionsChangedData if err := json.Unmarshal(raw.Data, &d); err != nil { @@ -635,6 +731,12 @@ func (e *SessionEvent) UnmarshalJSON(data []byte) error { return err } e.Data = &d + case SessionEventTypeSubagentConfigured: + var d SubagentConfiguredData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d case SessionEventTypeSubagentDeselected: var d SubagentDeselectedData if err := json.Unmarshal(raw.Data, &d); err != nil { @@ -707,6 +809,12 @@ func (e *SessionEvent) UnmarshalJSON(data []byte) error { return err } e.Data = &d + case SessionEventTypeUIEphemeralQuery: + var d UIEphemeralQueryData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d case SessionEventTypeUserInputCompleted: var d UserInputCompletedData if err := json.Unmarshal(raw.Data, &d); err != nil { @@ -768,11 +876,13 @@ func (r *UserMessageData) UnmarshalJSON(data []byte) error { Delivery *UserMessageDelivery `json:"delivery,omitempty"` InteractionID *string `json:"interactionId,omitempty"` IsAutopilotContinuation *bool `json:"isAutopilotContinuation,omitempty"` + MessageID *string `json:"messageId,omitempty"` NativeDocumentPathFallbackPaths []string `json:"nativeDocumentPathFallbackPaths,omitzero"` ParentAgentTaskID *string `json:"parentAgentTaskId,omitempty"` Source *string `json:"source,omitempty"` SupportedNativeDocumentMIMETypes []string `json:"supportedNativeDocumentMimeTypes,omitzero"` TransformedContent *string `json:"transformedContent,omitempty"` + TurnID *string `json:"turnId,omitempty"` } var raw rawUserMessageData if err := json.Unmarshal(data, &raw); err != nil { @@ -793,11 +903,13 @@ func (r *UserMessageData) UnmarshalJSON(data []byte) error { r.Delivery = raw.Delivery r.InteractionID = raw.InteractionID r.IsAutopilotContinuation = raw.IsAutopilotContinuation + r.MessageID = raw.MessageID r.NativeDocumentPathFallbackPaths = raw.NativeDocumentPathFallbackPaths r.ParentAgentTaskID = raw.ParentAgentTaskID r.Source = raw.Source r.SupportedNativeDocumentMIMETypes = raw.SupportedNativeDocumentMIMETypes r.TransformedContent = raw.TransformedContent + r.TurnID = raw.TurnID return nil } @@ -905,7 +1017,7 @@ func (r *CitationReference) UnmarshalJSON(data []byte) error { return nil } -func matchesBinaryAssetReference(data []byte) bool { +func matchesPersistedBinaryResultBinaryAssetReference(data []byte) bool { var rawGroup0 struct { AssetID json.RawMessage `json:"assetId"` ByteLength json.RawMessage `json:"byteLength"` @@ -927,7 +1039,7 @@ func matchesBinaryAssetReference(data []byte) bool { return rawGroup0.OmittedReason == nil } -func matchesOmittedBinaryResult(data []byte) bool { +func matchesPersistedBinaryResultOmittedBinaryResult(data []byte) bool { var rawGroup0 struct { AssetID json.RawMessage `json:"assetId"` ByteLength json.RawMessage `json:"byteLength"` @@ -949,7 +1061,7 @@ func matchesOmittedBinaryResult(data []byte) bool { return rawGroup0.Data == nil } -func matchesPersistedBinaryImage(data []byte) bool { +func matchesPersistedBinaryResultPersistedBinaryImage(data []byte) bool { var rawGroup0 struct { AssetID json.RawMessage `json:"assetId"` ByteLength json.RawMessage `json:"byteLength"` @@ -985,21 +1097,21 @@ func unmarshalPersistedBinaryResult(data []byte) (PersistedBinaryResult, error) switch raw.Type { case PersistedBinaryResultTypeImage: - if matchesBinaryAssetReference(data) { + if matchesPersistedBinaryResultBinaryAssetReference(data) { var d BinaryAssetReference if err := json.Unmarshal(data, &d); err != nil { return nil, err } return &d, nil } - if matchesOmittedBinaryResult(data) { + if matchesPersistedBinaryResultOmittedBinaryResult(data) { var d OmittedBinaryResult if err := json.Unmarshal(data, &d); err != nil { return nil, err } return &d, nil } - if matchesPersistedBinaryImage(data) { + if matchesPersistedBinaryResultPersistedBinaryImage(data) { var d PersistedBinaryImage if err := json.Unmarshal(data, &d); err != nil { return nil, err @@ -1008,21 +1120,21 @@ func unmarshalPersistedBinaryResult(data []byte) (PersistedBinaryResult, error) } return &RawPersistedBinaryResult{Discriminator: raw.Type, Raw: data}, nil case PersistedBinaryResultTypeResource: - if matchesBinaryAssetReference(data) { + if matchesPersistedBinaryResultBinaryAssetReference(data) { var d BinaryAssetReference if err := json.Unmarshal(data, &d); err != nil { return nil, err } return &d, nil } - if matchesOmittedBinaryResult(data) { + if matchesPersistedBinaryResultOmittedBinaryResult(data) { var d OmittedBinaryResult if err := json.Unmarshal(data, &d); err != nil { return nil, err } return &d, nil } - if matchesPersistedBinaryImage(data) { + if matchesPersistedBinaryResultPersistedBinaryImage(data) { var d PersistedBinaryImage if err := json.Unmarshal(data, &d); err != nil { return nil, err @@ -1537,6 +1649,12 @@ func unmarshalPermissionRequest(data []byte) (PermissionRequest, error) { return nil, err } return &d, nil + case PermissionRequestKindExtensionEnvAccess: + var d PermissionRequestExtensionEnvAccess + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil case PermissionRequestKindExtensionManagement: var d PermissionRequestExtensionManagement if err := json.Unmarshal(data, &d); err != nil { @@ -1624,6 +1742,17 @@ func (r PermissionRequestCustomTool) MarshalJSON() ([]byte, error) { }) } +func (r PermissionRequestExtensionEnvAccess) MarshalJSON() ([]byte, error) { + type alias PermissionRequestExtensionEnvAccess + return json.Marshal(struct { + Kind PermissionRequestKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + func (r PermissionRequestExtensionManagement) MarshalJSON() ([]byte, error) { type alias PermissionRequestExtensionManagement return json.Marshal(struct { @@ -1759,6 +1888,12 @@ func unmarshalPermissionPromptRequest(data []byte) (PermissionPromptRequest, err return nil, err } return &d, nil + case PermissionPromptRequestKindExtensionEnvAccess: + var d PermissionPromptRequestExtensionEnvAccess + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil case PermissionPromptRequestKindExtensionManagement: var d PermissionPromptRequestExtensionManagement if err := json.Unmarshal(data, &d); err != nil { @@ -1857,6 +1992,17 @@ func (r PermissionPromptRequestCustomTool) MarshalJSON() ([]byte, error) { }) } +func (r PermissionPromptRequestExtensionEnvAccess) MarshalJSON() ([]byte, error) { + type alias PermissionPromptRequestExtensionEnvAccess + return json.Marshal(struct { + Kind PermissionPromptRequestKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + func (r PermissionPromptRequestExtensionManagement) MarshalJSON() ([]byte, error) { type alias PermissionPromptRequestExtensionManagement return json.Marshal(struct { @@ -1969,6 +2115,7 @@ func (r PermissionPromptRequestWrite) MarshalJSON() ([]byte, error) { func (r *PermissionRequestedData) UnmarshalJSON(data []byte) error { type rawPermissionRequestedData struct { + AgentMode *SessionMode `json:"agentMode,omitempty"` PermissionRequest json.RawMessage `json:"permissionRequest"` PromptRequest json.RawMessage `json:"promptRequest,omitempty"` RequestID string `json:"requestId"` @@ -1979,6 +2126,7 @@ func (r *PermissionRequestedData) UnmarshalJSON(data []byte) error { if err := json.Unmarshal(data, &raw); err != nil { return err } + r.AgentMode = raw.AgentMode if raw.PermissionRequest != nil { value, err := unmarshalPermissionRequest(raw.PermissionRequest) if err != nil { @@ -2095,8 +2243,9 @@ func (r PermissionApproved) MarshalJSON() ([]byte, error) { func (r *PermissionApprovedForLocation) UnmarshalJSON(data []byte) error { type rawPermissionApprovedForLocation struct { - Approval json.RawMessage `json:"approval"` - LocationKey string `json:"locationKey"` + Approval json.RawMessage `json:"approval"` + LocationKey string `json:"locationKey"` + ManagedApprovalHandled *bool `json:"managedApprovalHandled,omitempty"` } var raw rawPermissionApprovedForLocation if err := json.Unmarshal(data, &raw); err != nil { @@ -2110,6 +2259,7 @@ func (r *PermissionApprovedForLocation) UnmarshalJSON(data []byte) error { r.Approval = value } r.LocationKey = raw.LocationKey + r.ManagedApprovalHandled = raw.ManagedApprovalHandled return nil } @@ -2126,7 +2276,8 @@ func (r PermissionApprovedForLocation) MarshalJSON() ([]byte, error) { func (r *PermissionApprovedForSession) UnmarshalJSON(data []byte) error { type rawPermissionApprovedForSession struct { - Approval json.RawMessage `json:"approval"` + Approval json.RawMessage `json:"approval"` + ManagedApprovalHandled *bool `json:"managedApprovalHandled,omitempty"` } var raw rawPermissionApprovedForSession if err := json.Unmarshal(data, &raw); err != nil { @@ -2139,6 +2290,7 @@ func (r *PermissionApprovedForSession) UnmarshalJSON(data []byte) error { } r.Approval = value } + r.ManagedApprovalHandled = raw.ManagedApprovalHandled return nil } diff --git a/go/rpc/zsession_events.go b/go/rpc/zsession_events.go index 26e7b5f726..63709921d8 100644 --- a/go/rpc/zsession_events.go +++ b/go/rpc/zsession_events.go @@ -53,7 +53,20 @@ func (r RawSessionEventData) Type() SessionEventType { type SessionEventType string const ( - SessionEventTypeAbort SessionEventType = "abort" + SessionEventTypeAbort SessionEventType = "abort" + SessionEventTypeAgentInterrupted SessionEventType = "agent.interrupted" + // Experimental: SessionEventTypeAssistantFusionPhaseActivity identifies an experimental + // event that may change or be removed. + SessionEventTypeAssistantFusionPhaseActivity SessionEventType = "assistant.fusion_phase_activity" + // Experimental: SessionEventTypeAssistantFusionPhaseCompleted identifies an experimental + // event that may change or be removed. + SessionEventTypeAssistantFusionPhaseCompleted SessionEventType = "assistant.fusion_phase_completed" + // Experimental: SessionEventTypeAssistantFusionPhaseFailed identifies an experimental event + // that may change or be removed. + SessionEventTypeAssistantFusionPhaseFailed SessionEventType = "assistant.fusion_phase_failed" + // Experimental: SessionEventTypeAssistantFusionPhaseStarted identifies an experimental + // event that may change or be removed. + SessionEventTypeAssistantFusionPhaseStarted SessionEventType = "assistant.fusion_phase_started" SessionEventTypeAssistantIdle SessionEventType = "assistant.idle" SessionEventTypeAssistantIntent SessionEventType = "assistant.intent" SessionEventTypeAssistantMessage SessionEventType = "assistant.message" @@ -81,6 +94,12 @@ const ( SessionEventTypeExitPlanModeRequested SessionEventType = "exit_plan_mode.requested" SessionEventTypeExternalToolCompleted SessionEventType = "external_tool.completed" SessionEventTypeExternalToolRequested SessionEventType = "external_tool.requested" + // Experimental: SessionEventTypeFactoryRunSettled identifies an experimental event that may + // change or be removed. + SessionEventTypeFactoryRunSettled SessionEventType = "factory.run_settled" + // Experimental: SessionEventTypeFactoryRunStarted identifies an experimental event that may + // change or be removed. + SessionEventTypeFactoryRunStarted SessionEventType = "factory.run_started" // Experimental: SessionEventTypeFactoryRunUpdated identifies an experimental event that may // change or be removed. SessionEventTypeFactoryRunUpdated SessionEventType = "factory.run_updated" @@ -96,12 +115,15 @@ const ( SessionEventTypeMCPResourcesListChanged SessionEventType = "mcp.resources.list_changed" SessionEventTypeMCPToolsListChanged SessionEventType = "mcp.tools.list_changed" SessionEventTypeModelCallFailure SessionEventType = "model.call_failure" + SessionEventTypeModelCallFinished SessionEventType = "model.call_finished" SessionEventTypeModelCallStart SessionEventType = "model.call_start" SessionEventTypePendingMessagesModified SessionEventType = "pending_messages.modified" SessionEventTypePermissionCompleted SessionEventType = "permission.completed" SessionEventTypePermissionRequested SessionEventType = "permission.requested" + SessionEventTypePromptCacheBreak SessionEventType = "prompt_cache_break" SessionEventTypeSamplingCompleted SessionEventType = "sampling.completed" SessionEventTypeSamplingRequested SessionEventType = "sampling.requested" + SessionEventTypeSandboxDecision SessionEventType = "sandbox.decision" // Experimental: SessionEventTypeSessionAutoModeResolved identifies an experimental event // that may change or be removed. SessionEventTypeSessionAutoModeResolved SessionEventType = "session.auto_mode_resolved" @@ -127,9 +149,12 @@ const ( SessionEventTypeSessionCanvasRemoved SessionEventType = "session.canvas.removed" // Experimental: SessionEventTypeSessionCanvasUnavailable identifies an experimental event // that may change or be removed. - SessionEventTypeSessionCanvasUnavailable SessionEventType = "session.canvas.unavailable" - SessionEventTypeSessionCompactionComplete SessionEventType = "session.compaction_complete" - SessionEventTypeSessionCompactionStart SessionEventType = "session.compaction_start" + SessionEventTypeSessionCanvasUnavailable SessionEventType = "session.canvas.unavailable" + SessionEventTypeSessionCompactionComplete SessionEventType = "session.compaction_complete" + SessionEventTypeSessionCompactionStart SessionEventType = "session.compaction_start" + // Experimental: SessionEventTypeSessionCompletionReceipt identifies an experimental event + // that may change or be removed. + SessionEventTypeSessionCompletionReceipt SessionEventType = "session.completion_receipt" SessionEventTypeSessionContextChanged SessionEventType = "session.context_changed" SessionEventTypeSessionContextCleared SessionEventType = "session.context_cleared" SessionEventTypeSessionCustomAgentsUpdated SessionEventType = "session.custom_agents_updated" @@ -137,11 +162,23 @@ const ( SessionEventTypeSessionError SessionEventType = "session.error" SessionEventTypeSessionExtensionsAttachmentsPushed SessionEventType = "session.extensions.attachments_pushed" SessionEventTypeSessionExtensionsLoaded SessionEventType = "session.extensions_loaded" - SessionEventTypeSessionHandoff SessionEventType = "session.handoff" - SessionEventTypeSessionIdle SessionEventType = "session.idle" - SessionEventTypeSessionInfo SessionEventType = "session.info" - SessionEventTypeSessionLimitsExhaustedCompleted SessionEventType = "session_limits_exhausted.completed" - SessionEventTypeSessionLimitsExhaustedRequested SessionEventType = "session_limits_exhausted.requested" + // Experimental: SessionEventTypeSessionFusionCompleted identifies an experimental event + // that may change or be removed. + SessionEventTypeSessionFusionCompleted SessionEventType = "session.fusion_completed" + // Experimental: SessionEventTypeSessionFusionResolved identifies an experimental event that + // may change or be removed. + SessionEventTypeSessionFusionResolved SessionEventType = "session.fusion_resolved" + // Experimental: SessionEventTypeSessionFusionRouteFailed identifies an experimental event + // that may change or be removed. + SessionEventTypeSessionFusionRouteFailed SessionEventType = "session.fusion_route_failed" + // Experimental: SessionEventTypeSessionFusionRouteStarted identifies an experimental event + // that may change or be removed. + SessionEventTypeSessionFusionRouteStarted SessionEventType = "session.fusion_route_started" + SessionEventTypeSessionHandoff SessionEventType = "session.handoff" + SessionEventTypeSessionIdle SessionEventType = "session.idle" + SessionEventTypeSessionInfo SessionEventType = "session.info" + SessionEventTypeSessionLimitsExhaustedCompleted SessionEventType = "session_limits_exhausted.completed" + SessionEventTypeSessionLimitsExhaustedRequested SessionEventType = "session_limits_exhausted.requested" // Experimental: SessionEventTypeSessionManagedSettingsEnforced identifies an experimental // event that may change or be removed. SessionEventTypeSessionManagedSettingsEnforced SessionEventType = "session.managed_settings_enforced" @@ -152,46 +189,123 @@ const ( SessionEventTypeSessionMCPServerStatusChanged SessionEventType = "session.mcp_server_status_changed" SessionEventTypeSessionModeChanged SessionEventType = "session.mode_changed" SessionEventTypeSessionModelChange SessionEventType = "session.model_change" - SessionEventTypeSessionPermissionsChanged SessionEventType = "session.permissions_changed" - SessionEventTypeSessionPlanChanged SessionEventType = "session.plan_changed" - SessionEventTypeSessionRemoteSteerableChanged SessionEventType = "session.remote_steerable_changed" - SessionEventTypeSessionResume SessionEventType = "session.resume" - SessionEventTypeSessionScheduleCancelled SessionEventType = "session.schedule_cancelled" - SessionEventTypeSessionScheduleCreated SessionEventType = "session.schedule_created" - SessionEventTypeSessionScheduleRearmed SessionEventType = "session.schedule_rearmed" - SessionEventTypeSessionSessionLimitsChanged SessionEventType = "session.session_limits_changed" - SessionEventTypeSessionShutdown SessionEventType = "session.shutdown" - SessionEventTypeSessionSkillsLoaded SessionEventType = "session.skills_loaded" - SessionEventTypeSessionSnapshotRewind SessionEventType = "session.snapshot_rewind" - SessionEventTypeSessionStart SessionEventType = "session.start" - SessionEventTypeSessionTaskComplete SessionEventType = "session.task_complete" - SessionEventTypeSessionTitleChanged SessionEventType = "session.title_changed" - SessionEventTypeSessionTodosChanged SessionEventType = "session.todos_changed" - SessionEventTypeSessionToolsUpdated SessionEventType = "session.tools_updated" - SessionEventTypeSessionTruncation SessionEventType = "session.truncation" - SessionEventTypeSessionUsageCheckpoint SessionEventType = "session.usage_checkpoint" - SessionEventTypeSessionUsageInfo SessionEventType = "session.usage_info" - SessionEventTypeSessionWarning SessionEventType = "session.warning" - SessionEventTypeSessionWorkspaceFileChanged SessionEventType = "session.workspace_file_changed" - SessionEventTypeSkillInvoked SessionEventType = "skill.invoked" - SessionEventTypeSubagentCompleted SessionEventType = "subagent.completed" - SessionEventTypeSubagentDeselected SessionEventType = "subagent.deselected" - SessionEventTypeSubagentFailed SessionEventType = "subagent.failed" - SessionEventTypeSubagentSelected SessionEventType = "subagent.selected" - SessionEventTypeSubagentStarted SessionEventType = "subagent.started" - SessionEventTypeSystemMessage SessionEventType = "system.message" - SessionEventTypeSystemNotification SessionEventType = "system.notification" - SessionEventTypeToolExecutionComplete SessionEventType = "tool.execution_complete" - SessionEventTypeToolExecutionPartialResult SessionEventType = "tool.execution_partial_result" - SessionEventTypeToolExecutionProgress SessionEventType = "tool.execution_progress" - SessionEventTypeToolExecutionStart SessionEventType = "tool.execution_start" - SessionEventTypeToolSearchActivated SessionEventType = "tool_search.activated" - SessionEventTypeToolUserRequested SessionEventType = "tool.user_requested" - SessionEventTypeUserInputCompleted SessionEventType = "user_input.completed" - SessionEventTypeUserInputRequested SessionEventType = "user_input.requested" - SessionEventTypeUserMessage SessionEventType = "user.message" + SessionEventTypeSessionModeNoticeDelivered SessionEventType = "session.mode_notice_delivered" + // Experimental: SessionEventTypeSessionPermissionsChanged identifies an experimental event + // that may change or be removed. + SessionEventTypeSessionPermissionsChanged SessionEventType = "session.permissions_changed" + SessionEventTypeSessionPlanChanged SessionEventType = "session.plan_changed" + SessionEventTypeSessionRemoteSteerableChanged SessionEventType = "session.remote_steerable_changed" + SessionEventTypeSessionResume SessionEventType = "session.resume" + SessionEventTypeSessionScheduleCancelled SessionEventType = "session.schedule_cancelled" + SessionEventTypeSessionScheduleCreated SessionEventType = "session.schedule_created" + SessionEventTypeSessionScheduleRearmed SessionEventType = "session.schedule_rearmed" + SessionEventTypeSessionSessionLimitsChanged SessionEventType = "session.session_limits_changed" + SessionEventTypeSessionShutdown SessionEventType = "session.shutdown" + SessionEventTypeSessionSkillsLoaded SessionEventType = "session.skills_loaded" + SessionEventTypeSessionSnapshotRewind SessionEventType = "session.snapshot_rewind" + SessionEventTypeSessionStart SessionEventType = "session.start" + SessionEventTypeSessionTaskComplete SessionEventType = "session.task_complete" + SessionEventTypeSessionTitleChanged SessionEventType = "session.title_changed" + SessionEventTypeSessionTodosChanged SessionEventType = "session.todos_changed" + SessionEventTypeSessionToolsUpdated SessionEventType = "session.tools_updated" + SessionEventTypeSessionTruncation SessionEventType = "session.truncation" + SessionEventTypeSessionUsageCheckpoint SessionEventType = "session.usage_checkpoint" + SessionEventTypeSessionUsageInfo SessionEventType = "session.usage_info" + SessionEventTypeSessionWarning SessionEventType = "session.warning" + SessionEventTypeSessionWorkspaceFileChanged SessionEventType = "session.workspace_file_changed" + SessionEventTypeSkillInvoked SessionEventType = "skill.invoked" + SessionEventTypeSubagentCompleted SessionEventType = "subagent.completed" + SessionEventTypeSubagentConfigured SessionEventType = "subagent.configured" + SessionEventTypeSubagentDeselected SessionEventType = "subagent.deselected" + SessionEventTypeSubagentFailed SessionEventType = "subagent.failed" + SessionEventTypeSubagentSelected SessionEventType = "subagent.selected" + SessionEventTypeSubagentStarted SessionEventType = "subagent.started" + SessionEventTypeSystemMessage SessionEventType = "system.message" + SessionEventTypeSystemNotification SessionEventType = "system.notification" + SessionEventTypeToolExecutionComplete SessionEventType = "tool.execution_complete" + SessionEventTypeToolExecutionPartialResult SessionEventType = "tool.execution_partial_result" + SessionEventTypeToolExecutionProgress SessionEventType = "tool.execution_progress" + SessionEventTypeToolExecutionStart SessionEventType = "tool.execution_start" + SessionEventTypeToolSearchActivated SessionEventType = "tool_search.activated" + SessionEventTypeToolUserRequested SessionEventType = "tool.user_requested" + // Experimental: SessionEventTypeUIEphemeralQuery identifies an experimental event that may + // change or be removed. + SessionEventTypeUIEphemeralQuery SessionEventType = "ui.ephemeral_query" + SessionEventTypeUserInputCompleted SessionEventType = "user_input.completed" + SessionEventTypeUserInputRequested SessionEventType = "user_input.requested" + SessionEventTypeUserMessage SessionEventType = "user.message" ) +// A detected loss of a previously cached prompt prefix +type PromptCacheBreakData struct { + // Request state whose cached prefix fell short + // Internal: AfterRequest is part of the SDK's internal API surface and is not intended for external use. + AfterRequest any `json:"afterRequest,omitempty"` + // Name of the sub-agent whose conversation broke, stamped by the parent bridge + // Internal: AgentName is part of the SDK's internal API surface and is not intended for external use. + AgentName *string `json:"agentName,omitempty"` + // Request state that established the prior cache frontier + // Internal: BeforeRequest is part of the SDK's internal API surface and is not intended for external use. + BeforeRequest any `json:"beforeRequest,omitempty"` + // Names of the cache-configuration fields that changed + // Internal: CacheConfigChangedFields is part of the SDK's internal API surface and is not intended for external use. + CacheConfigChangedFields []string `json:"cacheConfigChangedFields,omitzero"` + // All reasons that contributed to the cache break, ordered by precedence + ContributingReasons []string `json:"contributingReasons"` + // Prior cached prompt frontier in tokens + FrontierTokens int64 `json:"frontierTokens"` + // Model that held the prior cache frontier, when the call changed models + // Internal: ModelFrom is part of the SDK's internal API surface and is not intended for external use. + ModelFrom *string `json:"modelFrom,omitempty"` + // Model this call targeted, when the call changed models + // Internal: ModelTo is part of the SDK's internal API surface and is not intended for external use. + ModelTo *string `json:"modelTo,omitempty"` + // The highest-precedence reason for the cache break + PrimaryReason string `json:"primaryReason"` + // Fraction of the prior cache frontier that survived + RetentionRatio float64 `json:"retentionRatio"` + // Index of the first conversation message whose content changed + // Internal: RewriteMessageIndex is part of the SDK's internal API surface and is not intended for external use. + RewriteMessageIndex *int64 `json:"rewriteMessageIndex,omitempty"` + // Shape of the history rewrite, for example whether the history grew or shrank + // Internal: RewriteShape is part of the SDK's internal API surface and is not intended for external use. + RewriteShape *string `json:"rewriteShape,omitempty"` + // Subsystems that announced a history rewrite before this call, for example compaction or truncation + // Internal: RewriteSource is part of the SDK's internal API surface and is not intended for external use. + RewriteSource []string `json:"rewriteSource,omitzero"` + // Cached prefix tokens lost since the prior call + ShortfallTokens int64 `json:"shortfallTokens"` + // Number of cached prefix tokens that survived + SurvivedTokens int64 `json:"survivedTokens"` + // Names of the system-prompt segments whose content changed + // Internal: SystemSegmentsChanged is part of the SDK's internal API surface and is not intended for external use. + SystemSegmentsChanged []string `json:"systemSegmentsChanged,omitzero"` + // Telemetry-safe names of tools added since the prior call + // Internal: ToolsAdded is part of the SDK's internal API surface and is not intended for external use. + ToolsAdded []string `json:"toolsAdded,omitzero"` + // Raw names of tools added since the prior call, restricted because a tool name can be user-authored + // Internal: ToolsAddedRaw is part of the SDK's internal API surface and is not intended for external use. + ToolsAddedRaw []string `json:"toolsAddedRaw,omitzero"` + // Telemetry-safe names of tools whose definition changed since the prior call + // Internal: ToolsRedefined is part of the SDK's internal API surface and is not intended for external use. + ToolsRedefined []string `json:"toolsRedefined,omitzero"` + // Raw names of tools redefined since the prior call, restricted because a tool name can be user-authored + // Internal: ToolsRedefinedRaw is part of the SDK's internal API surface and is not intended for external use. + ToolsRedefinedRaw []string `json:"toolsRedefinedRaw,omitzero"` + // Telemetry-safe names of tools removed since the prior call + // Internal: ToolsRemoved is part of the SDK's internal API surface and is not intended for external use. + ToolsRemoved []string `json:"toolsRemoved,omitzero"` + // Raw names of tools removed since the prior call, restricted because a tool name can be user-authored + // Internal: ToolsRemovedRaw is part of the SDK's internal API surface and is not intended for external use. + ToolsRemovedRaw []string `json:"toolsRemovedRaw,omitzero"` + // Whether the tool list kept its members but changed their order + // Internal: ToolsReordered is part of the SDK's internal API surface and is not intended for external use. + ToolsReordered *bool `json:"toolsReordered,omitempty"` +} + +func (*PromptCacheBreakData) sessionEventData() {} +func (*PromptCacheBreakData) Type() SessionEventType { return SessionEventTypePromptCacheBreak } + // Agent intent description for current activity or plan type AssistantIntentData struct { // Short description of what the agent is currently doing or planning to do @@ -218,7 +332,8 @@ type AssistantReasoningData struct { Content string `json:"content"` // Unique identifier for this reasoning block ReasoningID string `json:"reasoningId"` - Rte *bool `json:"rte,omitempty"` + // Per-request treatment/eligibility signal returned by the Copilot API in the `X-GitHub-Copilot-Request-TE` response header for the associated model call; `false` when the header was absent or unparseable. + Rte *bool `json:"rte,omitempty"` } func (*AssistantReasoningData) sessionEventData() {} @@ -241,6 +356,9 @@ type AssistantMessageData struct { Content string `json:"content"` // Encrypted reasoning content from OpenAI models. Session-bound and stripped on resume. EncryptedContent *string `json:"encryptedContent,omitempty"` + // Experimental HydraFusion source attribution for this ordinary authoritative assistant message. + // Experimental: Fusion is part of an experimental API and may change or be removed. + Fusion *FusionAttribution `json:"fusion,omitempty"` // CAPI interaction ID for correlating this message with upstream telemetry InteractionID *string `json:"interactionId,omitempty"` // Unique identifier for this assistant message @@ -254,6 +372,8 @@ type AssistantMessageData struct { ParentToolCallID *string `json:"parentToolCallId,omitempty"` // Generation phase for phased-output models (e.g., thinking vs. response phases) Phase *string `json:"phase,omitempty"` + // Neutral provider-tagged reasoning content blocks preserved verbatim for round-tripping. `reasoningText` and `reasoningOpaque` are a lossy derived view of these blocks, retained for display. + ReasoningBlocks *AssistantMessageReasoningBlocks `json:"reasoningBlocks,omitempty"` // Opaque/encrypted extended thinking data from Anthropic models. Session-bound and stripped on resume. ReasoningOpaque *string `json:"reasoningOpaque,omitempty"` // Readable reasoning text from the model's extended thinking @@ -262,7 +382,8 @@ 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"` + // Per-request treatment/eligibility signal returned by the Copilot API in the `X-GitHub-Copilot-Request-TE` response header for the associated model call; `false` when the header was absent or unparseable. + 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 @@ -359,6 +480,32 @@ func (*SessionAutopilotObjectiveChangedData) Type() SessionEventType { return SessionEventTypeSessionAutopilotObjectiveChanged } +// Behavior-neutral record of structured runtime facts present when an agent completion decision is accepted. +// Experimental: SessionCompletionReceiptData is part of an experimental API and may change or be removed. +type SessionCompletionReceiptData struct { + // One-based accepted completion receipt ordinal in the durable session history. + Attempt int64 `json:"attempt"` + // Inclusive durable event range summarized by this receipt. + EventRange CompletionReceiptEventRange `json:"eventRange"` + // Number of failed structured tool completions in the covered range. + FailedToolCount int64 `json:"failedToolCount"` + // Final structured tool completion in the covered range, when one exists. + FinalTool *CompletionReceiptFinalTool `json:"finalTool,omitempty"` + // Version of the completion receipt payload. + SchemaVersion int64 `json:"schemaVersion"` + // Identifier of the assistant turn-end event that supplied the accepted completion boundary. This is the receipt's idempotency key, and always equals eventRange.endEventId. + SourceEventID string `json:"sourceEventId"` + // Runtime reason the completion decision was accepted. + StopReason CompletionReceiptStopReason `json:"stopReason"` + // Number of successful structured tool completions in the covered range. + SuccessfulToolCount int64 `json:"successfulToolCount"` +} + +func (*SessionCompletionReceiptData) sessionEventData() {} +func (*SessionCompletionReceiptData) Type() SessionEventType { + return SessionEventTypeSessionCompletionReceipt +} + // Canonical bytes for a content-addressed binary asset shared by reference across events type SessionBinaryAssetData struct { // Content-addressed id for this binary asset (e.g. "sha256:..."). @@ -418,6 +565,8 @@ func (*SessionContextClearedData) Type() SessionEventType { // Conversation compaction results including success status, metrics, and optional error details type SessionCompactionCompleteData struct { + // Canonical model identifier used for model-specific behavior when replaying compaction + BehaviorModelID *string `json:"behaviorModelId,omitempty"` // Checkpoint snapshot number created for recovery CheckpointNumber *int64 `json:"checkpointNumber,omitempty"` // File path where the checkpoint was stored @@ -561,6 +710,9 @@ type SessionUsageCheckpointData struct { // Internal per-model prompt-cache state used to restore expiration tracking on resume // Internal: ModelCacheState is part of the SDK's internal API surface and is not intended for external use. ModelCacheState []UsageCheckpointModelCacheState `json:"modelCacheState,omitzero"` + // Internal per-conversation prompt-cache-break detector baselines restored on resume + // Internal: PromptCacheBreakState is part of the SDK's internal API surface and is not intended for external use. + PromptCacheBreakState []any `json:"promptCacheBreakState,omitzero"` // Session-wide accumulated nano-AI units cost at checkpoint time TotalNanoAiu float64 `json:"totalNanoAiu"` // Total number of premium API requests used at checkpoint time @@ -649,24 +801,30 @@ 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, then the policy helper, per ordinary key, while permissions compose restrictively across device, server, policy-helper, 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 server and device each supplied a permission allowlist, so enforcement intersects them and the flattened settings payload omits `permissions.allow`. + // 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 policy-helper managed-settings layer was present. The policy helper is the weakest channel: it fills keys no enterprise source set and can never replace one. + PolicyHelperManaged *bool `json:"policyHelperManaged,omitempty"` + // Whether the effective sandbox policy forces the sandbox on *only* because managed policy could not be determined, rather than because the policy requires it. Lets clients tell a user whose `--no-sandbox` was overridden that the sandbox stayed on as a fail-closed fallback, instead of attributing it to an administrator who set no such policy. + SandboxEnabledByUndeterminedPolicy *bool `json:"sandboxEnabledByUndeterminedPolicy,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`, `client`, or `policyHelper` when exactly one channel contributed; `mixed` when multiple channels contributed; otherwise `none`. Consult the per-channel booleans for exact provenance. Source ManagedSettingsResolvedSource `json:"source"` } @@ -679,8 +837,9 @@ func (*SessionManagedSettingsResolvedData) Type() SessionEventType { // 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"` + Revision int64 `json:"revision"` + // Factory run identifier. + RunID string `json:"runId"` } func (*FactoryRunUpdatedData) sessionEventData() {} @@ -697,6 +856,40 @@ type HookProgressData struct { func (*HookProgressData) sessionEventData() {} func (*HookProgressData) Type() SessionEventType { return SessionEventTypeHookProgress } +// Ephemeral signal that a factory run attempt began executing. +// Experimental: FactoryRunStartedData is part of an experimental API and may change or be removed. +type FactoryRunStartedData struct { + // Attempt number this start committed; a resumed run increments it. + Attempt int64 `json:"attempt"` + // Name of the factory this run executes. Low cardinality by construction. + FactoryName string `json:"factoryName"` + // Identifier of the factory run that started. + RunID string `json:"runId"` +} + +func (*FactoryRunStartedData) sessionEventData() {} +func (*FactoryRunStartedData) Type() SessionEventType { return SessionEventTypeFactoryRunStarted } + +// Ephemeral signal that a factory run reached a terminal status. +// Experimental: FactoryRunSettledData is part of an experimental API and may change or be removed. +type FactoryRunSettledData struct { + // AI credits this run consumed, in nano-AIU. + ConsumedNanoAiu int64 `json:"consumedNanoAiu"` + // Subagents this run consumed against its limits. + ConsumedSubagents int64 `json:"consumedSubagents"` + // Active milliseconds accumulated across every attempt of this run. + ElapsedMs int64 `json:"elapsedMs"` + // Typed failure class recorded on the run, when it failed with one (e.g. `factory_limit_reached`). + FailureType *string `json:"failureType,omitempty"` + // Identifier of the factory run that settled. + RunID string `json:"runId"` + // Terminal status the run committed. + Status FactoryRunSettledStatus `json:"status"` +} + +func (*FactoryRunSettledData) sessionEventData() {} +func (*FactoryRunSettledData) Type() SessionEventType { return SessionEventTypeFactoryRunSettled } + // Error details for timeline display including message and optional diagnostic information type SessionErrorData struct { // Only set on `errorType: "rate_limit"`. When `true`, the runtime will follow this error with an `auto_mode_switch.requested` event (or silently switch if `continueOnAutoMode` is enabled). UI clients can use this flag to suppress duplicate rendering of the rate-limit error when they show their own auto-mode-switch prompt. @@ -722,6 +915,274 @@ type SessionErrorData struct { func (*SessionErrorData) sessionEventData() {} func (*SessionErrorData) Type() SessionEventType { return SessionEventTypeSessionError } +// Experimental content-safe activity signal for a running HydraFusion phase. +// Experimental: AssistantFusionPhaseActivityData is part of an experimental API and may change or be removed. +type AssistantFusionPhaseActivityData struct { + // Kind of real activity observed. + Activity FusionPhaseActivityKind `json:"activity"` + // Conversation scope in which the phase executes. + ConversationScope FusionConversationScope `json:"conversationScope"` + // Identifier of the HydraFusion turn containing the phase. + FusionID string `json:"fusionId"` + // HydraFusion orchestration pattern containing the phase. + Pattern FusionPattern `json:"pattern"` + // Stable identifier for the concrete phase. + PhaseID string `json:"phaseId"` + // Kind of phase currently executing. + PhaseKind FusionPhaseKind `json:"phaseKind"` + // Semantic role assigned to the phase. + Role string `json:"role"` + // Opaque hashed correlation token for matching tool-started and tool-completed activity within this Fusion activity stream. It is not the tool call identifier exposed by tool lifecycle events. + ToolCallID *string `json:"toolCallId,omitempty"` + // Cumulative private response bytes observed for this model call. The event never includes response text. + TotalResponseSizeBytes *int64 `json:"totalResponseSizeBytes,omitempty"` +} + +func (*AssistantFusionPhaseActivityData) sessionEventData() {} +func (*AssistantFusionPhaseActivityData) Type() SessionEventType { + return SessionEventTypeAssistantFusionPhaseActivity +} + +// Experimental durable HydraFusion phase output and lossless replay checkpoint. +// Experimental: AssistantFusionPhaseCompletedData is part of an experimental API and may change or be removed. +type AssistantFusionPhaseCompletedData struct { + // Provider-normalized textual output produced by the phase. + Content string `json:"content"` + // Conversation scope in which the phase executed. + ConversationScope FusionConversationScope `json:"conversationScope"` + // Elapsed execution time for the phase in milliseconds. + DurationMs float64 `json:"durationMs"` + // Identifier of the HydraFusion turn containing the phase. + FusionID string `json:"fusionId"` + // Concrete model that executed the phase. + Model string `json:"model"` + // Stable identifier for the completed phase. + PhaseID string `json:"phaseId"` + // Kind of phase that completed. + PhaseKind FusionPhaseKind `json:"phaseKind"` + // Exact provider-normalized message used to reconstruct canonical model history. + // Internal: ProjectionMessage is part of the SDK's internal API surface and is not intended for external use. + ProjectionMessage any `json:"projectionMessage,omitempty"` + // Projection action for the exact internal message. + // Internal: ProjectionMode is part of the SDK's internal API surface and is not intended for external use. + ProjectionMode *FusionProjectionMode `json:"projectionMode,omitempty"` + // Semantic role assigned to the completed phase. + Role string `json:"role"` + // Terminal request held outside canonical state until selected by the final commit. + // Internal: StagedTerminal is part of the SDK's internal API surface and is not intended for external use. + StagedTerminal *FusionStagedTerminal `json:"stagedTerminal,omitempty"` + // Durable outcome status of the phase. + Status FusionPhaseStatus `json:"status"` + // Aggregate concrete-model usage consumed by the phase. + Usage FusionPhaseUsage `json:"usage"` + // Structured judge or critic verdict, when the phase produces one. + Verdict *string `json:"verdict"` +} + +func (*AssistantFusionPhaseCompletedData) sessionEventData() {} +func (*AssistantFusionPhaseCompletedData) Type() SessionEventType { + return SessionEventTypeAssistantFusionPhaseCompleted +} + +// Experimental durable HydraFusion routing failure and the deterministic concrete fallback selected for the turn. +// Experimental: SessionFusionRouteFailedData is part of an experimental API and may change or be removed. +type SessionFusionRouteFailedData struct { + // Identifier of the routing attempt that failed. + AttemptID string `json:"attemptId"` + // Provider or validation error detail, when available. + ErrorMessage *string `json:"errorMessage,omitempty"` + // Concrete model selected as the deterministic fallback. + FallbackModel string `json:"fallbackModel"` + // HydraFusion routing policy requested for the turn. + Policy string `json:"policy"` + // Stable machine-readable reason for the routing failure. + Reason string `json:"reason"` + // Elapsed routing time in milliseconds before the failure. + RoutingLatencyMs *float64 `json:"routingLatencyMs,omitempty"` + // Synthetic HydraFusion model selected for the session. + SyntheticModel string `json:"syntheticModel"` +} + +func (*SessionFusionRouteFailedData) sessionEventData() {} +func (*SessionFusionRouteFailedData) Type() SessionEventType { + return SessionEventTypeSessionFusionRouteFailed +} + +// Experimental durable aggregate outcome of a HydraFusion turn. +// Experimental: SessionFusionCompletedData is part of an experimental API and may change or be removed. +type SessionFusionCompletedData struct { + // Total cached input tokens reported across all phases. + CachedTokens int64 `json:"cachedTokens"` + // Total tokens written to prompt cache across all phases. + CacheWriteTokens *int64 `json:"cacheWriteTokens,omitempty"` + // Idempotency identifier for the authoritative final commit. + CommitID string `json:"commitId"` + // Reason the turn used a degraded route, when applicable. + DegradedReason *string `json:"degradedReason"` + // Total elapsed execution time for the HydraFusion turn in milliseconds. + DurationMs float64 `json:"durationMs"` + // Concrete model that supplied the authoritative final content. + FinalSourceModel *string `json:"finalSourceModel"` + // Phase whose output supplied the authoritative final content. + FinalSourcePhaseID *string `json:"finalSourcePhaseId"` + // Concrete model recommended for eligible follow-up turns. + FollowUpModel string `json:"followUpModel"` + // Stable identifier for the completed HydraFusion turn. + FusionID string `json:"fusionId"` + // Total input tokens consumed across all phases. + InputTokens int64 `json:"inputTokens"` + // Stable aggregate outcome of the HydraFusion turn. + Outcome string `json:"outcome"` + // Total output tokens produced across all phases. + OutputTokens int64 `json:"outputTokens"` + // HydraFusion orchestration pattern executed for the turn. + Pattern FusionPattern `json:"pattern"` + // Number of concrete phases attempted by the turn. + PhaseCount int64 `json:"phaseCount"` + // Total concrete model requests made across all phases. + RequestCount int64 `json:"requestCount"` + // Synthetic HydraFusion model selected for the session. + SyntheticModel string `json:"syntheticModel"` + // Total normalized AI-unit cost reported across all phases, in nano-AIU. + TotalNanoAiu float64 `json:"totalNanoAiu"` + // Identifier of the session turn associated with the completion. + TurnID string `json:"turnId"` +} + +func (*SessionFusionCompletedData) sessionEventData() {} +func (*SessionFusionCompletedData) Type() SessionEventType { + return SessionEventTypeSessionFusionCompleted +} + +// Experimental durable typed HydraFusion phase failure and degradation transition. +// Experimental: AssistantFusionPhaseFailedData is part of an experimental API and may change or be removed. +type AssistantFusionPhaseFailedData struct { + // Conversation scope in which the phase executed. + ConversationScope FusionConversationScope `json:"conversationScope"` + // Identifier of the fallback phase used to continue the turn after degradation. + DegradedToPhaseID *string `json:"degradedToPhaseId,omitempty"` + // Elapsed execution time before the phase failed, in milliseconds. + DurationMs float64 `json:"durationMs"` + // Provider or execution error detail, when available. + ErrorMessage *string `json:"errorMessage,omitempty"` + // Identifier of the HydraFusion turn containing the phase. + FusionID string `json:"fusionId"` + // Concrete model that attempted the phase. + Model string `json:"model"` + // Stable identifier for the failed phase. + PhaseID string `json:"phaseId"` + // Kind of phase that failed. + PhaseKind FusionPhaseKind `json:"phaseKind"` + // Stable machine-readable reason for the phase failure. + Reason string `json:"reason"` + // Semantic role assigned to the failed phase. + Role string `json:"role"` + // Durable outcome status of the phase. + Status FusionPhaseStatus `json:"status"` + // Aggregate concrete-model usage consumed before the failure. + Usage FusionPhaseUsage `json:"usage"` +} + +func (*AssistantFusionPhaseFailedData) sessionEventData() {} +func (*AssistantFusionPhaseFailedData) Type() SessionEventType { + return SessionEventTypeAssistantFusionPhaseFailed +} + +// Experimental durable validated HydraFusion route and turn policy. +// Experimental: SessionFusionResolvedData is part of an experimental API and may change or be removed. +type SessionFusionResolvedData struct { + // Version of the validated HydraFusion event contract. + ContractVersion int64 `json:"contractVersion"` + // Concrete model used when the planned primary model cannot execute. + FallbackModel string `json:"fallbackModel"` + // Router recommendation controlling reuse or rerouting on later turns. + FollowUp *FusionFollowUpRecommendation `json:"followUp,omitempty"` + // Concrete model recommended for eligible follow-up turns. + FollowUpModel string `json:"followUpModel"` + // Stable identifier for the resolved HydraFusion turn. + FusionID string `json:"fusionId"` + // Version of the executable model universe used for selection. + ModelUniverseVersion *string `json:"modelUniverseVersion,omitempty"` + // Validated orchestration pattern selected for the turn. + Pattern FusionPattern `json:"pattern"` + // Presentation-neutral phase plan for clients that render workflow progress. + // Experimental: PhasePlan is part of an experimental API and may change or be removed. + PhasePlan []FusionPhasePlanStep `json:"phasePlan,omitzero"` + // Version of the validated execution-plan format. + PlanVersion *string `json:"planVersion,omitempty"` + // HydraFusion routing policy used to resolve the plan. + Policy string `json:"policy"` + // Version of the local routing policy. + PolicyVersion *string `json:"policyVersion,omitempty"` + // Concrete model selected for the primary solver phase. + PrimaryModel string `json:"primaryModel"` + // Router implementation that supplied the plan. + RouteSource *string `json:"routeSource,omitempty"` + // Elapsed time in milliseconds required to resolve and validate the route. + RoutingLatencyMs *float64 `json:"routingLatencyMs,omitempty"` + // Identifier of the local policy rule that matched. + RuleID *string `json:"ruleId,omitempty"` + // Zero-based index of the local policy rule that matched. + RuleIndex *int64 `json:"ruleIndex,omitempty"` + // Human-readable name of the local policy rule that matched. + RuleName *string `json:"ruleName,omitempty"` + // Validated capability scores used to select the route. + Scores *FusionScores `json:"scores,omitempty"` + // Concrete model selected for the review or judge phase, when required. + SecondaryModel *string `json:"secondaryModel"` + // Synthetic HydraFusion model selected for the session. + SyntheticModel string `json:"syntheticModel"` + // Identifier of the session turn associated with the route. + TurnID string `json:"turnId"` +} + +func (*SessionFusionResolvedData) sessionEventData() {} +func (*SessionFusionResolvedData) Type() SessionEventType { + return SessionEventTypeSessionFusionResolved +} + +// Experimental transient HydraFusion phase/model/role signal. +// Experimental: AssistantFusionPhaseStartedData is part of an experimental API and may change or be removed. +type AssistantFusionPhaseStartedData struct { + // Conversation scope in which the phase executes. + ConversationScope FusionConversationScope `json:"conversationScope"` + // Identifier of the HydraFusion turn containing the phase. + FusionID string `json:"fusionId"` + // Concrete model executing the phase. + Model string `json:"model"` + // HydraFusion orchestration pattern containing the phase. + Pattern FusionPattern `json:"pattern"` + // Stable identifier for the concrete phase. + PhaseID string `json:"phaseId"` + // Kind of phase being executed. + PhaseKind FusionPhaseKind `json:"phaseKind"` + // Semantic role assigned to the phase. + Role string `json:"role"` +} + +func (*AssistantFusionPhaseStartedData) sessionEventData() {} +func (*AssistantFusionPhaseStartedData) Type() SessionEventType { + return SessionEventTypeAssistantFusionPhaseStarted +} + +// Experimental transient signal that HydraFusion routing has started for an eligible turn. +// Experimental: SessionFusionRouteStartedData is part of an experimental API and may change or be removed. +type SessionFusionRouteStartedData struct { + // Identifier for this routing attempt before a durable Fusion turn exists. + AttemptID string `json:"attemptId"` + // HydraFusion routing policy requested for the turn. + Policy *string `json:"policy,omitempty"` + // Synthetic HydraFusion model selected for the session. + SyntheticModel *string `json:"syntheticModel,omitempty"` + // Kind of turn being routed. + TurnKind FusionTurnKind `json:"turnKind"` +} + +func (*SessionFusionRouteStartedData) sessionEventData() {} +func (*SessionFusionRouteStartedData) Type() SessionEventType { + return SessionEventTypeSessionFusionRouteStarted +} + // External tool completion notification signaling UI dismissal type ExternalToolCompletedData struct { // Request ID of the resolved external tool request; clients should dismiss any UI for this request @@ -737,6 +1198,8 @@ func (*ExternalToolCompletedData) Type() SessionEventType { type ExternalToolRequestedData struct { // Arguments to pass to the external tool Arguments any `json:"arguments,omitempty"` + // Stable provider identity captured with an extension-owned tool definition; hosts use it to route the request to the same provider that was offered to the model + ProviderID *string `json:"providerId,omitempty"` // Unique identifier for this request; used to respond via session.respondToExternalTool() RequestID string `json:"requestId"` // Session ID that this external tool request belongs to @@ -776,8 +1239,13 @@ type ModelCallFailureData struct { ErrorType *string `json:"errorType,omitempty"` // Whether the failure originated from an API response or the request transport FailureKind *ModelCallFailureKind `json:"failureKind,omitempty"` + // Experimental HydraFusion attribution for this failed concrete model call. + // Experimental: Fusion is part of an experimental API and may change or be removed. + Fusion *FusionAttribution `json:"fusion,omitempty"` // What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls Initiator *string `json:"initiator,omitempty"` + // Authoritative interaction classification for the failed call, matching `assistant.usage.interactionType` (for example `conversation-agent`, `conversation-subagent`, or `conversation-sampling`). Absent when the producer cannot classify the interaction. + InteractionType *string `json:"interactionType,omitempty"` // Whether the session selected Auto mode for the failed call IsAuto *bool `json:"isAuto,omitempty"` // Whether the failed call used a bring-your-own-key provider @@ -797,7 +1265,8 @@ 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"` + // Per-request treatment/eligibility signal returned by the Copilot API in the `X-GitHub-Copilot-Request-TE` response header for the associated model call; `false` when the header was absent or unparseable. + 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 @@ -811,6 +1280,25 @@ type ModelCallFailureData struct { func (*ModelCallFailureData) sessionEventData() {} func (*ModelCallFailureData) Type() SessionEventType { return SessionEventTypeModelCallFailure } +// Final lifecycle outcome for one logical model dispatch. A logical dispatch may include internal reconnect or fallback work, so event count is not provider HTTP-request count. +type ModelCallFinishedData struct { + // Whether an accepted successful response requested the exact name and command semantics of a built-in file edit tool, including an external tool explicitly replacing that built-in name. Absent when the logical dispatch did not produce an accepted response. + ContainsBuiltInFileEditRequest *bool `json:"containsBuiltInFileEditRequest,omitempty"` + // Monotonic elapsed time spent in the logical model dispatch, including any internal transport reconnect or fallback and excluding orchestrator retry backoff, tool execution, confirmations, and post-response processing + DispatchDurationMs float64 `json:"dispatchDurationMs"` + // Version of the built-in file-edit semantic classifier used for this event + EditClassifierVersion int64 `json:"editClassifierVersion"` + // Identifier of the user interaction that owns the model dispatch, matching assistant.turn_start.interactionId when available + InteractionID *string `json:"interactionId,omitempty"` + // Final outcome after post-response acceptance processing + Outcome ModelCallFinishedOutcome `json:"outcome"` + // Agent-loop iteration within the interaction that initiated the model dispatch + TurnID string `json:"turnId"` +} + +func (*ModelCallFinishedData) sessionEventData() {} +func (*ModelCallFinishedData) Type() SessionEventType { return SessionEventTypeModelCallFinished } + // Hook invocation completion details including output, success status, and error information type HookEndData struct { // Error details when the hook failed @@ -821,6 +1309,8 @@ type HookEndData struct { HookType string `json:"hookType"` // Output data produced by the hook Output any `json:"output,omitempty"` + // Tool call ID of the parent tool invocation when this event originates from a sub-agent + ParentToolCallID *string `json:"parentToolCallId,omitempty"` // Whether the hook completed successfully Success bool `json:"success"` } @@ -834,8 +1324,10 @@ type HookStartData struct { HookInvocationID string `json:"hookInvocationId"` // Type of hook being invoked (e.g., "preToolUse", "postToolUse", "sessionStart") HookType string `json:"hookType"` - // Input data passed to the hook + // Input data passed to the hook. For postToolUse hooks the retained copy served by session.eventLog.read (and by a resumed session) elides the tool result's inline `contents`/`uiResource` and replaces an over-long `textResultForLlm` with a `[copilot:elided ...]` marker, to keep a multi-megabyte payload out of the durable event log; the live subscription stream still delivers the full value. Read the adjacent tool.execution_complete event for the tool result itself. Input any `json:"input,omitempty"` + // Tool call ID of the parent tool invocation when this event originates from a sub-agent + ParentToolCallID *string `json:"parentToolCallId,omitempty"` } func (*HookStartData) sessionEventData() {} @@ -858,6 +1350,8 @@ func (*SessionInfoData) Type() SessionEventType { return SessionEventTypeSession // LLM API call usage metrics including tokens, costs, quotas, and billing information type AssistantUsageData struct { + // Number of accepted speculative prediction tokens + AcceptedPredictionTokens *int64 `json:"acceptedPredictionTokens,omitempty"` // Completion ID from the model provider (e.g., chatcmpl-abc123) APICallID *string `json:"apiCallId,omitempty"` // API endpoint used for this model call, matching CAPI supported_endpoints vocabulary @@ -865,10 +1359,16 @@ type AssistantUsageData struct { // 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"` + // Whether the provider reported prompt-cache usage details for this call + // Internal: CacheDetailsReported is part of the SDK's internal API surface and is not intended for external use. + CacheDetailsReported *bool `json:"cacheDetailsReported,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 CacheReadTokens *int64 `json:"cacheReadTokens,omitempty"` + // Effective prompt-cache lifetime in seconds for this call + // Internal: CacheTtlSeconds is part of the SDK's internal API surface and is not intended for external use. + CacheTtlSeconds *int64 `json:"cacheTtlSeconds,omitempty"` // Number of tokens written to prompt cache CacheWriteTokens *int64 `json:"cacheWriteTokens,omitempty"` // 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. @@ -882,6 +1382,12 @@ type AssistantUsageData struct { Duration *int64 `json:"duration,omitempty"` // 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". FinishReason *string `json:"finishReason,omitempty"` + // How the prompt-cache frontier was determined for this call + // Internal: FrontierSource is part of the SDK's internal API surface and is not intended for external use. + FrontierSource *string `json:"frontierSource,omitempty"` + // Experimental HydraFusion attribution for this concrete model call's usage. + // Experimental: Fusion is part of an experimental API and may change or be removed. + Fusion *FusionAttribution `json:"fusion,omitempty"` // What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls Initiator *string `json:"initiator,omitempty"` // Number of input tokens consumed @@ -890,6 +1396,14 @@ type AssistantUsageData struct { InteractionType *string `json:"interactionType,omitempty"` // Average inter-token latency in milliseconds. Only available for streaming requests InterTokenLatencyMs *float64 `json:"interTokenLatencyMs,omitempty"` + // Whether Auto mode was selected for this model call + IsAuto *bool `json:"isAuto,omitempty"` + // Whether this model call used a bring-your-own-key provider + IsByok *bool `json:"isByok,omitempty"` + // Requested maximum output tokens used for this model call + MaxOutputTokens *int64 `json:"maxOutputTokens,omitempty"` + // Effective maximum prompt-token limit used for this model call + MaxPromptTokens *int64 `json:"maxPromptTokens,omitempty"` // Model identifier used for this API call Model string `json:"model"` // Number of tool calls returned by the model @@ -897,6 +1411,8 @@ type AssistantUsageData struct { NumToolCalls *int64 `json:"numToolCalls,omitempty"` // Number of output tokens produced OutputTokens *int64 `json:"outputTokens,omitempty"` + // Time to first observable model output in milliseconds. Includes text, reasoning, and tool-call output; only available for streaming requests that produce observable output. + OutputTtftMs *float64 `json:"outputTtftMs,omitempty"` // Parent tool call ID when this usage originates from a sub-agent // Deprecated: ParentToolCallID is deprecated. ParentToolCallID *string `json:"parentToolCallId,omitempty"` @@ -907,9 +1423,14 @@ type AssistantUsageData struct { QuotaSnapshots map[string]AssistantUsageQuotaSnapshot `json:"quotaSnapshots,omitzero"` // Reasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh", "max") ReasoningEffort *string `json:"reasoningEffort,omitempty"` + // Reasoning summary mode used for this model call, if applicable + ReasoningSummary *ReasoningSummary `json:"reasoningSummary,omitempty"` // Number of output tokens used for reasoning (e.g., chain-of-thought) ReasoningTokens *int64 `json:"reasoningTokens,omitempty"` - Rte *bool `json:"rte,omitempty"` + // Number of rejected speculative prediction tokens + RejectedPredictionTokens *int64 `json:"rejectedPredictionTokens,omitempty"` + // Per-request treatment/eligibility signal returned by the Copilot API in the `X-GitHub-Copilot-Request-TE` response header for the associated model call; `false` when the header was absent or unparseable. + 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 @@ -920,6 +1441,8 @@ type AssistantUsageData struct { // 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"` + // Transport used for this model call (http or websocket) + Transport *AssistantUsageTransport `json:"transport,omitempty"` } func (*AssistantUsageData) sessionEventData() {} @@ -1002,8 +1525,44 @@ type AssistantTurnRetryData struct { func (*AssistantTurnRetryData) sessionEventData() {} func (*AssistantTurnRetryData) Type() SessionEventType { return SessionEventTypeAssistantTurnRetry } +// Metadata for work the user interrupted while the agent was running +type AgentInterruptedData struct { + // What the agent was doing when the user interrupted it + Activity AgentInterruptedActivity `json:"activity"` + // For an interrupted model call: the provider endpoint the request targeted + APIEndpoint *string `json:"apiEndpoint,omitempty"` + // For an interrupted model call: whether the user interrupted before any token arrived or while the response was streaming + CancelPhase *AgentInterruptedCancelPhase `json:"cancelPhase,omitempty"` + // How long the interrupted work had been running, in milliseconds + ElapsedMs float64 `json:"elapsedMs"` + // For an interrupted background-agent batch: how many background sub-agents the stop swept. Counts accepted cancellations, so an agent cancelled as a cascade of its interrupted parent is covered by that parent rather than counted again. + InterruptedAgentCount *int64 `json:"interruptedAgentCount,omitempty"` + // For an interrupted model call: the model the request targeted + Model *string `json:"model,omitempty"` + // For a mid-stream interrupt: the observed time to first observable output, in milliseconds. Deliberately distinct from the `ttftMs` reported on a successful model call, which measures time to first stream event. + OutputTtftMs *float64 `json:"outputTtftMs,omitempty"` + // For an interrupted model call: the reasoning effort the request asked for + ReasoningEffort *string `json:"reasoningEffort,omitempty"` + // Subset of `toolNames` whose tool metadata marks the tool name as safe to record unhashed in telemetry. + SafeToolNames []string `json:"safeToolNames,omitzero"` + // Tool call identifiers that were still running + ToolCallIDs []string `json:"toolCallIds,omitzero"` + // Names of the tools that were still running. More than one when the model requested a parallel fan-out. + ToolNames []string `json:"toolNames,omitzero"` + // For an interrupted model call: the transport the request used + Transport *ModelCallFailureTransport `json:"transport,omitempty"` + // Zero-based agentic-loop iteration the interrupt landed in + Turn int64 `json:"turn"` +} + +func (*AgentInterruptedData) sessionEventData() {} +func (*AgentInterruptedData) Type() SessionEventType { return SessionEventTypeAgentInterrupted } + // Model API dispatch metadata for internal telemetry type ModelCallStartData struct { + // Experimental HydraFusion attribution for this concrete model call. + // Experimental: Fusion is part of an experimental API and may change or be removed. + Fusion *FusionAttribution `json:"fusion,omitempty"` // 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 @@ -1036,6 +1595,8 @@ type SessionModelChangeData struct { ReasoningEffort *string `json:"reasoningEffort,omitempty"` // Reasoning summary mode after the model change, if applicable ReasoningSummary *ReasoningSummary `json:"reasoningSummary,omitempty"` + // Origin of the effective model change, when known. + Source *ModelChangeSource `json:"source,omitempty"` // Output verbosity level after the model change, if applicable Verbosity *Verbosity `json:"verbosity,omitempty"` } @@ -1096,6 +1657,24 @@ func (*SessionCustomNotificationData) Type() SessionEventType { return SessionEventTypeSessionCustomNotification } +// Ordered output and terminal state for a transient query that does not modify conversation history. +// Experimental: UIEphemeralQueryData is part of an experimental API and may change or be removed. +type UIEphemeralQueryData struct { + // Full response text, present for the `completed` phase. + Answer *string `json:"answer,omitempty"` + // Ordered text delta, present for the `chunk` phase. + Chunk *string `json:"chunk,omitempty"` + // Model or transport failure message, present for the `failed` phase. + Error *string `json:"error,omitempty"` + // Current query lifecycle phase. + Phase UIEphemeralQueryPhase `json:"phase"` + // Runtime-minted query identifier. + RequestID string `json:"requestId"` +} + +func (*UIEphemeralQueryData) sessionEventData() {} +func (*UIEphemeralQueryData) Type() SessionEventType { return SessionEventTypeUIEphemeralQuery } + // Payload emitted whenever the main agent's processing loop goes idle, including while related background work (running agents or in-flight attached shell commands) is still pending and the session-level idle event is therefore deferred type AssistantIdleData struct { // True when the preceding agentic loop was cancelled via abort signal @@ -1140,11 +1719,20 @@ func (*MCPToolsListChangedData) Type() SessionEventType { return SessionEventTyp type SessionIdleData struct { // True when the preceding agentic loop was cancelled via abort signal Aborted *bool `json:"aborted,omitempty"` + // The session mode the agent was operating in when it went idle, when the mode is known. Lets turn-scoped consumers distinguish an autopilot continuation boundary (where the agent keeps working after this idle) from a genuine turn completion. + Mode *SessionMode `json:"mode,omitempty"` } func (*SessionIdleData) sessionEventData() {} func (*SessionIdleData) Type() SessionEventType { return SessionEventTypeSessionIdle } +// Payload of `sandbox.decision`, a bounded governance record of what the process sandbox was configured to do and whether it took effect. Discriminated by `kind`. +type SandboxDecisionData struct { +} + +func (*SandboxDecisionData) sessionEventData() {} +func (*SandboxDecisionData) Type() SessionEventType { return SessionEventTypeSandboxDecision } + // Payload of `session.canvas.closed` with the closed canvas instance ID, provider ID, and canvas ID. // Experimental: SessionCanvasClosedData is part of an experimental API and may change or be removed. type SessionCanvasClosedData struct { @@ -1292,6 +1880,8 @@ type UserMessageData struct { InteractionID *string `json:"interactionId,omitempty"` // True when this user message was auto-injected by autopilot's continuation loop rather than typed by the user; used to distinguish autopilot-driven turns in telemetry. IsAutopilotContinuation *bool `json:"isAutopilotContinuation,omitempty"` + // Stable identity of the logical user message, matching the ID returned by send and retained by pending queue snapshots + MessageID *string `json:"messageId,omitempty"` // 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 NativeDocumentPathFallbackPaths []string `json:"nativeDocumentPathFallbackPaths,omitzero"` // Parent agent task ID for background telemetry correlated to this user turn @@ -1302,6 +1892,8 @@ type UserMessageData struct { SupportedNativeDocumentMIMETypes []string `json:"supportedNativeDocumentMimeTypes,omitzero"` // Transformed version of the message sent to the model, with XML wrapping, timestamps, and other augmentations for prompt caching TransformedContent *string `json:"transformedContent,omitempty"` + // The agent-loop turn ID that consumed this message; absent when no agent-loop turn consumed it + TurnID *string `json:"turnId,omitempty"` } func (*UserMessageData) sessionEventData() {} @@ -1322,6 +1914,8 @@ func (*PermissionCompletedData) Type() SessionEventType { return SessionEventTyp // Permission request notification requiring client approval with request details type PermissionRequestedData struct { + // Agent mode captured from the owning turn when permission evaluation began. + AgentMode *SessionMode `json:"agentMode,omitempty"` // Details of the permission being requested PermissionRequest PermissionRequest `json:"permissionRequest"` // Derived user-facing permission prompt details for UI consumers @@ -1337,18 +1931,18 @@ type PermissionRequestedData struct { func (*PermissionRequestedData) sessionEventData() {} func (*PermissionRequestedData) Type() SessionEventType { return SessionEventTypePermissionRequested } -// Permissions change details carrying the aggregate allow-all transition. +// Permission-mode transition details. +// Experimental: SessionPermissionsChangedData is part of an experimental API and may change or be removed. type SessionPermissionsChangedData struct { - // Allow-all mode after the change - // Experimental: AllowAllPermissionMode is part of an experimental API and may change or be removed. - AllowAllPermissionMode *PermissionAllowAllMode `json:"allowAllPermissionMode,omitempty"` - // Aggregate allow-all flag after the change - AllowAllPermissions bool `json:"allowAllPermissions"` - // Allow-all mode before the change - // Experimental: PreviousAllowAllPermissionMode is part of an experimental API and may change or be removed. - PreviousAllowAllPermissionMode *PermissionAllowAllMode `json:"previousAllowAllPermissionMode,omitempty"` - // Aggregate allow-all flag before the change - PreviousAllowAllPermissions bool `json:"previousAllowAllPermissions"` + // Explicit LLM judge model override used by assisted mode; omitted when the provider default applies + // Experimental: AssistedApprovalModel is part of an experimental API and may change or be removed. + AssistedApprovalModel *string `json:"assistedApprovalModel,omitempty"` + // Permission mode after the change + // Experimental: Mode is part of an experimental API and may change or be removed. + Mode PermissionMode `json:"mode"` + // Permission mode before the change + // Experimental: PreviousMode is part of an experimental API and may change or be removed. + PreviousMode PermissionMode `json:"previousMode"` } func (*SessionPermissionsChangedData) sessionEventData() {} @@ -1371,6 +1965,8 @@ func (*ToolSearchActivatedData) Type() SessionEventType { return SessionEventTyp type ExitPlanModeRequestedData struct { // Available actions the user can take Actions []ExitPlanModeAction `json:"actions"` + // Model the session had selected when the plan was authored, when one is known + Model *string `json:"model,omitempty"` // Full content of the plan file PlanContent string `json:"planContent"` // Recommended action to preselect for the user @@ -1434,6 +2030,19 @@ type CommandQueuedData struct { func (*CommandQueuedData) sessionEventData() {} func (*CommandQueuedData) Type() SessionEventType { return SessionEventTypeCommandQueued } +// Records that a mode transition notice reached the model so cache-stable mode tools can remain offered across resume. +type SessionModeNoticeDeliveredData struct { + // Model-visible transition notice persisted for a mid-turn delivery + Content *string `json:"content,omitempty"` + // Mode established by the delivered transition notice + Mode SessionMode `json:"mode"` +} + +func (*SessionModeNoticeDeliveredData) sessionEventData() {} +func (*SessionModeNoticeDeliveredData) Type() SessionEventType { + return SessionEventTypeSessionModeNoticeDelivered +} + // Registered command dispatch request routed to the owning client type CommandExecuteData struct { // Raw argument string after the command name @@ -1449,6 +2058,21 @@ type CommandExecuteData struct { func (*CommandExecuteData) sessionEventData() {} func (*CommandExecuteData) Type() SessionEventType { return SessionEventTypeCommandExecute } +// Resolved runtime configuration for a configured sub-agent +type SubagentConfiguredData struct { + // Resolved context tier, when configured for the model + ContextTier *string `json:"contextTier,omitempty"` + // Resolved model the sub-agent will run with + Model string `json:"model"` + // Whether the sub-agent accepts follow-up turns + MultiTurn bool `json:"multiTurn"` + // Resolved reasoning effort, when configured for the model + ReasoningEffort *string `json:"reasoningEffort,omitempty"` +} + +func (*SubagentConfiguredData) sessionEventData() {} +func (*SubagentConfiguredData) Type() SessionEventType { return SessionEventTypeSubagentConfigured } + // Runtime enforcement of enterprise managed settings: fires when the session blocks or caps a runtime action because enterprise policy governs it, so SDK clients can explain *why* an action was governed. Unlike `session.managed_settings_resolved` (which reports *what* is managed), this reports a concrete governed action — e.g. a user or host tried to turn on a bypass-permissions escalation while policy disables it. Emitted live (not persisted to the session event log) on user/host-initiated attempts only, never for silent policy application. Marked experimental while the managed-settings surface stabilizes. // Experimental: SessionManagedSettingsEnforcedData is part of an experimental API and may change or be removed. type SessionManagedSettingsEnforcedData struct { @@ -1587,6 +2211,8 @@ func (*SessionHandoffData) Type() SessionEventType { return SessionEventTypeSess type SessionStartData struct { // Whether the session was already in use by another client at start time AlreadyInUse *bool `json:"alreadyInUse,omitempty"` + // Auto routing preference selected at session creation time + AutoTier *AutoTier `json:"autoTier,omitempty"` // Working directory and git context at session start Context *WorkingDirectoryContext `json:"context,omitempty"` // Context tier selected at session creation time for models with tiered context pricing; null when no tier is selected (e.g., non-tiered model) @@ -1665,6 +2291,8 @@ func (*SessionSessionLimitsChangedData) Type() SessionEventType { type SessionResumeData struct { // Whether the session was already in use by another client at resume time AlreadyInUse *bool `json:"alreadyInUse,omitempty"` + // Auto routing preference active at resume time + AutoTier *AutoTier `json:"autoTier,omitempty"` // Updated working directory and git context at resume time Context *WorkingDirectoryContext `json:"context,omitempty"` // Context tier currently selected at resume time; null when no tier is active @@ -1711,6 +2339,8 @@ func (*SessionSnapshotRewindData) Type() SessionEventType { // Session termination metrics including usage statistics, code changes, and shutdown reason type SessionShutdownData struct { + // Per-agent usage breakdown, keyed by agent instance identifier. The main conversation uses the stable key `main`. + AgentMetrics map[string]ShutdownAgentMetric `json:"agentMetrics,omitzero"` // Aggregate code change metrics for the session CodeChanges ShutdownCodeChanges `json:"codeChanges"` // Non-system message token count at shutdown @@ -1772,17 +2402,19 @@ type SkillInvokedData struct { Content string `json:"content"` // Description of the skill from its SKILL.md frontmatter Description *string `json:"description,omitempty"` + // Whether model invocation is disabled for this skill + DisableModelInvocation *bool `json:"disableModelInvocation,omitempty"` // Model identifier active when the skill was invoked, when known Model *string `json:"model,omitempty"` // Name of the invoked skill Name string `json:"name"` - // File path to the SKILL.md definition + // File path to the SKILL.md definition, or an empty string for an SDK-provided skill without a filesystem identity Path string `json:"path"` // Name of the plugin this skill originated from, when applicable PluginName *string `json:"pluginName,omitempty"` // Version of the plugin this skill originated from, when applicable PluginVersion *string `json:"pluginVersion,omitempty"` - // Source identifier for where the skill was discovered. Known values include: project (workspace skill), inherited (parent-directory skill), personal-copilot (~/.copilot/skills), personal-agents (~/.agents/skills), custom (configured directory), plugin (installed plugin), builtin (bundled runtime skill), and remote (org/enterprise skill) + // Source identifier for where the skill was discovered. Known values include: project (workspace skill), inherited (parent-directory skill), personal-copilot (~/.copilot/skills), personal-agents (~/.agents/skills), custom (configured directory), plugin (installed plugin), builtin (bundled runtime skill), remote (org/enterprise skill), and sdk (SDK-provided skill) Source *string `json:"source,omitempty"` // What triggered the skill invocation: `user-invoked` (explicit user action, such as via a slash command or UI affordance), `agent-invoked` (agent requested the skill), or `context-load` (loaded as part of another context, such as preloading skills configured on a custom agent or subagent) Trigger *SkillInvokedTrigger `json:"trigger,omitempty"` @@ -1880,10 +2512,24 @@ 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"` + // Whether the first model actually dispatched matched the user's configured preference + ConfiguredModelMatchesActual *bool `json:"configuredModelMatchesActual,omitempty"` + // Concrete model the user configured for this sub-agent via `/subagents`, when present + ConfiguredModelPreference *string `json:"configuredModelPreference,omitempty"` // Wall-clock duration of the sub-agent execution in milliseconds DurationMs *int64 `json:"durationMs,omitempty"` + // Whether the explicit task-call model matched the user's configured preference + ExplicitModelMatchesPreference *bool `json:"explicitModelMatchesPreference,omitempty"` + // Explicit model supplied by the parent agent on the task call, when present + ExplicitModelOverride *string `json:"explicitModelOverride,omitempty"` + // First model for which the sub-agent started an inference request, when one was dispatched + FirstDispatchedModel *string `json:"firstDispatchedModel,omitempty"` // Model used by the sub-agent Model *string `json:"model,omitempty"` + // Why an explicit task-call model did not become the effective model + ModelOverrideReason *string `json:"modelOverrideReason,omitempty"` // Tool call ID of the parent tool invocation that spawned this sub-agent ToolCallID string `json:"toolCallId"` // Total tokens (input + output) consumed by the sub-agent @@ -1901,12 +2547,24 @@ type SubagentFailedData struct { AgentDisplayName string `json:"agentDisplayName"` // Internal name of the sub-agent AgentName string `json:"agentName"` + // Whether the first model actually dispatched matched the user's configured preference + ConfiguredModelMatchesActual *bool `json:"configuredModelMatchesActual,omitempty"` + // Concrete model the user configured for this sub-agent via `/subagents`, when present + ConfiguredModelPreference *string `json:"configuredModelPreference,omitempty"` // Wall-clock duration of the sub-agent execution in milliseconds DurationMs *int64 `json:"durationMs,omitempty"` // Error message describing why the sub-agent failed Error string `json:"error"` + // Whether the explicit task-call model matched the user's configured preference + ExplicitModelMatchesPreference *bool `json:"explicitModelMatchesPreference,omitempty"` + // Explicit model supplied by the parent agent on the task call, when present + ExplicitModelOverride *string `json:"explicitModelOverride,omitempty"` + // First model for which the sub-agent started an inference request, when one was dispatched + FirstDispatchedModel *string `json:"firstDispatchedModel,omitempty"` // Model selected for the sub-agent, when known Model *string `json:"model,omitempty"` + // Why an explicit task-call model did not become the effective model + ModelOverrideReason *string `json:"modelOverrideReason,omitempty"` // Tool call ID of the parent tool invocation that spawned this sub-agent ToolCallID string `json:"toolCallId"` // Total tokens (input + output) consumed before the sub-agent failed @@ -1926,8 +2584,18 @@ type SubagentStartedData struct { AgentDisplayName string `json:"agentDisplayName"` // Internal name of the sub-agent AgentName string `json:"agentName"` + // Type of the sub-agent selected at spawn time. + AgentType *string `json:"agentType,omitempty"` + // Whether the sub-agent runs synchronously or in the background. + ExecutionMode *string `json:"executionMode,omitempty"` + // Root id of the factory run that spawned this sub-agent, when it was spawned by one. + FactoryRunID *string `json:"factoryRunId,omitempty"` // Model the sub-agent will run with, when known at start. Model *string `json:"model,omitempty"` + // Task-registry ID of the spawning sub-agent. Absent when the root session spawned this child. + ParentID *string `json:"parentId,omitempty"` + // Whether this sub-agent can be resumed. Currently always false. + Resumable *bool `json:"resumable,omitempty"` // Tool call ID of the parent tool invocation that spawned this sub-agent ToolCallID string `json:"toolCallId"` } @@ -1965,16 +2633,6 @@ func (*SystemMessageData) Type() SessionEventType { return SessionEventTypeSyste // Task completion notification with summary from the agent type SessionTaskCompleteData struct { - // 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"` } func (*SessionTaskCompleteData) sessionEventData() {} @@ -1984,6 +2642,9 @@ func (*SessionTaskCompleteData) Type() SessionEventType { return SessionEventTyp type ToolExecutionCompleteData struct { // Error details when the tool execution failed Error *ToolExecutionCompleteError `json:"error,omitempty"` + // Experimental HydraFusion attribution for this tool completion. + // Experimental: Fusion is part of an experimental API and may change or be removed. + Fusion *FusionAttribution `json:"fusion,omitempty"` // CAPI interaction ID for correlating this tool execution with upstream telemetry InteractionID *string `json:"interactionId,omitempty"` // Whether this tool call was explicitly requested by the user rather than the assistant @@ -1998,7 +2659,8 @@ type ToolExecutionCompleteData struct { ParentToolCallID *string `json:"parentToolCallId,omitempty"` // Tool execution result on success Result *ToolExecutionCompleteResult `json:"result,omitempty"` - Rte *bool `json:"rte,omitempty"` + // Per-request treatment/eligibility signal returned by the Copilot API in the `X-GitHub-Copilot-Request-TE` response header for the associated model call; `false` when the header was absent or unparseable. + 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 @@ -2037,6 +2699,9 @@ type ToolExecutionStartData struct { Arguments any `json:"arguments,omitempty"` // When true, the tool output should be displayed expanded (verbatim) in the CLI timeline DisplayVerbatim *bool `json:"displayVerbatim,omitempty"` + // Experimental HydraFusion attribution for this tool execution. + // Experimental: Fusion is part of an experimental API and may change or be removed. + Fusion *FusionAttribution `json:"fusion,omitempty"` // Name of the MCP server hosting this tool, when the tool is an MCP tool MCPServerName *string `json:"mcpServerName,omitempty"` // Original tool name on the MCP server, when the tool is an MCP tool @@ -2046,7 +2711,8 @@ 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"` + // Per-request treatment/eligibility signal returned by the Copilot API in the `X-GitHub-Copilot-Request-TE` response header for the associated model call; `false` when the header was absent or unparseable. + 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 @@ -2207,20 +2873,36 @@ func (*SessionWorkspaceFileChangedData) Type() SessionEventType { return SessionEventTypeSessionWorkspaceFileChanged } +// Neutral provider-tagged reasoning content blocks preserved verbatim for round-tripping +// Experimental: AssistantMessageReasoningBlocks is part of an experimental API and may change or be removed. +type AssistantMessageReasoningBlocks struct { + // Provider-native reasoning items or content blocks preserved verbatim, in order. A single response can carry several, and provider signatures or identifiers may depend on their exact content and ordering. + Blocks []any `json:"blocks,omitzero"` + // Model provider that produced these reasoning blocks. + Provider string `json:"provider"` +} + // Neutral provider-tagged server-side tool-use payload (tool search, advisor) for verbatim round-tripping // Experimental: AssistantMessageServerTools is part of an experimental API and may change or be removed. type AssistantMessageServerTools struct { - AdvisorModel *string `json:"advisorModel,omitempty"` + // Advisor model identifier associated with the server-tool payload. + AdvisorModel *string `json:"advisorModel,omitempty"` + // Provider function-call namespaces keyed by function-call identifier. FunctionCallNamespaces map[string]string `json:"functionCallNamespaces,omitzero"` - Items []any `json:"items,omitzero"` - Provider string `json:"provider"` - RawContentBlocks []any `json:"rawContentBlocks,omitzero"` + // Provider-native server-tool call and output items preserved verbatim for replay. + Items []any `json:"items,omitzero"` + // Model provider that produced this server-tool payload. + Provider string `json:"provider"` + // Raw provider content blocks retained for verbatim round-tripping. + RawContentBlocks []any `json:"rawContentBlocks,omitzero"` } // A tool invocation request from the assistant type AssistantMessageToolRequest struct { // Arguments to pass to the tool, format depends on the tool Arguments any `json:"arguments,omitempty"` + // Hosted program that requested this client tool call + Caller *AssistantMessageToolRequestCaller `json:"caller,omitempty"` // Resolved intention summary describing what this specific call does IntentionSummary *string `json:"intentionSummary,omitempty"` // Name of the MCP server hosting this tool, when the tool is an MCP tool @@ -2237,6 +2919,14 @@ type AssistantMessageToolRequest struct { Type *AssistantMessageToolRequestType `json:"type,omitempty"` } +// Hosted program that requested this client tool call +type AssistantMessageToolRequestCaller struct { + // Provider-assigned identifier for the hosted caller. + CallerID string `json:"callerId"` + // Kind of hosted caller that requested the client tool call. + Type AssistantMessageToolRequestCallerType `json:"type"` +} + // Per-request cost and usage data from the CAPI copilot_usage response field type AssistantUsageCopilotUsage struct { // Itemized token usage breakdown @@ -2506,7 +3196,27 @@ type CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail struct { TokenType string `json:"tokenType"` } -// A single loaded custom agent in `session.custom_agents_updated`, with identity, source, tools, invocability, and model override. +// Inclusive durable event range summarized by a completion receipt. +type CompletionReceiptEventRange struct { + // Identifier of the assistant turn-end event that ends the covered exchange. Always equals the receipt's sourceEventId, so either field is a valid join key. + EndEventID string `json:"endEventId"` + // Identifier of the user message that starts the covered exchange. + StartEventID string `json:"startEventId"` +} + +// Final structured tool completion in the covered event range. +type CompletionReceiptFinalTool struct { + // Process exit code from a structured shell result, when available. + ExitCode *int64 `json:"exitCode,omitempty"` + // Structured success or failure status from the tool completion event. + Status CompletionReceiptToolStatus `json:"status"` + // Unique identifier of the completed tool call. + ToolCallID string `json:"toolCallId"` + // Tool name from the matching tool execution start event, when available. + ToolName *string `json:"toolName,omitempty"` +} + +// A single loaded custom agent in `session.custom_agents_updated`, with identity, source, tools, invocability, and authored model configuration. type CustomAgentsUpdatedAgent struct { // Description of what the agent does Description string `json:"description"` @@ -2516,6 +3226,10 @@ type CustomAgentsUpdatedAgent struct { ID string `json:"id"` // Model override for this agent, if set Model *string `json:"model,omitempty"` + // Whether authored models are preferences or required constraints + ModelPolicy *AgentModelPolicy `json:"modelPolicy,omitempty"` + // Authored model ids in priority order, if configured + Models []string `json:"models,omitzero"` // Internal name of the agent Name string `json:"name"` // Source location: user, project, inherited, remote, or plugin @@ -2556,6 +3270,96 @@ type FactoryPermissionPhase struct { Title string `json:"title"` } +// Experimental attribution linking an ordinary event to the HydraFusion turn, phase, and concrete source that produced it. +// Experimental: FusionAttribution is part of an experimental API and may change or be removed. +type FusionAttribution struct { + // Idempotency identifier for the authoritative commit, when the event belongs to the selected output. + CommitID *string `json:"commitId,omitempty"` + // Conversation scope in which the concrete phase executed. + ConversationScope *string `json:"conversationScope,omitempty"` + // Stable identifier for the HydraFusion turn that produced the event. + FusionID string `json:"fusionId"` + // HydraFusion orchestration pattern selected for the turn. + Pattern string `json:"pattern"` + // Identifier of the concrete phase that produced the event. + PhaseID *string `json:"phaseId,omitempty"` + // Kind of concrete phase that produced the event. + PhaseKind *string `json:"phaseKind,omitempty"` + // HydraFusion routing policy used for the turn. + Policy string `json:"policy"` + // Semantic role assigned to the concrete phase. + Role *string `json:"role,omitempty"` + // Concrete model that produced the attributed event. + SourceModel *string `json:"sourceModel,omitempty"` + // Phase whose output supplied the authoritative content, when different from the executing phase. + SourcePhaseID *string `json:"sourcePhaseId,omitempty"` + // Synthetic HydraFusion model selected for the session. + SyntheticModel string `json:"syntheticModel"` +} + +// Durable server recommendation for subsequent HydraFusion turns. +// Experimental: FusionFollowUpRecommendation is part of an experimental API and may change or be removed. +type FusionFollowUpRecommendation struct { + // Recommended routing action for the next compaction turn. + CompactionTurn FusionFollowUpAction `json:"compactionTurn"` + // Recommended routing action for the next user-message turn. + UserTurn FusionFollowUpAction `json:"userTurn"` +} + +// Presentation-neutral phase planned for a HydraFusion turn. +// Experimental: FusionPhasePlanStep is part of an experimental API and may change or be removed. +type FusionPhasePlanStep struct { + // Whether the phase executes only when an earlier phase requests it. + Conditional bool `json:"conditional"` + // Kind of phase that may execute. + Kind FusionPhaseKind `json:"kind"` + // Semantic role assigned to the phase. + Role string `json:"role"` + // Conversation scope in which the phase executes. + Scope FusionConversationScope `json:"scope"` +} + +// Aggregate concrete-model usage for one HydraFusion phase. +// Experimental: FusionPhaseUsage is part of an experimental API and may change or be removed. +type FusionPhaseUsage struct { + // Total cached input tokens reported for the phase. + CachedTokens int64 `json:"cachedTokens"` + // Total tokens written to prompt cache during the phase. + CacheWriteTokens *int64 `json:"cacheWriteTokens,omitempty"` + // Total input tokens consumed by the phase. + InputTokens int64 `json:"inputTokens"` + // Total output tokens produced by the phase. + OutputTokens int64 `json:"outputTokens"` + // Number of concrete model requests made by the phase. + RequestCount int64 `json:"requestCount"` + // Total normalized AI-unit cost reported for the phase, in nano-AIU. + TotalNanoAiu float64 `json:"totalNanoAiu"` +} + +// Validated HydraFusion routing capability scores. +// Experimental: FusionScores is part of an experimental API and may change or be removed. +type FusionScores struct { + // Code-generation capability score returned by the authenticated router. + CodeGen float64 `json:"codeGen"` + // Debugging capability score returned by the authenticated router. + Debugging float64 `json:"debugging"` + // Reasoning capability score returned by the authenticated router. + Reasoning float64 `json:"reasoning"` + // Tool-use capability score returned by the authenticated router. + ToolUse float64 `json:"toolUse"` +} + +// Internal durable terminal request staged by a HydraFusion phase until an idempotent final commit selects it. +// Experimental: FusionStagedTerminal is part of an experimental API and may change or be removed. +// Internal: FusionStagedTerminal is an internal SDK API and is not part of the public surface. +type FusionStagedTerminal struct { + Arguments string `json:"arguments"` + AssistantMessage any `json:"assistantMessage"` + PhaseID string `json:"phaseId"` + ToolCallID string `json:"toolCallId"` + ToolName string `json:"toolName"` +} + // Per-session configuration for the built-in GitHub MCP server type GitHubMCPToolConfig struct { // Additional GitHub MCP tools requested by the session @@ -2684,17 +3488,17 @@ type ModelCallFailureRequestFingerprint struct { ToolResultMessageCount int64 `json:"toolResultMessageCount"` } -// 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 { +// Assisted-approval judge information attached to a permission request. Present only in assisted mode; its absence means the judge did not evaluate the request. The `recommendation` conveys the judge's disposition for this request. +// Experimental: PermissionAssistedApproval is part of an experimental API and may change or be removed. +type PermissionAssistedApproval struct { // Classified cause of an `error` recommendation. Absent for every other recommendation. - FailureReason *AutoApprovalJudgeFailureReason `json:"failureReason,omitempty"` + FailureReason *AssistedApprovalJudgeFailureReason `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. - Recommendation AutoApprovalRecommendation `json:"recommendation"` + // The assisted-approval safety judge's outcome for this request. + Recommendation AssistedApprovalRecommendation `json:"recommendation"` } // Derived user-facing permission prompt details for UI consumers @@ -2715,9 +3519,9 @@ func (r RawPermissionPromptRequest) Kind() PermissionPromptRequestKind { // Shell command permission prompt type PermissionPromptRequestCommands struct { - // 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"` + // Assisted-approval judge information for this request; present only in assisted mode. + // Experimental: AssistedApproval is part of an experimental API and may change or be removed. + AssistedApproval *PermissionAssistedApproval `json:"assistedApproval,omitempty"` // Whether the UI can offer session-wide approval for this command pattern CanOfferSessionApproval bool `json:"canOfferSessionApproval"` // Command identifiers covered by this approval prompt @@ -2743,9 +3547,9 @@ func (PermissionPromptRequestCommands) Kind() PermissionPromptRequestKind { type PermissionPromptRequestCustomTool struct { // Arguments to pass to the custom tool Args any `json:"args,omitempty"` - // 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"` + // Assisted-approval judge information for this request; present only in assisted mode. + // Experimental: AssistedApproval is part of an experimental API and may change or be removed. + AssistedApproval *PermissionAssistedApproval `json:"assistedApproval,omitempty"` // Tool call ID that triggered this permission request ToolCallID *string `json:"toolCallId,omitempty"` // Description of what the custom tool does @@ -2759,11 +3563,29 @@ func (PermissionPromptRequestCustomTool) Kind() PermissionPromptRequestKind { return PermissionPromptRequestKindCustomTool } +// Extension sensitive environment variable access prompt +type PermissionPromptRequestExtensionEnvAccess struct { + // Assisted-approval judge information for this request; present only in assisted mode. + // Experimental: AssistedApproval is part of an experimental API and may change or be removed. + AssistedApproval *PermissionAssistedApproval `json:"assistedApproval,omitempty"` + // Names of the sensitive environment variables the extension is requesting. Values never appear here. + EnvironmentVariables []string `json:"environmentVariables"` + // Name of the extension requesting environment variable access + ExtensionName string `json:"extensionName"` + // Tool call ID that triggered this permission request + ToolCallID *string `json:"toolCallId,omitempty"` +} + +func (PermissionPromptRequestExtensionEnvAccess) permissionPromptRequest() {} +func (PermissionPromptRequestExtensionEnvAccess) Kind() PermissionPromptRequestKind { + return PermissionPromptRequestKindExtensionEnvAccess +} + // Extension management permission prompt type PermissionPromptRequestExtensionManagement struct { - // 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"` + // Assisted-approval judge information for this request; present only in assisted mode. + // Experimental: AssistedApproval is part of an experimental API and may change or be removed. + AssistedApproval *PermissionAssistedApproval `json:"assistedApproval,omitempty"` // Name of the extension being managed ExtensionName *string `json:"extensionName,omitempty"` // The extension management operation (scaffold, reload) @@ -2779,9 +3601,9 @@ func (PermissionPromptRequestExtensionManagement) Kind() PermissionPromptRequest // Extension permission access prompt type PermissionPromptRequestExtensionPermissionAccess struct { - // 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"` + // Assisted-approval judge information for this request; present only in assisted mode. + // Experimental: AssistedApproval is part of an experimental API and may change or be removed. + AssistedApproval *PermissionAssistedApproval `json:"assistedApproval,omitempty"` // Capabilities the extension is requesting Capabilities []string `json:"capabilities"` // Name of the extension requesting permission access @@ -2799,15 +3621,19 @@ func (PermissionPromptRequestExtensionPermissionAccess) Kind() PermissionPromptR 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"` + // Assisted-approval judge information for this request; present only in assisted mode. + // Experimental: AssistedApproval is part of an experimental API and may change or be removed. + AssistedApproval *PermissionAssistedApproval `json:"assistedApproval,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"` + CanPersistApproval bool `json:"canPersistApproval"` + // Factory-declared AI-credit limit before any run/resume caller override is applied. + DeclaredMaxAiCredits *float64 `json:"declaredMaxAiCredits,omitempty"` + // Factory-declared concurrent-subagent limit before any run/resume caller override is applied. + DeclaredMaxConcurrentSubagents *int64 `json:"declaredMaxConcurrentSubagents,omitempty"` + // Factory-declared total-subagent limit before any run/resume caller override is applied. + DeclaredMaxTotalSubagents *int64 `json:"declaredMaxTotalSubagents,omitempty"` + // Factory-declared active-time limit in seconds before any run/resume caller override is applied. + DeclaredTimeoutSeconds *float64 `json:"declaredTimeoutSeconds,omitempty"` // Factory description Description string `json:"description"` // Whether managed policy requires a human response and forbids host auto-approval @@ -2837,9 +3663,9 @@ func (PermissionPromptRequestFactory) Kind() PermissionPromptRequestKind { // Hook confirmation permission prompt type PermissionPromptRequestHook struct { - // 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"` + // Assisted-approval judge information for this request; present only in assisted mode. + // Experimental: AssistedApproval is part of an experimental API and may change or be removed. + AssistedApproval *PermissionAssistedApproval `json:"assistedApproval,omitempty"` // Optional message from the hook explaining why confirmation is needed HookMessage *string `json:"hookMessage,omitempty"` // Arguments of the tool call being gated @@ -2859,9 +3685,14 @@ func (PermissionPromptRequestHook) Kind() PermissionPromptRequestKind { type PermissionPromptRequestMCP struct { // Arguments to pass to the MCP tool Args any `json:"args,omitempty"` - // 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"` + // Assisted-approval judge information for this request; present only in assisted mode. + // Experimental: AssistedApproval is part of an experimental API and may change or be removed. + AssistedApproval *PermissionAssistedApproval `json:"assistedApproval,omitempty"` + // Whether the host may offer a server-wide "approve all tools from this server" blanket. Absent is treated as true; the runtime sends false when managed policy disables bypass-permissions mode, which forbids the server-wide escalation while still allowing per-tool approval. + CanOfferServerWideApproval *bool `json:"canOfferServerWideApproval,omitempty"` + // Advisory runtime permission recommendation. The host remains responsible for deciding the request and may reject it. + // Experimental: PermissionRecommendation is part of an experimental API and may change or be removed. + PermissionRecommendation *PermissionRecommendation `json:"permissionRecommendation,omitempty"` // Name of the MCP server providing the tool ServerName string `json:"serverName"` // Tool call ID that triggered this permission request @@ -2881,9 +3712,9 @@ func (PermissionPromptRequestMCP) Kind() PermissionPromptRequestKind { type PermissionPromptRequestMemory struct { // Whether this is a store or vote memory operation Action *PermissionRequestMemoryAction `json:"action,omitempty"` - // 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"` + // Assisted-approval judge information for this request; present only in assisted mode. + // Experimental: AssistedApproval is part of an experimental API and may change or be removed. + AssistedApproval *PermissionAssistedApproval `json:"assistedApproval,omitempty"` // Source references for the stored fact (store only) Citations *string `json:"citations,omitempty"` // Vote direction (vote only) @@ -2907,9 +3738,9 @@ func (PermissionPromptRequestMemory) Kind() PermissionPromptRequestKind { type PermissionPromptRequestPath struct { // Underlying permission kind that needs path approval AccessKind PermissionPromptRequestPathAccessKind `json:"accessKind"` - // 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"` + // Assisted-approval judge information for this request; present only in assisted mode. + // Experimental: AssistedApproval is part of an experimental API and may change or be removed. + AssistedApproval *PermissionAssistedApproval `json:"assistedApproval,omitempty"` // File paths that require explicit approval Paths []string `json:"paths"` // Tool call ID that triggered this permission request @@ -2923,9 +3754,9 @@ func (PermissionPromptRequestPath) Kind() PermissionPromptRequestKind { // File read permission prompt type PermissionPromptRequestRead struct { - // 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"` + // Assisted-approval judge information for this request; present only in assisted mode. + // Experimental: AssistedApproval is part of an experimental API and may change or be removed. + AssistedApproval *PermissionAssistedApproval `json:"assistedApproval,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 @@ -2943,9 +3774,9 @@ func (PermissionPromptRequestRead) Kind() PermissionPromptRequestKind { // URL access permission prompt type PermissionPromptRequestURL struct { - // 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"` + // Assisted-approval judge information for this request; present only in assisted mode. + // Experimental: AssistedApproval is part of an experimental API and may change or be removed. + AssistedApproval *PermissionAssistedApproval `json:"assistedApproval,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 @@ -2969,9 +3800,9 @@ func (PermissionPromptRequestURL) Kind() PermissionPromptRequestKind { // File write permission prompt type PermissionPromptRequestWrite struct { - // 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"` + // Assisted-approval judge information for this request; present only in assisted mode. + // Experimental: AssistedApproval is part of an experimental API and may change or be removed. + AssistedApproval *PermissionAssistedApproval `json:"assistedApproval,omitempty"` // Whether the UI can offer session-wide approval for file write operations CanOfferSessionApproval bool `json:"canOfferSessionApproval"` // Unified diff showing the proposed changes @@ -3016,6 +3847,8 @@ type PermissionRequestCustomTool struct { 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 the tool declared that permission may be skipped unless a deny rule matches + SkipPermission *bool `json:"skipPermission,omitempty"` // Tool call ID that triggered this permission request ToolCallID *string `json:"toolCallId,omitempty"` // Description of what the custom tool does @@ -3029,6 +3862,23 @@ func (PermissionRequestCustomTool) Kind() PermissionRequestKind { return PermissionRequestKindCustomTool } +// Extension sensitive environment variable access request +type PermissionRequestExtensionEnvAccess struct { + // Names of the sensitive environment variables the extension is requesting. Values never appear here. + EnvironmentVariables []string `json:"environmentVariables"` + // Name of the extension requesting environment variable 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"` +} + +func (PermissionRequestExtensionEnvAccess) permissionRequest() {} +func (PermissionRequestExtensionEnvAccess) Kind() PermissionRequestKind { + return PermissionRequestKindExtensionEnvAccess +} + // Extension management permission request type PermissionRequestExtensionManagement struct { // Name of the extension being managed @@ -3068,11 +3918,15 @@ 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"` + CanPersistApproval bool `json:"canPersistApproval"` + // Factory-declared AI-credit limit before any run/resume caller override is applied. + DeclaredMaxAiCredits *float64 `json:"declaredMaxAiCredits,omitempty"` + // Factory-declared concurrent-subagent limit before any run/resume caller override is applied. + DeclaredMaxConcurrentSubagents *int64 `json:"declaredMaxConcurrentSubagents,omitempty"` + // Factory-declared total-subagent limit before any run/resume caller override is applied. + DeclaredMaxTotalSubagents *int64 `json:"declaredMaxTotalSubagents,omitempty"` + // Factory-declared active-time limit in seconds before any run/resume caller override is applied. + 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. @@ -3125,6 +3979,9 @@ type PermissionRequestMCP struct { 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"` + // Advisory runtime permission recommendation. The SDK host remains responsible for deciding the request and may reject it. + // Experimental: PermissionRecommendation is part of an experimental API and may change or be removed. + PermissionRecommendation *PermissionRecommendation `json:"permissionRecommendation,omitempty"` // Whether this MCP tool is read-only (no side effects) ReadOnly bool `json:"readOnly"` // Name of the MCP server providing the tool @@ -3146,6 +4003,9 @@ func (PermissionRequestMCP) Kind() PermissionRequestKind { type PermissionRequestMemory struct { // Whether this is a store or vote memory operation Action *PermissionRequestMemoryAction `json:"action,omitempty"` + // Assisted-approval judge information for this request; present only in assisted mode. + // Experimental: AssistedApproval is part of an experimental API and may change or be removed. + AssistedApproval *PermissionAssistedApproval `json:"assistedApproval,omitempty"` // Source references for the stored fact (store only) Citations *string `json:"citations,omitempty"` // Vote direction (vote only) @@ -3156,6 +4016,10 @@ type PermissionRequestMemory struct { ManagedApprovalRequired *bool `json:"managedApprovalRequired,omitempty"` // Reason for the vote (vote only) Reason *string `json:"reason,omitempty"` + // Repository name with owner associated with the stored memory (store only) + RepoNwo *string `json:"repoNwo,omitempty"` + // Scope of the stored memory (store only) + Scope *PermissionRequestMemoryScope `json:"scope,omitempty"` // Topic or subject of the memory (store only) Subject *string `json:"subject,omitempty"` // Tool call ID that triggered this permission request @@ -3313,6 +4177,8 @@ func (r RawPermissionResult) Kind() PermissionResultKind { // Permission response variant indicating the request was approved without persisting an approval rule. type PermissionApproved struct { + // Whether a managed approval policy already handled this request + ManagedApprovalHandled *bool `json:"managedApprovalHandled,omitempty"` } func (PermissionApproved) permissionResult() {} @@ -3326,6 +4192,8 @@ type PermissionApprovedForLocation struct { Approval UserToolSessionApproval `json:"approval"` // The location key (git root or cwd) to persist the approval to LocationKey string `json:"locationKey"` + // Whether a managed approval policy already handled this request + ManagedApprovalHandled *bool `json:"managedApprovalHandled,omitempty"` } func (PermissionApprovedForLocation) permissionResult() {} @@ -3337,6 +4205,8 @@ func (PermissionApprovedForLocation) Kind() PermissionResultKind { type PermissionApprovedForSession struct { // The approval to add as a session-scoped rule Approval UserToolSessionApproval `json:"approval"` + // Whether a managed approval policy already handled this request + ManagedApprovalHandled *bool `json:"managedApprovalHandled,omitempty"` } func (PermissionApprovedForSession) permissionResult() {} @@ -3508,6 +4378,20 @@ type SessionLimitsExhaustedResponse struct { MaxAiCredits *float64 `json:"maxAiCredits,omitempty"` } +// Usage attributed to one agent instance at session shutdown. +type ShutdownAgentMetric struct { + // Human-readable label for this subagent invocation, copied from the originating `subagent.started` event. For task-tool subagents this is the invocation's task description rather than the agent's configured display name, so group by `agentName` for stable per-agent labels. + AgentDisplayName *string `json:"agentDisplayName,omitempty"` + // Configured agent name, when this is a subagent + AgentName *string `json:"agentName,omitempty"` + // Per-model usage for this agent, keyed by model identifier + ModelMetrics map[string]ShutdownModelMetric `json:"modelMetrics"` + // Time spent in model API calls by this agent, in milliseconds + TotalAPIDurationMs int64 `json:"totalApiDurationMs"` + // Accumulated nano-AI units cost for this agent + TotalNanoAiu float64 `json:"totalNanoAiu"` +} + // Aggregate code change metrics for the session type ShutdownCodeChanges struct { // List of file paths that were modified during the session @@ -3571,6 +4455,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 @@ -3579,7 +4465,7 @@ type SkillsLoadedSkill struct { Name string `json:"name"` // Absolute path to the skill file, if available Path *string `json:"path,omitempty"` - // Source location type (e.g., project, personal-copilot, plugin, builtin) + // Source location type (e.g., project, personal-copilot, plugin, builtin, remote, sdk) Source SkillSource `json:"source"` // Whether the skill can be invoked by the user as a slash command UserInvocable bool `json:"userInvocable"` @@ -3611,12 +4497,14 @@ func (r RawSystemNotification) Type() SystemNotificationType { // System notification metadata for a background agent that completed or failed, including agent ID, type, status, description, and prompt. type SystemNotificationAgentCompleted struct { - // Unique identifier of the background agent + // Unique task identifier AgentID string `json:"agentId"` // Type of the agent (e.g., explore, task, general-purpose) AgentType string `json:"agentType"` // Human-readable description of the agent task Description *string `json:"description,omitempty"` + // Friendly, non-unique name intended for display + DisplayName *string `json:"displayName,omitempty"` // The full prompt given to the background agent Prompt *string `json:"prompt,omitempty"` // Whether the agent completed successfully or failed @@ -3630,12 +4518,14 @@ func (SystemNotificationAgentCompleted) Type() SystemNotificationType { // System notification metadata for a background agent that became idle, including agent ID, type, and description. type SystemNotificationAgentIdle struct { - // Unique identifier of the background agent + // Unique task identifier AgentID string `json:"agentId"` // Type of the agent (e.g., explore, task, general-purpose) AgentType string `json:"agentType"` // Human-readable description of the agent task Description *string `json:"description,omitempty"` + // Friendly, non-unique name intended for display + DisplayName *string `json:"displayName,omitempty"` } func (SystemNotificationAgentIdle) systemNotification() {} @@ -3827,6 +4717,8 @@ type ToolExecutionCompleteContentShellExit struct { Cwd *string `json:"cwd,omitempty"` // Exit code from the completed shell command ExitCode int64 `json:"exitCode"` + // Path reported in the shell session's filesystem namespace when shell output exceeded the configured large-output threshold. + OutputFilePath *string `json:"outputFilePath,omitempty"` // Output associated with this shell command, if available. May be partial, truncated, or a preview; not guaranteed to be full output. OutputPreview *string `json:"outputPreview,omitempty"` // Whether outputPreview is known to be incomplete or truncated @@ -3962,18 +4854,24 @@ type ToolExecutionCompleteUIResourceMeta struct { // MCP Apps UI resource metadata for a completed tool result, including CSP, permissions, domain, and border preference. type ToolExecutionCompleteUIResourceMetaUI struct { // CSP domain allowlists for an MCP Apps UI resource, including connect, resource, frame, and base URI domains. - Csp *ToolExecutionCompleteUIResourceMetaUICsp `json:"csp,omitempty"` - Domain *string `json:"domain,omitempty"` + Csp *ToolExecutionCompleteUIResourceMetaUICsp `json:"csp,omitempty"` + // Optional dedicated origin for the rendered MCP Apps UI resource. + Domain *string `json:"domain,omitempty"` // Browser permission metadata for an MCP Apps UI resource, including camera, microphone, geolocation, and clipboard-write. - Permissions *ToolExecutionCompleteUIResourceMetaUIPermissions `json:"permissions,omitempty"` - PrefersBorder *bool `json:"prefersBorder,omitempty"` + Permissions *ToolExecutionCompleteUIResourceMetaUIPermissions `json:"permissions,omitempty"` + // Whether the host should render a border around the MCP Apps UI resource. + PrefersBorder *bool `json:"prefersBorder,omitempty"` } // CSP domain allowlists for an MCP Apps UI resource, including connect, resource, frame, and base URI domains. type ToolExecutionCompleteUIResourceMetaUICsp struct { - BaseURIDomains []string `json:"baseUriDomains,omitzero"` - ConnectDomains []string `json:"connectDomains,omitzero"` - FrameDomains []string `json:"frameDomains,omitzero"` + // Domains the UI resource may use as document base URIs. + BaseURIDomains []string `json:"baseUriDomains,omitzero"` + // Domains the UI resource may connect to. + ConnectDomains []string `json:"connectDomains,omitzero"` + // Domains the UI resource may embed as nested frames. + FrameDomains []string `json:"frameDomains,omitzero"` + // Domains from which the UI resource may load scripts, styles, images, and other resources. ResourceDomains []string `json:"resourceDomains,omitzero"` } @@ -4074,6 +4972,37 @@ type WorkingDirectoryContext struct { RepositoryHost *string `json:"repositoryHost,omitempty"` } +// What the agent was doing when the user interrupted it. +type AgentInterruptedActivity string + +const ( + // Background sub-agents were running while the main loop was idle. + AgentInterruptedActivityBackgroundAgent AgentInterruptedActivity = "background_agent" + // A request to the model was open. + AgentInterruptedActivityModelCall AgentInterruptedActivity = "model_call" + // The turn was sleeping between retry attempts. + AgentInterruptedActivityRetryBackoff AgentInterruptedActivity = "retry_backoff" + // One or more tools were executing. + AgentInterruptedActivityToolCall AgentInterruptedActivity = "tool_call" +) + +// Where the interruption landed relative to the first streamed token. +type AgentInterruptedCancelPhase string + +const ( + // The response was already streaming when the request was cancelled. + AgentInterruptedCancelPhaseMidStream AgentInterruptedCancelPhase = "mid_stream" + // No output had been produced when the request was cancelled. + AgentInterruptedCancelPhasePreFirstToken AgentInterruptedCancelPhase = "pre_first_token" +) + +// Hosted program caller type +type AssistantMessageToolRequestCallerType string + +const ( + AssistantMessageToolRequestCallerTypeProgram AssistantMessageToolRequestCallerType = "program" +) + // Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. type AssistantMessageToolRequestType string @@ -4098,36 +5027,46 @@ 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 +// Transport used for a successful model call +type AssistantUsageTransport string + +const ( + // HTTP transport, including SSE streams. + AssistantUsageTransportHTTP AssistantUsageTransport = "http" + // WebSocket transport. + AssistantUsageTransportWebsocket AssistantUsageTransport = "websocket" +) + +// Why the assisted-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: AssistedApprovalJudgeFailureReason is part of an experimental API and may change or be removed. +type AssistedApprovalJudgeFailureReason string const ( // The judge model call was cancelled before it returned. - AutoApprovalJudgeFailureReasonAbort AutoApprovalJudgeFailureReason = "abort" + AssistedApprovalJudgeFailureReasonAbort AssistedApprovalJudgeFailureReason = "abort" // The judge model call completed but returned no content. - AutoApprovalJudgeFailureReasonEmptyResponse AutoApprovalJudgeFailureReason = "empty_response" + AssistedApprovalJudgeFailureReasonEmptyResponse AssistedApprovalJudgeFailureReason = "empty_response" // The judge model call failed (for example a transport, authentication, or rate-limit error). - AutoApprovalJudgeFailureReasonModelError AutoApprovalJudgeFailureReason = "model_error" + AssistedApprovalJudgeFailureReasonModelError AssistedApprovalJudgeFailureReason = "model_error" // The judge model replied, but the reply carried no ALLOW/DENY verdict. - AutoApprovalJudgeFailureReasonParseError AutoApprovalJudgeFailureReason = "parse_error" + AssistedApprovalJudgeFailureReasonParseError AssistedApprovalJudgeFailureReason = "parse_error" // The judge model call exceeded its deadline. - AutoApprovalJudgeFailureReasonTimeout AutoApprovalJudgeFailureReason = "timeout" + AssistedApprovalJudgeFailureReasonTimeout AssistedApprovalJudgeFailureReason = "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 +// Outcome of the assisted-approval safety judge for a permission request. Present only in assisted mode; its absence means the judge did not evaluate the request. +// Experimental: AssistedApprovalRecommendation is part of an experimental API and may change or be removed. +type AssistedApprovalRecommendation string const ( // The judge evaluated the request and recommends automatically approving it. - AutoApprovalRecommendationApprove AutoApprovalRecommendation = "approve" + AssistedApprovalRecommendationApprove AssistedApprovalRecommendation = "approve" // The judge was consulted but did not return a usable recommendation, so the request requires explicit approval. - AutoApprovalRecommendationError AutoApprovalRecommendation = "error" - // Auto mode is enabled, but this request category is never auto-approvable (for example, sandbox-bypass requests), so the judge was not consulted. - AutoApprovalRecommendationExcluded AutoApprovalRecommendation = "excluded" - // The judge evaluated the request and does not recommend auto-approving it; explicit approval is required. Whether that means prompting, denying, or something else is the consumer's decision. - AutoApprovalRecommendationRequireApproval AutoApprovalRecommendation = "requireApproval" + AssistedApprovalRecommendationError AssistedApprovalRecommendation = "error" + // Assisted mode is enabled, but this request category is never automatically approvable (for example, sandbox-bypass requests), so the judge was not consulted. + AssistedApprovalRecommendationExcluded AssistedApprovalRecommendation = "excluded" + // The judge evaluated the request and does not recommend automatically approving it; explicit approval is required. Whether that means prompting, denying, or something else is the consumer's decision. + AssistedApprovalRecommendationRequireApproval AssistedApprovalRecommendation = "requireApproval" ) // Coarse request-difficulty bucket for UX explainability @@ -4239,6 +5178,34 @@ const ( CompactionTriggerThreshold CompactionTrigger = "threshold" ) +// Runtime reason the completion decision was accepted. +type CompletionReceiptStopReason string + +const ( + // The configured agentStop continuation limit was reached. + CompletionReceiptStopReasonAgentStopBlockLimit CompletionReceiptStopReason = "agent_stop_block_limit" + // The model reached a natural terminal response. + CompletionReceiptStopReasonNatural CompletionReceiptStopReason = "natural" + // A terminal tool ended the interaction. + CompletionReceiptStopReasonTerminalTool CompletionReceiptStopReason = "terminal_tool" +) + +// Structured terminal status from a tool completion event. +type CompletionReceiptToolStatus string + +const ( + // The permissions service denied the tool call. + CompletionReceiptToolStatusDenied CompletionReceiptToolStatus = "denied" + // The tool failed without a more specific structured status. + CompletionReceiptToolStatusFailure CompletionReceiptToolStatus = "failure" + // The user rejected the tool call. + CompletionReceiptToolStatusRejected CompletionReceiptToolStatus = "rejected" + // The tool completed successfully. + CompletionReceiptToolStatusSuccess CompletionReceiptToolStatus = "success" + // The tool exceeded its time budget. + CompletionReceiptToolStatusTimeout CompletionReceiptToolStatus = "timeout" +) + // The user action: "accept" (submitted form), "decline" (explicitly refused), or "cancel" (dismissed) type ElicitationCompletedAction string @@ -4320,6 +5287,126 @@ const ( FactoryPermissionOperationRun FactoryPermissionOperation = "run" ) +// Terminal status a factory run committed. A settled run is never `pending` or `running`, so those two members of the run-status domain are deliberately absent. +type FactoryRunSettledStatus string + +const ( + // The run was cancelled by its caller or by session disposal. + FactoryRunSettledStatusCancelled FactoryRunSettledStatus = "cancelled" + // The factory body resolved and its result was committed. + FactoryRunSettledStatusCompleted FactoryRunSettledStatus = "completed" + // The run failed, with `failureType` carrying the class when it has one. + FactoryRunSettledStatusError FactoryRunSettledStatus = "error" + // The run was stopped by a limit, an approval refusal or another policy decision. + FactoryRunSettledStatusHalted FactoryRunSettledStatus = "halted" +) + +// Conversation scope in which a HydraFusion phase executes. +// Experimental: FusionConversationScope is part of an experimental API and may change or be removed. +type FusionConversationScope string + +const ( + // Isolated read-only review history that does not enter the root conversation. + FusionConversationScopeReview FusionConversationScope = "review" + // Canonical root conversation history. + FusionConversationScopeRoot FusionConversationScope = "root" +) + +// Server-recommended routing behavior for a later HydraFusion turn. +// Experimental: FusionFollowUpAction is part of an experimental API and may change or be removed. +type FusionFollowUpAction string + +const ( + // Request a new routing decision. + FusionFollowUpActionReroute FusionFollowUpAction = "reroute" + // Reuse the durable primary model without routing. + FusionFollowUpActionReusePrimary FusionFollowUpAction = "reuse_primary" +) + +// Validated HydraFusion execution pattern. +// Experimental: FusionPattern is part of an experimental API and may change or be removed. +type FusionPattern string + +const ( + // Run a primary phase, a judge, and an optional repair. + FusionPatternCascade FusionPattern = "cascade" + // Run a primary draft, a read-only critique, and a revision. + FusionPatternCritique FusionPattern = "critique" + // Run one primary solver phase. + FusionPatternSingle FusionPattern = "single" +) + +// Content-safe activity observed while a HydraFusion phase is running. +// Experimental: FusionPhaseActivityKind is part of an experimental API and may change or be removed. +type FusionPhaseActivityKind string + +const ( + // The provider produced additional private output bytes. + FusionPhaseActivityKindModelOutput FusionPhaseActivityKind = "model_output" + // A tool finished executing inside the phase. + FusionPhaseActivityKindToolCompleted FusionPhaseActivityKind = "tool_completed" + // A tool began executing inside the phase. + FusionPhaseActivityKindToolStarted FusionPhaseActivityKind = "tool_started" +) + +// HydraFusion phase kind. +// Experimental: FusionPhaseKind is part of an experimental API and may change or be removed. +type FusionPhaseKind string + +const ( + // Read-only critique phase. + FusionPhaseKindCritic FusionPhaseKind = "critic" + // Initial critique-pattern draft phase. + FusionPhaseKindDraft FusionPhaseKind = "draft" + // Follow-up phase continuing from the resolved model. + FusionPhaseKindFollowUp FusionPhaseKind = "follow_up" + // Read-only cascade judge phase. + FusionPhaseKindJudge FusionPhaseKind = "judge" + // Primary solver phase. + FusionPhaseKindPrimary FusionPhaseKind = "primary" + // Cascade repair phase. + FusionPhaseKindRepair FusionPhaseKind = "repair" + // Critique-pattern revision phase. + FusionPhaseKindRevision FusionPhaseKind = "revision" +) + +// Durable outcome status of a HydraFusion phase. +// Experimental: FusionPhaseStatus is part of an experimental API and may change or be removed. +type FusionPhaseStatus string + +const ( + // The phase was cancelled. + FusionPhaseStatusCancelled FusionPhaseStatus = "cancelled" + // The phase failed. + FusionPhaseStatusFailed FusionPhaseStatus = "failed" + // The phase completed successfully. + FusionPhaseStatusSucceeded FusionPhaseStatus = "succeeded" +) + +// How a durable phase checkpoint contributes its exact message to canonical root history. +// Experimental: FusionProjectionMode is part of an experimental API and may change or be removed. +type FusionProjectionMode string + +const ( + // Append the exact root message immediately. + FusionProjectionModeAppend FusionProjectionMode = "append" + // Do not project the checkpoint into root history. + FusionProjectionModeNone FusionProjectionMode = "none" + // Hold a terminal message outside canonical history until the final commit selects it. + FusionProjectionModeStaged FusionProjectionMode = "staged" +) + +// Kind of turn for which HydraFusion routing is running. +// Experimental: FusionTurnKind is part of an experimental API and may change or be removed. +type FusionTurnKind string + +const ( + // A conversation-compaction turn. + FusionTurnKindCompaction FusionTurnKind = "compaction" + // A user-message turn. + FusionTurnKindUser FusionTurnKind = "user" +) + // Origin type of the session being handed off type HandoffSourceType string @@ -4342,27 +5429,35 @@ const ( type ManagedSettingsEnforcedEscalation string const ( - // Full allow-all ("/allow-all on") permissions — auto-approving tools, paths, and URLs. + // Full allow-all permissions — automatically approving tools, paths, and URLs. ManagedSettingsEnforcedEscalationAllowAll ManagedSettingsEnforcedEscalation = "allow_all" - // Auto-approval of all tool permission requests. + // Automatic approval of all tool permission requests. ManagedSettingsEnforcedEscalationApproveAll ManagedSettingsEnforcedEscalation = "approve_all" - // Advisory auto-approval ("/allow-all auto") mode — keeps normal prompt paths and adds LLM-advised approval, distinct from full allow-all. - ManagedSettingsEnforcedEscalationAutoApproval ManagedSettingsEnforcedEscalation = "auto_approval" + // Assisted mode — keeps normal prompt paths and adds an LLM recommendation, distinct from allow-all. + ManagedSettingsEnforcedEscalationAssistedApproval ManagedSettingsEnforcedEscalation = "assisted_approval" + // A server-wide MCP "Always Allow" (or `--allow-tool `) blanket that would auto-approve every tool from an MCP server. Capped to per-tool approval; each tool still prompts. + ManagedSettingsEnforcedEscalationServerWideMCPApproval ManagedSettingsEnforcedEscalation = "server_wide_mcp_approval" // Unrestricted filesystem access outside the session's allowed directories. ManagedSettingsEnforcedEscalationUnrestrictedPaths ManagedSettingsEnforcedEscalation = "unrestricted_paths" // Unrestricted URL fetch access. 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 over policy helper 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). + // A policy helper registered by device or server policy contributed. Device registration takes priority when present. + ManagedSettingsResolvedSourcePolicyHelper ManagedSettingsResolvedSource = "policyHelper" + // Only the server/account channel contributed. ManagedSettingsResolvedSourceServer ManagedSettingsResolvedSource = "server" ) @@ -4477,6 +5572,20 @@ const ( ModelCallFailureTransportWebsocket ModelCallFailureTransport = "websocket" ) +// Final outcome of one logical model dispatch after response acceptance processing +type ModelCallFinishedOutcome string + +const ( + // The dispatch was cancelled before an accepted response was produced. + ModelCallFinishedOutcomeCancelled ModelCallFinishedOutcome = "cancelled" + // The dispatch ended with a provider or transport error. + ModelCallFinishedOutcomeError ModelCallFinishedOutcome = "error" + // The provider response was rejected during post-response acceptance processing. + ModelCallFinishedOutcomeRejected ModelCallFinishedOutcome = "rejected" + // The provider response was accepted for continued agent processing. + ModelCallFinishedOutcomeSuccess ModelCallFinishedOutcome = "success" +) + // Binary result type discriminator. Use "image" for images and "resource" for other binary data. type OmittedBinaryType string @@ -4487,25 +5596,13 @@ const ( OmittedBinaryTypeResource OmittedBinaryType = "resource" ) -// Allow-all mode for the session. -// Experimental: PermissionAllowAllMode is part of an experimental API and may change or be removed. -type PermissionAllowAllMode string - -const ( - // 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. - PermissionAllowAllModeAuto PermissionAllowAllMode = "auto" - // Permission requests follow the normal approval flow. - PermissionAllowAllModeOff PermissionAllowAllMode = "off" - // Tool, path, and URL permission requests are automatically approved. - PermissionAllowAllModeOn PermissionAllowAllMode = "on" -) - // Kind discriminator for PermissionPromptRequest. type PermissionPromptRequestKind string const ( PermissionPromptRequestKindCommands PermissionPromptRequestKind = "commands" PermissionPromptRequestKindCustomTool PermissionPromptRequestKind = "custom-tool" + PermissionPromptRequestKindExtensionEnvAccess PermissionPromptRequestKind = "extension-env-access" PermissionPromptRequestKindExtensionManagement PermissionPromptRequestKind = "extension-management" PermissionPromptRequestKindExtensionPermissionAccess PermissionPromptRequestKind = "extension-permission-access" PermissionPromptRequestKindFactory PermissionPromptRequestKind = "factory" @@ -4530,11 +5627,21 @@ const ( PermissionPromptRequestPathAccessKindWrite PermissionPromptRequestPathAccessKind = "write" ) +// Advisory recommendation the runtime attaches to a permission request whose origin it can vouch for by construction. Unlike the auto-approval judge this does not depend on auto mode and does not evaluate what the tool call does; its absence simply means the runtime has no opinion and the request follows the host's normal approval flow. +// Experimental: PermissionRecommendation is part of an experimental API and may change or be removed. +type PermissionRecommendation string + +const ( + // The runtime vouches for the request's origin and recommends approving it without prompting. The host still owns the decision and may deny it; deny rules, managed policy, and the auto-approval safety judge all outrank this recommendation. + PermissionRecommendationApprove PermissionRecommendation = "approve" +) + // Kind discriminator for PermissionRequest. type PermissionRequestKind string const ( PermissionRequestKindCustomTool PermissionRequestKind = "custom-tool" + PermissionRequestKindExtensionEnvAccess PermissionRequestKind = "extension-env-access" PermissionRequestKindExtensionManagement PermissionRequestKind = "extension-management" PermissionRequestKindExtensionPermissionAccess PermissionRequestKind = "extension-permission-access" PermissionRequestKindFactory PermissionRequestKind = "factory" @@ -4567,6 +5674,16 @@ const ( PermissionRequestMemoryDirectionUpvote PermissionRequestMemoryDirection = "upvote" ) +// Scope of a stored memory. +type PermissionRequestMemoryScope string + +const ( + // Store the memory for the current repository. + PermissionRequestMemoryScopeRepository PermissionRequestMemoryScope = "repository" + // Store the memory for the current user. + PermissionRequestMemoryScopeUser PermissionRequestMemoryScope = "user" +) + // Kind discriminator for PermissionResult. type PermissionResultKind string @@ -4697,18 +5814,6 @@ const ( 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 type ToolExecutionCompleteContentResourceLinkIconTheme string @@ -4752,6 +5857,23 @@ const ( ToolExecutionStartToolDescriptionMetaUIVisibilityModel ToolExecutionStartToolDescriptionMetaUIVisibility = "model" ) +// Lifecycle phase for a Rust-owned ephemeral query stream. +// Experimental: UIEphemeralQueryPhase is part of an experimental API and may change or be removed. +type UIEphemeralQueryPhase string + +const ( + // The ephemeral query stream was cancelled before completing. + UIEphemeralQueryPhaseAborted UIEphemeralQueryPhase = "aborted" + // A partial result chunk was produced by the stream. + UIEphemeralQueryPhaseChunk UIEphemeralQueryPhase = "chunk" + // The ephemeral query stream finished successfully. + UIEphemeralQueryPhaseCompleted UIEphemeralQueryPhase = "completed" + // The ephemeral query stream ended with an error. + UIEphemeralQueryPhaseFailed UIEphemeralQueryPhase = "failed" + // The ephemeral query stream has begun. + UIEphemeralQueryPhaseStarted UIEphemeralQueryPhase = "started" +) + // The agent mode that was active when this message was sent type UserMessageAgentMode string diff --git a/go/samples/chat.go b/go/samples/chat.go index 2f34a243c9..85128a1b56 100644 --- a/go/samples/chat.go +++ b/go/samples/chat.go @@ -5,10 +5,10 @@ import ( "context" "fmt" "os" - "path/filepath" "strings" copilot "github.com/github/copilot-sdk/go" + "github.com/github/copilot-sdk/go/samples/internal/sampleutil" ) const blue = "\033[34m" @@ -16,7 +16,10 @@ const reset = "\033[0m" func main() { ctx := context.Background() - cliPath := filepath.Join("..", "..", "nodejs", "node_modules", "@github", "copilot", "index.js") + cliPath, err := sampleutil.CLIPath() + if err != nil { + panic(err) + } client := copilot.NewClient(&copilot.ClientOptions{Connection: copilot.StdioConnection{Path: cliPath}}) if err := client.Start(ctx); err != nil { panic(err) diff --git a/go/samples/internal/sampleutil/runtime.go b/go/samples/internal/sampleutil/runtime.go new file mode 100644 index 0000000000..5c51f0d8db --- /dev/null +++ b/go/samples/internal/sampleutil/runtime.go @@ -0,0 +1,48 @@ +package sampleutil + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" +) + +// CLIPath resolves the pinned source-checkout runtime used by SDK samples. +func CLIPath() (string, error) { + if cliPath := os.Getenv("COPILOT_CLI_PATH"); cliPath != "" { + return cliPath, nil + } + + current, err := os.Getwd() + if err != nil { + return "", err + } + for { + nodeDir := filepath.Join(current, "nodejs") + if _, err := os.Stat(filepath.Join(nodeDir, "package.json")); err == nil { + command := exec.Command( + "node", + "node_modules/tsx/dist/cli.mjs", + "scripts/prepare-runtime.ts", + "--print-path", + ) + command.Dir = nodeDir + output, err := command.CombinedOutput() + if err != nil { + return "", fmt.Errorf("prepare pinned Copilot CLI: %w: %s", err, output) + } + cliPath := strings.TrimSpace(string(output)) + if info, err := os.Stat(cliPath); err != nil || info.IsDir() { + return "", fmt.Errorf("prepared Copilot CLI path is not a file: %q", cliPath) + } + return cliPath, nil + } + + parent := filepath.Dir(current) + if parent == current { + return "", fmt.Errorf("could not find nodejs/package.json; set COPILOT_CLI_PATH") + } + current = parent + } +} diff --git a/go/samples/manual_tool_resume/main.go b/go/samples/manual_tool_resume/main.go index a7391ff405..62806b7a78 100644 --- a/go/samples/manual_tool_resume/main.go +++ b/go/samples/manual_tool_resume/main.go @@ -4,11 +4,11 @@ import ( "context" "fmt" "os" - "path/filepath" "time" copilot "github.com/github/copilot-sdk/go" "github.com/github/copilot-sdk/go/rpc" + "github.com/github/copilot-sdk/go/samples/internal/sampleutil" ) const timeout = 2 * time.Minute @@ -32,7 +32,10 @@ func manualTool() copilot.Tool { } func newClient() *copilot.Client { - cliPath := filepath.Join("..", "..", "nodejs", "node_modules", "@github", "copilot", "index.js") + cliPath, err := sampleutil.CLIPath() + if err != nil { + panic(err) + } return copilot.NewClient(&copilot.ClientOptions{Connection: copilot.StdioConnection{Path: cliPath}}) } diff --git a/go/session.go b/go/session.go index 99939de4a8..4c31c01e8c 100644 --- a/go/session.go +++ b/go/session.go @@ -56,47 +56,52 @@ type sessionHandler struct { // }) type Session struct { // SessionID is the unique identifier for this session. - SessionID string - workspacePath string - client *jsonrpc2.Client - clientSessionAPIs *rpc.ClientSessionAPIHandlers - handlers []sessionHandler - nextHandlerID uint64 - handlerMutex sync.RWMutex - toolHandlers map[string]ToolHandler - toolHandlersM sync.RWMutex - permissionHandler PermissionHandlerFunc - permissionMux sync.RWMutex - managedSettings bool - mcpAuthHandler MCPAuthHandler - mcpAuthMu sync.RWMutex - userInputHandler UserInputHandler - userInputMux sync.RWMutex - exitPlanModeHandler ExitPlanModeRequestHandler - exitPlanModeMu sync.RWMutex - autoModeSwitchHandler AutoModeSwitchRequestHandler - autoModeSwitchMu sync.RWMutex - hooks *SessionHooks - hooksMux sync.RWMutex - transformCallbacks map[string]SectionTransformFn - transformMu sync.Mutex - commandHandlers map[string]CommandHandler - commandHandlersMu sync.RWMutex - elicitationHandler ElicitationHandler - elicitationMu sync.RWMutex - canvasHandler CanvasHandler - canvasMu sync.RWMutex - bearerTokenProviders map[string]BearerTokenProvider - bearerTokenMu sync.RWMutex - openCanvases []rpc.OpenCanvasInstance - openCanvasesMu sync.RWMutex - capabilities SessionCapabilities - capabilitiesMu sync.RWMutex + SessionID string + workspacePath string + client *jsonrpc2.Client + clientSessionAPIs *rpc.ClientSessionAPIHandlers + handlers []sessionHandler + nextHandlerID uint64 + handlerMutex sync.RWMutex + toolHandlers map[string]ToolHandler + toolHandlersM sync.RWMutex + permissionHandler PermissionHandlerFunc + permissionMux sync.RWMutex + managedSettings bool + mcpAuthHandler MCPAuthHandler + mcpAuthMu sync.RWMutex + userInputHandler UserInputHandler + userInputMux sync.RWMutex + exitPlanModeHandler ExitPlanModeRequestHandler + exitPlanModeMu sync.RWMutex + autoModeSwitchHandler AutoModeSwitchRequestHandler + autoModeSwitchMu sync.RWMutex + hooks *SessionHooks + hooksMux sync.RWMutex + transformCallbacks map[string]SectionTransformFn + transformMu sync.Mutex + commandHandlers map[string]CommandHandler + commandHandlersMu sync.RWMutex + elicitationHandler ElicitationHandler + elicitationMu sync.RWMutex + canvasHandler CanvasHandler + canvasMu sync.RWMutex + bearerTokenProviders map[string]BearerTokenProvider + bearerTokenMu sync.RWMutex + releaseGitHubTokenProvider func() + gitHubTokenProviderMu sync.Mutex + gitHubTokenProviderReleased bool + openCanvases []rpc.OpenCanvasInstance + openCanvasesMu sync.RWMutex + capabilities SessionCapabilities + capabilitiesMu sync.RWMutex // eventCh serializes user event handler dispatch. dispatchEvent enqueues; // a single goroutine (processEvents) dequeues and invokes handlers in FIFO order. + // eventDone stops both sides without closing eventCh while a sender may be active. eventCh chan SessionEvent - closeOnce sync.Once // guards eventCh close so Disconnect is safe to call more than once + eventDone chan struct{} + closeOnce sync.Once // RPC provides typed session-scoped RPC methods. RPC *rpc.SessionRPC @@ -382,6 +387,7 @@ func newSession( toolHandlers: make(map[string]ToolHandler), commandHandlers: make(map[string]CommandHandler), eventCh: make(chan SessionEvent, 128), + eventDone: make(chan struct{}), RPC: rpc.NewSessionRPC(client, sessionID), } s.clientSessionAPIs.Canvas = newCanvasClientSessionAdapter(s) @@ -495,6 +501,9 @@ func (s *Session) SendAndWait(ctx context.Context, options MessageOptions) (*Ses lastAssistantMessage = &eventCopy mu.Unlock() case *SessionIdleData: + if d.Mode != nil && *d.Mode == SessionModeAutopilot { + break + } select { case idleCh <- struct{}{}: default: @@ -1385,14 +1394,10 @@ func (s *Session) dispatchEvent(event SessionEvent) { s.updateOpenCanvasesFromEvent(event) go s.handleBroadcastEvent(event) - // Send to the event channel in a closure with a recover guard. - // Disconnect closes eventCh, and in Go sending on a closed channel - // panics — there is no non-panicking send primitive. We only want - // to suppress that specific panic; other panics are not expected here. - func() { - defer func() { recover() }() - s.eventCh <- event - }() + select { + case s.eventCh <- event: + case <-s.eventDone: + } } // processEvents is the single consumer goroutine for the event channel. @@ -1400,7 +1405,14 @@ func (s *Session) dispatchEvent(event SessionEvent) { // handlers are recovered so that one misbehaving handler does not prevent // others from receiving the event. func (s *Session) processEvents() { - for event := range s.eventCh { + for { + var event SessionEvent + select { + case event = <-s.eventCh: + case <-s.eventDone: + return + } + s.handlerMutex.RLock() handlers := make([]SessionEventHandler, 0, len(s.handlers)) for _, h := range s.handlers { @@ -1421,6 +1433,13 @@ func (s *Session) processEvents() { } } +// stopEventProcessing stops the session event consumer without making an RPC. +// CreateSession/ResumeSession use this when a locally registered session fails +// before it can be returned to the caller. +func (s *Session) stopEventProcessing() { + s.closeOnce.Do(func() { close(s.eventDone) }) +} + // handleBroadcastEvent handles broadcast request events by executing local handlers // and responding via RPC. This implements the protocol v3 broadcast model where tool // calls and permission requests are broadcast as session events to all clients. @@ -1646,6 +1665,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 } @@ -1654,8 +1677,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, }) } @@ -1717,11 +1741,9 @@ func (s *Session) GetEvents(ctx context.Context) ([]SessionEvent, error) { // } func (s *Session) Disconnect() error { _, err := s.client.Request(context.Background(), "session.destroy", sessionDestroyRequest{SessionID: s.SessionID}) - if err != nil { - return fmt.Errorf("failed to disconnect session: %w", err) - } - s.closeOnce.Do(func() { close(s.eventCh) }) + s.stopEventProcessing() + s.releaseGitHubTokenProviderRegistration() // Clear handlers s.handlerMutex.Lock() @@ -1744,9 +1766,38 @@ func (s *Session) Disconnect() error { s.elicitationHandler = nil s.elicitationMu.Unlock() + if err != nil { + return fmt.Errorf("failed to disconnect session: %w", err) + } return nil } +func (s *Session) releaseGitHubTokenProviderRegistration() { + s.gitHubTokenProviderMu.Lock() + if s.gitHubTokenProviderReleased { + s.gitHubTokenProviderMu.Unlock() + return + } + s.gitHubTokenProviderReleased = true + release := s.releaseGitHubTokenProvider + s.releaseGitHubTokenProvider = nil + s.gitHubTokenProviderMu.Unlock() + if release != nil { + release() + } +} + +func (s *Session) setGitHubTokenProviderRegistrationRelease(release func()) { + s.gitHubTokenProviderMu.Lock() + if !s.gitHubTokenProviderReleased { + s.releaseGitHubTokenProvider = release + s.gitHubTokenProviderMu.Unlock() + return + } + s.gitHubTokenProviderMu.Unlock() + release() +} + // Abort aborts the currently processing message in this session. // // Use this to cancel a long-running request. The session remains valid diff --git a/go/session_event_serialization_test.go b/go/session_event_serialization_test.go index bd47fdfbe2..96bf53bb5e 100644 --- a/go/session_event_serialization_test.go +++ b/go/session_event_serialization_test.go @@ -14,6 +14,50 @@ var _ SessionEventData = (*rpc.UserMessageData)(nil) var _ rpc.EmbeddedTextResourceContents = EmbeddedTextResourceContents{} var _ EmbeddedTextResourceContents = rpc.EmbeddedTextResourceContents{} +func TestSessionEventAutoTier(t *testing.T) { + for _, eventType := range []string{"session.start", "session.resume"} { + for _, tier := range []AutoTier{"", AutoTierEfficiency, AutoTierBalance, AutoTierIntelligence} { + t.Run(eventType+"/"+string(tier), func(t *testing.T) { + data := map[string]any{ + "sessionId": "test-session", "version": 1, + "producer": "copilot", "copilotVersion": "1.0.82-1", + "startTime": "2026-08-28T00:00:00Z", + "resumeTime": "2026-08-28T00:00:00Z", "eventCount": 1, + } + if tier != "" { + data["autoTier"] = tier + } + wire, err := json.Marshal(map[string]any{ + "id": "00000000-0000-0000-0000-000000000001", + "timestamp": "2026-08-28T00:00:00Z", "parentId": nil, + "type": eventType, "data": data, + }) + if err != nil { + t.Fatal(err) + } + var event SessionEvent + if err := json.Unmarshal(wire, &event); err != nil { + t.Fatal(err) + } + var actual *AutoTier + switch eventType { + case "session.start": + actual = event.Data.(*SessionStartData).AutoTier + case "session.resume": + actual = event.Data.(*SessionResumeData).AutoTier + } + if tier == "" { + if actual != nil { + t.Fatalf("expected omitted autoTier, got %v", *actual) + } + } else if actual == nil || *actual != tier { + t.Fatalf("expected autoTier %q, got %v", tier, actual) + } + }) + } + } +} + func TestSessionEventAgentIDRoundTripsKnownEvent(t *testing.T) { var event SessionEvent if err := json.Unmarshal([]byte(`{ @@ -189,3 +233,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_test.go b/go/session_test.go index 9c5f4df8c9..8d29cfc88f 100644 --- a/go/session_test.go +++ b/go/session_test.go @@ -18,21 +18,43 @@ import ( ) // newTestSession creates a session with an event channel and starts the consumer goroutine. -// Returns a cleanup function that closes the channel (stopping the consumer). +// Returns a cleanup function that stops the consumer. func newTestSession() (*Session, func()) { s := &Session{ handlers: make([]sessionHandler, 0), commandHandlers: make(map[string]CommandHandler), eventCh: make(chan SessionEvent, 128), + eventDone: make(chan struct{}), } go s.processEvents() - return s, func() { close(s.eventCh) } + return s, s.stopEventProcessing } func newTestEvent() SessionEvent { return SessionEvent{Data: &SessionIdleData{}} } +func TestDispatchEventReturnsAfterEventProcessingStops(t *testing.T) { + session := &Session{ + eventCh: make(chan SessionEvent), + eventDone: make(chan struct{}), + } + + dispatched := make(chan struct{}) + go func() { + session.dispatchEvent(newTestEvent()) + close(dispatched) + }() + + session.stopEventProcessing() + + select { + case <-dispatched: + case <-time.After(time.Second): + t.Fatal("dispatchEvent remained blocked after event processing stopped") + } +} + func ptr[T any](value T) *T { return &value } @@ -366,6 +388,139 @@ func readTestJSONRPCFrame(r io.Reader) ([]byte, error) { return data, err } +func TestSession_SendAndWaitSkipsAutopilotContinuationIdle(t *testing.T) { + stdinR, stdinW := io.Pipe() + stdoutR, stdoutW := io.Pipe() + defer stdinR.Close() + defer stdinW.Close() + defer stdoutR.Close() + defer stdoutW.Close() + + client := jsonrpc2.NewClient(stdinW, stdoutR) + client.Start() + defer client.Stop() + + requestReceived := make(chan struct{}) + errCh := make(chan error, 1) + go func() { + frame, err := readTestJSONRPCFrame(stdinR) + if err != nil { + errCh <- err + return + } + + var request struct { + ID json.RawMessage `json:"id"` + Method string `json:"method"` + } + if err := json.Unmarshal(frame, &request); err != nil { + errCh <- err + return + } + if request.Method != "session.send" { + errCh <- fmt.Errorf("expected session.send, got %s", request.Method) + return + } + + response := map[string]any{ + "jsonrpc": "2.0", + "id": json.RawMessage(request.ID), + "result": map[string]any{"messageId": "message-1"}, + } + data, err := json.Marshal(response) + if err != nil { + errCh <- err + return + } + if _, err := fmt.Fprintf(stdoutW, "Content-Length: %d\r\n\r\n%s", len(data), data); err != nil { + errCh <- err + return + } + close(requestReceived) + }() + + session := &Session{ + SessionID: "session-1", + client: client, + RPC: rpc.NewSessionRPC(client, "session-1"), + handlers: make([]sessionHandler, 0), + eventCh: make(chan SessionEvent, 8), + eventDone: make(chan struct{}), + } + go session.processEvents() + defer session.stopEventProcessing() + + resultCh := make(chan *SessionEvent, 1) + go func() { + result, err := session.SendAndWait(t.Context(), MessageOptions{Prompt: "keep going"}) + if err != nil { + errCh <- err + return + } + resultCh <- result + }() + + select { + case <-requestReceived: + case err := <-errCh: + t.Fatal(err) + case <-time.After(2 * time.Second): + t.Fatal("timed out waiting for session.send request") + } + + continuationIdleProcessed := make(chan struct{}) + unsubscribe := session.On(func(event SessionEvent) { + if idle, ok := event.Data.(*SessionIdleData); ok && + idle.Mode != nil && *idle.Mode == SessionModeAutopilot { + close(continuationIdleProcessed) + } + }) + defer unsubscribe() + + autopilot := SessionModeAutopilot + session.dispatchEvent(SessionEvent{Data: &AssistantMessageData{ + Content: "intermediate", + MessageID: "assistant-1", + }}) + session.dispatchEvent(SessionEvent{Data: &SessionIdleData{Mode: &autopilot}}) + + select { + case <-continuationIdleProcessed: + case err := <-errCh: + t.Fatal(err) + case <-time.After(2 * time.Second): + t.Fatal("timed out waiting for autopilot continuation idle") + } + + select { + case <-resultCh: + t.Fatal("SendAndWait returned at an autopilot continuation idle") + default: + } + + interactive := SessionModeInteractive + session.dispatchEvent(SessionEvent{Data: &AssistantMessageData{ + Content: "final", + MessageID: "assistant-2", + }}) + session.dispatchEvent(SessionEvent{Data: &SessionIdleData{Mode: &interactive}}) + + select { + case result := <-resultCh: + message, ok := result.Data.(*AssistantMessageData) + if !ok { + t.Fatalf("expected assistant message, got %T", result.Data) + } + if message.Content != "final" { + t.Fatalf("expected final assistant message, got %q", message.Content) + } + case err := <-errCh: + t.Fatal(err) + case <-time.After(2 * time.Second): + t.Fatal("timed out waiting for terminal idle") + } +} + func TestSession_On(t *testing.T) { t.Run("multiple handlers all receive events", func(t *testing.T) { session, cleanup := newTestSession() diff --git a/go/test.sh b/go/test.sh index dfb7bac1dd..f5924b3e8d 100755 --- a/go/test.sh +++ b/go/test.sh @@ -15,17 +15,14 @@ fi # Determine COPILOT_CLI_PATH if [ -z "$COPILOT_CLI_PATH" ]; then - # Try to find it relative to the SDK. 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. SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" - POTENTIAL_PATH="$(ls "$SCRIPT_DIR"/../nodejs/node_modules/@github/copilot-*/index.js 2>/dev/null | head -n1)" - if [ -n "$POTENTIAL_PATH" ] && [ -f "$POTENTIAL_PATH" ]; then + if POTENTIAL_PATH="$(cd "$SCRIPT_DIR/../nodejs" && npm run --silent prepare:runtime -- --print-path)" && + [ -n "$POTENTIAL_PATH" ] && [ -f "$POTENTIAL_PATH" ]; then export COPILOT_CLI_PATH="$POTENTIAL_PATH" echo "📍 Auto-detected CLI path: $COPILOT_CLI_PATH" else echo "❌ COPILOT_CLI_PATH environment variable not set" - echo " Run: export COPILOT_CLI_PATH=/path/to/dist-cli/index.js" + echo " Run: export COPILOT_CLI_PATH=/path/to/copilot" exit 1 fi fi diff --git a/go/types.go b/go/types.go index 19689c7074..772202a0bc 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". @@ -188,6 +193,13 @@ type ClientOptions struct { // directory are accessible from GitHub web and mobile. // Ignored when connecting to an existing runtime via [URIConnection]. EnableRemoteSessions bool + // ClientInfo declares the integrating application's identity, forwarded to the + // runtime on the `server.connect` handshake. Declaring it lets the + // telemetry the runtime emits on this connection be attributed to a + // consistent surface (the application and its Copilot integration) instead of + // the runtime's own build. All fields are optional; leave it nil to keep the + // runtime's default attribution. + ClientInfo *ClientInfo // Mode controls the default tool surface and feature flags presented to // sessions created by this client. The zero value ([ModeCopilotCli]) // matches legacy CLI defaults. Set to [ModeEmpty] to opt in to @@ -199,6 +211,54 @@ type ClientOptions struct { Mode ClientMode } +// ClientInfo identifies the integrating application on the `server.connect` handshake. +// +// Declaring it lets the telemetry the runtime emits on the connection be +// attributed to a single, consistent surface instead of the runtime's own +// build. All fields are optional; an empty field is omitted from the handshake. +type ClientInfo struct { + // ApplicationName is the name of the application using the SDK. + ApplicationName string + // ApplicationVersion is the version of the application using the SDK. + ApplicationVersion string + // IntegrationName optionally identifies a specific integration within the + // application, such as an extension or plugin. + IntegrationName string + // IntegrationVersion is the optional version of the named integration. + IntegrationVersion string +} + +// toWire maps the public [ClientInfo] onto the generated connect wire shape, +// omitting empty fields. It returns nil when no identity was supplied so the +// caller drops the clientInfo field and keeps the runtime's default attribution. +func (ci *ClientInfo) toWire() *rpc.ConnectClientInfo { + if ci == nil { + return nil + } + wire := &rpc.ConnectClientInfo{} + populated := false + if ci.ApplicationName != "" { + wire.EditorName = &ci.ApplicationName + populated = true + } + if ci.ApplicationVersion != "" { + wire.EditorVersion = &ci.ApplicationVersion + populated = true + } + if ci.IntegrationName != "" { + wire.ExtensionName = &ci.IntegrationName + populated = true + } + if ci.IntegrationVersion != "" { + wire.ExtensionVersion = &ci.IntegrationVersion + populated = true + } + if !populated { + return nil + } + return wire +} + // CloudSessionRepository is GitHub repository metadata associated with a cloud session. type CloudSessionRepository struct { Owner string `json:"owner"` @@ -379,6 +439,58 @@ type PermissionInvocation struct { 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 + +// PermissionResponseCapability describes whether the responding client could +// ask a user for a permission decision. +type PermissionResponseCapability = rpc.PermissionResponseCapability + +const ( + PermissionResponseCapabilityHeadless = rpc.PermissionResponseCapabilityHeadless + PermissionResponseCapabilityInteractive = rpc.PermissionResponseCapabilityInteractive + PermissionResponseCapabilityNone = rpc.PermissionResponseCapabilityNone +) + +// 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 ( + PermissionDecisionSourceAssistedApproval = rpc.PermissionDecisionSourceAssistedApproval + PermissionDecisionSourceHostPolicy = rpc.PermissionDecisionSourceHostPolicy + PermissionDecisionSourceHumanResponse = rpc.PermissionDecisionSourceHumanResponse + PermissionDecisionSourceUnattendedFallback = rpc.PermissionDecisionSourceUnattendedFallback +) + +// PermissionDecisionSurface identifies the client surface that submitted a +// permission response. +type PermissionDecisionSurface = rpc.PermissionDecisionSurface + +const ( + PermissionDecisionSurfaceAcp = rpc.PermissionDecisionSurfaceAcp + 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"` @@ -1194,6 +1306,16 @@ type GitHubMCPToolConfig struct { DisableFormDeferral *bool `json:"disableFormDeferral,omitempty"` } +// AskUserVariant selects the model-facing shape of the ask_user tool. +type AskUserVariant string + +const ( + // AskUserVariantLegacy uses the legacy user-input request implementation. + AskUserVariantLegacy AskUserVariant = "legacy" + // AskUserVariantElicitation uses the elicitation-based implementation. + AskUserVariantElicitation AskUserVariant = "elicitation" +) + // SessionConfig configures a new session type SessionConfig struct { // SessionID is an optional custom session ID @@ -1252,6 +1374,10 @@ type SessionConfig struct { // and discovered skill directories). When false, no skills are loaded regardless // of SkillDirectories or EnableConfigDiscovery settings. EnableSkills *bool + // IncludedBuiltinSkills is the allowlist of runtime-bundled skill names. + // In ModeEmpty, nil excludes all built-in skills; a non-nil list opts the + // named built-ins back in. Skills from other sources remain eligible. + IncludedBuiltinSkills []string // Tools exposes caller-implemented tools to the CLI. A Tool with a nil Handler // is declaration-only; the consumer must resolve its calls via pending tool RPCs. Tools []Tool @@ -1276,8 +1402,16 @@ type SessionConfig struct { // When provided, the SDK can satisfy MCP server OAuth requests with host-provided // token data or cancellation. OnMCPAuthRequest MCPAuthHandler - // OnUserInputRequest is a handler for user input requests from the agent (enables ask_user tool) + // GitHubTokenProvider acquires session-scoped GitHub tokens on demand. It + // cannot be combined with GitHubToken. + GitHubTokenProvider GitHubTokenProvider + // OnUserInputRequest handles legacy question-and-answer requests from the agent + // and enables the legacy ask_user tool. OnUserInputRequest UserInputHandler + // AskUserVariant selects the model-facing shape of the ask_user tool. + // The zero value preserves legacy behavior. AskUserVariantElicitation also + // requires OnElicitationRequest so the host can answer structured forms. + AskUserVariant AskUserVariant // Hooks configures hook handlers for session lifecycle events Hooks *SessionHooks // WorkingDirectory is the working directory for the session. @@ -1326,6 +1460,9 @@ 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 @@ -1376,6 +1513,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 @@ -1488,12 +1629,65 @@ type SessionConfig struct { // intended for trusted out-of-process integrators, and is not intended for // general external use. ExpAssignments *CopilotExpAssignmentResponse + // FeatureFlags contains feature-flag values resolved by the host for this session. + // Re-supply them when resuming after a runtime restart. + FeatureFlags map[string]bool // EnableManagedSettings, when set to true, opts the runtime into // self-fetching enterprise managed settings (bypass-permissions policy) at // session bootstrap using the session's GitHubToken. Requires GitHubToken to // 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. +// +// The runtime may introduce additional fail-closed modes. Values are serialized +// as strings so callers can use newer modes without waiting for an SDK release. +type DisableBypassPermissionsMode string + +const ( + // DisableBypassPermissionsModeDisable turns off bypass-permissions mode. + DisableBypassPermissionsModeDisable DisableBypassPermissionsMode = "disable" + // DisableBypassPermissionsModeAllowAutoOnly permits only automatic bypass. + DisableBypassPermissionsModeAllowAutoOnly DisableBypassPermissionsMode = "allow-auto-only" +) + +// 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 restricts bypass-permissions mode for the + // session. See the DisableBypassPermissionsMode constants for known values. + // Newer values are forwarded unchanged so runtime policies remain fail-closed. + 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 @@ -1513,6 +1707,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"` @@ -1690,6 +1889,9 @@ type ResumeSessionConfig struct { // ClientName identifies the application using the SDK. // Included in the User-Agent header for API requests. ClientName string + // GitHubTokenProvider acquires session-scoped GitHub tokens on demand. It + // cannot be combined with GitHubToken. + GitHubTokenProvider GitHubTokenProvider // Model to use for this session. Can change the model when resuming. Model string // Tools exposes caller-implemented tools to the CLI. A Tool with a nil Handler @@ -1708,6 +1910,10 @@ type ResumeSessionConfig struct { // be selected or invoked unless a custom agent with the same name is // configured. ExcludedBuiltInAgents []string + // IncludedBuiltinSkills is the allowlist of runtime-bundled skill names. + // In ModeEmpty, nil excludes all built-in skills; a non-nil list opts the + // named built-ins back in. Skills from other sources remain eligible. + IncludedBuiltinSkills []string // Provider configures a custom model provider Provider *ProviderConfig // Capi configures provider-scoped CAPI (Copilot API) session options. @@ -1736,6 +1942,10 @@ 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 @@ -1777,8 +1987,13 @@ type ResumeSessionConfig struct { // OnMCPAuthRequest is an optional handler for MCP OAuth requests from MCP servers. // See SessionConfig.OnMCPAuthRequest. OnMCPAuthRequest MCPAuthHandler - // OnUserInputRequest is a handler for user input requests from the agent (enables ask_user tool) + // OnUserInputRequest handles legacy question-and-answer requests from the agent + // and enables the legacy ask_user tool. OnUserInputRequest UserInputHandler + // AskUserVariant selects the model-facing shape of the ask_user tool. + // The zero value preserves legacy behavior. AskUserVariantElicitation also + // requires OnElicitationRequest so the host can answer structured forms. + AskUserVariant AskUserVariant // Hooks configures hook handlers for session lifecycle events Hooks *SessionHooks // WorkingDirectory is the working directory for the session. @@ -1855,6 +2070,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 @@ -1944,10 +2163,18 @@ type ResumeSessionConfig struct { // intended for trusted out-of-process integrators, and is not intended for // general external use. ExpAssignments *CopilotExpAssignmentResponse + // FeatureFlags contains host-resolved feature-flag values to apply on resume. + // See SessionConfig.FeatureFlags. + FeatureFlags map[string]bool // EnableManagedSettings injects the same opt-in flag on resume. See // 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 @@ -2059,6 +2286,18 @@ func (p ProviderConfig) MarshalJSON() ([]byte, error) { return json.Marshal(aux) } +// AutoTier selects the routing tier for model "auto" with V2 Auto. +type AutoTier = rpc.AutoTier + +const ( + // AutoTierEfficiency selects the efficiency routing tier. + AutoTierEfficiency = rpc.AutoTierEfficiency + // AutoTierBalance selects the balance routing tier. + AutoTierBalance = rpc.AutoTierBalance + // AutoTierIntelligence selects the intelligence routing tier. + AutoTierIntelligence = rpc.AutoTierIntelligence +) + // CapiSessionOptions configures provider-scoped Copilot API (CAPI) session behavior. // // WebSocket transport is the default for the CAPI Responses API whenever the @@ -2073,6 +2312,14 @@ type CapiSessionOptions struct { // WebSocket transport. Enabled by default when the model advertises // ws:/responses support; set to Bool(false) to force HTTP Responses transport. EnableWebSocketResponses *bool `json:"enableWebSocketResponses,omitempty"` + + // AutoTier selects the routing tier for model "auto" with V2 Auto. + // Requires a runtime that supports Auto tiers; it has no effect outside V2 Auto. + // When unset, the runtime uses its default on create and preserves the + // persisted or current tier on resume. An explicit tier overrides the + // persisted tier on a cold resume; a conflicting tier on a resident + // session resume is rejected by the runtime. + AutoTier AutoTier `json:"autoTier,omitempty"` } // AzureProviderOptions contains Azure-specific provider configuration @@ -2353,6 +2600,7 @@ 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"` @@ -2362,6 +2610,7 @@ type createSessionRequest struct { ModelCapabilities *rpc.ModelCapabilitiesOverride `json:"modelCapabilities,omitempty"` RequestPermission *bool `json:"requestPermission,omitempty"` RequestUserInput *bool `json:"requestUserInput,omitempty"` + AskUserVariant AskUserVariant `json:"askUserVariant,omitempty"` RequestExitPlanMode *bool `json:"requestExitPlanMode,omitempty"` RequestAutoModeSwitch *bool `json:"requestAutoModeSwitch,omitempty"` Hooks *bool `json:"hooks,omitempty"` @@ -2390,6 +2639,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"` @@ -2399,6 +2649,7 @@ type createSessionRequest struct { RequestMCPApps *bool `json:"requestMcpApps,omitempty"` GitHubMCPToolConfig *GitHubMCPToolConfig `json:"githubMcpToolConfig,omitempty"` GitHubToken string `json:"gitHubToken,omitempty"` + GitHubTokenProviderRegistrationID string `json:"gitHubTokenProviderRegistrationId,omitempty"` RemoteSession rpc.RemoteSessionMode `json:"remoteSession,omitempty"` Cloud *CloudSessionOptions `json:"cloud,omitempty"` Canvases []CanvasDeclaration `json:"canvases,omitempty"` @@ -2408,7 +2659,9 @@ type createSessionRequest struct { ExtensionInfo *ExtensionInfo `json:"extensionInfo,omitempty"` CanvasProvider *CanvasProviderIdentity `json:"canvasProvider,omitempty"` ExpAssignments *CopilotExpAssignmentResponse `json:"expAssignments,omitempty"` + FeatureFlags *map[string]bool `json:"featureFlags,omitempty"` EnableManagedSettings *bool `json:"enableManagedSettings,omitempty"` + ManagedSettings *ManagedSettings `json:"managedSettings,omitempty"` Traceparent string `json:"traceparent,omitempty"` Tracestate string `json:"tracestate,omitempty"` } @@ -2416,7 +2669,7 @@ type createSessionRequest struct { // wireCommand is the wire representation of a command (name + description only, no handler). type wireCommand struct { Name string `json:"name"` - Description string `json:"description,omitempty"` + Description string `json:"description"` } // createSessionResponse is the response from session.create @@ -2446,6 +2699,7 @@ 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"` @@ -2455,6 +2709,7 @@ type resumeSessionRequest struct { ModelCapabilities *rpc.ModelCapabilitiesOverride `json:"modelCapabilities,omitempty"` RequestPermission *bool `json:"requestPermission,omitempty"` RequestUserInput *bool `json:"requestUserInput,omitempty"` + AskUserVariant AskUserVariant `json:"askUserVariant,omitempty"` RequestExitPlanMode *bool `json:"requestExitPlanMode,omitempty"` RequestAutoModeSwitch *bool `json:"requestAutoModeSwitch,omitempty"` Hooks *bool `json:"hooks,omitempty"` @@ -2485,6 +2740,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"` @@ -2494,6 +2750,7 @@ type resumeSessionRequest struct { RequestMCPApps *bool `json:"requestMcpApps,omitempty"` GitHubMCPToolConfig *GitHubMCPToolConfig `json:"githubMcpToolConfig,omitempty"` GitHubToken string `json:"gitHubToken,omitempty"` + GitHubTokenProviderRegistrationID string `json:"gitHubTokenProviderRegistrationId,omitempty"` RemoteSession rpc.RemoteSessionMode `json:"remoteSession,omitempty"` Canvases []CanvasDeclaration `json:"canvases,omitempty"` OpenCanvases []rpc.OpenCanvasInstance `json:"openCanvases,omitempty"` @@ -2503,7 +2760,9 @@ type resumeSessionRequest struct { ExtensionInfo *ExtensionInfo `json:"extensionInfo,omitempty"` CanvasProvider *CanvasProviderIdentity `json:"canvasProvider,omitempty"` ExpAssignments *CopilotExpAssignmentResponse `json:"expAssignments,omitempty"` + FeatureFlags *map[string]bool `json:"featureFlags,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/zsession_events.go b/go/zsession_events.go index fbc1002774..b5f6fa34f0 100644 --- a/go/zsession_events.go +++ b/go/zsession_events.go @@ -9,13 +9,24 @@ import "github.com/github/copilot-sdk/go/rpc" type ( AbortData = rpc.AbortData AbortReason = rpc.AbortReason + AgentInterruptedActivity = rpc.AgentInterruptedActivity + AgentInterruptedCancelPhase = rpc.AgentInterruptedCancelPhase + AgentInterruptedData = rpc.AgentInterruptedData + AgentModelPolicy = rpc.AgentModelPolicy + AssistantFusionPhaseActivityData = rpc.AssistantFusionPhaseActivityData + AssistantFusionPhaseCompletedData = rpc.AssistantFusionPhaseCompletedData + AssistantFusionPhaseFailedData = rpc.AssistantFusionPhaseFailedData + AssistantFusionPhaseStartedData = rpc.AssistantFusionPhaseStartedData AssistantIdleData = rpc.AssistantIdleData AssistantIntentData = rpc.AssistantIntentData AssistantMessageData = rpc.AssistantMessageData AssistantMessageDeltaData = rpc.AssistantMessageDeltaData + AssistantMessageReasoningBlocks = rpc.AssistantMessageReasoningBlocks AssistantMessageServerTools = rpc.AssistantMessageServerTools AssistantMessageStartData = rpc.AssistantMessageStartData AssistantMessageToolRequest = rpc.AssistantMessageToolRequest + AssistantMessageToolRequestCaller = rpc.AssistantMessageToolRequestCaller + AssistantMessageToolRequestCallerType = rpc.AssistantMessageToolRequestCallerType AssistantMessageToolRequestType = rpc.AssistantMessageToolRequestType AssistantReasoningData = rpc.AssistantReasoningData AssistantReasoningDeltaData = rpc.AssistantReasoningDeltaData @@ -29,6 +40,9 @@ type ( AssistantUsageCopilotUsage = rpc.AssistantUsageCopilotUsage AssistantUsageCopilotUsageTokenDetail = rpc.AssistantUsageCopilotUsageTokenDetail AssistantUsageData = rpc.AssistantUsageData + AssistantUsageTransport = rpc.AssistantUsageTransport + AssistedApprovalJudgeFailureReason = rpc.AssistedApprovalJudgeFailureReason + AssistedApprovalRecommendation = rpc.AssistedApprovalRecommendation Attachment = rpc.Attachment AttachmentBlob = rpc.AttachmentBlob AttachmentDirectory = rpc.AttachmentDirectory @@ -53,8 +67,6 @@ type ( AttachmentSelectionDetailsEnd = rpc.AttachmentSelectionDetailsEnd AttachmentSelectionDetailsStart = rpc.AttachmentSelectionDetailsStart AttachmentType = rpc.AttachmentType - AutoApprovalJudgeFailureReason = rpc.AutoApprovalJudgeFailureReason - AutoApprovalRecommendation = rpc.AutoApprovalRecommendation AutoModeResolvedReasoningBucket = rpc.AutoModeResolvedReasoningBucket AutoModeSwitchCompletedData = rpc.AutoModeSwitchCompletedData AutoModeSwitchRequestedData = rpc.AutoModeSwitchRequestedData @@ -87,6 +99,10 @@ type ( CompactionCompleteCompactionTokensUsed = rpc.CompactionCompleteCompactionTokensUsed CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail = rpc.CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail CompactionTrigger = rpc.CompactionTrigger + CompletionReceiptEventRange = rpc.CompletionReceiptEventRange + CompletionReceiptFinalTool = rpc.CompletionReceiptFinalTool + CompletionReceiptStopReason = rpc.CompletionReceiptStopReason + CompletionReceiptToolStatus = rpc.CompletionReceiptToolStatus ContextTier = rpc.ContextTier CustomAgentsUpdatedAgent = rpc.CustomAgentsUpdatedAgent ElicitationCompletedAction = rpc.ElicitationCompletedAction @@ -107,7 +123,22 @@ type ( ExternalToolRequestedData = rpc.ExternalToolRequestedData FactoryPermissionOperation = rpc.FactoryPermissionOperation FactoryPermissionPhase = rpc.FactoryPermissionPhase + FactoryRunSettledData = rpc.FactoryRunSettledData + FactoryRunSettledStatus = rpc.FactoryRunSettledStatus + FactoryRunStartedData = rpc.FactoryRunStartedData FactoryRunUpdatedData = rpc.FactoryRunUpdatedData + FusionAttribution = rpc.FusionAttribution + FusionConversationScope = rpc.FusionConversationScope + FusionFollowUpAction = rpc.FusionFollowUpAction + FusionFollowUpRecommendation = rpc.FusionFollowUpRecommendation + FusionPattern = rpc.FusionPattern + FusionPhaseActivityKind = rpc.FusionPhaseActivityKind + FusionPhaseKind = rpc.FusionPhaseKind + FusionPhasePlanStep = rpc.FusionPhasePlanStep + FusionPhaseStatus = rpc.FusionPhaseStatus + FusionPhaseUsage = rpc.FusionPhaseUsage + FusionScores = rpc.FusionScores + FusionTurnKind = rpc.FusionTurnKind GitHubRepoRef = rpc.GitHubRepoRef HandoffRepository = rpc.HandoffRepository HandoffSourceType = rpc.HandoffSourceType @@ -148,16 +179,18 @@ type ( ModelCallFailureRequestFingerprint = rpc.ModelCallFailureRequestFingerprint ModelCallFailureSource = rpc.ModelCallFailureSource ModelCallFailureTransport = rpc.ModelCallFailureTransport + ModelCallFinishedData = rpc.ModelCallFinishedData + ModelCallFinishedOutcome = rpc.ModelCallFinishedOutcome ModelCallStartData = rpc.ModelCallStartData + ModelChangeSource = rpc.ModelChangeSource OmittedBinaryOmittedReason = rpc.OmittedBinaryOmittedReason OmittedBinaryResult = rpc.OmittedBinaryResult OmittedBinaryType = rpc.OmittedBinaryType PendingMessagesModifiedData = rpc.PendingMessagesModifiedData - PermissionAllowAllMode = rpc.PermissionAllowAllMode PermissionApproved = rpc.PermissionApproved PermissionApprovedForLocation = rpc.PermissionApprovedForLocation PermissionApprovedForSession = rpc.PermissionApprovedForSession - PermissionAutoApproval = rpc.PermissionAutoApproval + PermissionAssistedApproval = rpc.PermissionAssistedApproval PermissionCancelled = rpc.PermissionCancelled PermissionCompletedData = rpc.PermissionCompletedData PermissionDeniedByContentExclusionPolicy = rpc.PermissionDeniedByContentExclusionPolicy @@ -165,9 +198,11 @@ type ( PermissionDeniedByRules = rpc.PermissionDeniedByRules PermissionDeniedInteractivelyByUser = rpc.PermissionDeniedInteractivelyByUser PermissionDeniedNoApprovalRuleAndCouldNotRequestFromUser = rpc.PermissionDeniedNoApprovalRuleAndCouldNotRequestFromUser + PermissionMode = rpc.PermissionMode PermissionPromptRequest = rpc.PermissionPromptRequest PermissionPromptRequestCommands = rpc.PermissionPromptRequestCommands PermissionPromptRequestCustomTool = rpc.PermissionPromptRequestCustomTool + PermissionPromptRequestExtensionEnvAccess = rpc.PermissionPromptRequestExtensionEnvAccess PermissionPromptRequestExtensionManagement = rpc.PermissionPromptRequestExtensionManagement PermissionPromptRequestExtensionPermissionAccess = rpc.PermissionPromptRequestExtensionPermissionAccess PermissionPromptRequestFactory = rpc.PermissionPromptRequestFactory @@ -180,10 +215,12 @@ type ( PermissionPromptRequestRead = rpc.PermissionPromptRequestRead PermissionPromptRequestURL = rpc.PermissionPromptRequestURL PermissionPromptRequestWrite = rpc.PermissionPromptRequestWrite + PermissionRecommendation = rpc.PermissionRecommendation PermissionRequest = rpc.PermissionRequest PermissionRequestCommand = rpc.PermissionRequestCommand PermissionRequestCustomTool = rpc.PermissionRequestCustomTool PermissionRequestedData = rpc.PermissionRequestedData + PermissionRequestExtensionEnvAccess = rpc.PermissionRequestExtensionEnvAccess PermissionRequestExtensionManagement = rpc.PermissionRequestExtensionManagement PermissionRequestExtensionPermissionAccess = rpc.PermissionRequestExtensionPermissionAccess PermissionRequestFactory = rpc.PermissionRequestFactory @@ -193,6 +230,7 @@ type ( PermissionRequestMemory = rpc.PermissionRequestMemory PermissionRequestMemoryAction = rpc.PermissionRequestMemoryAction PermissionRequestMemoryDirection = rpc.PermissionRequestMemoryDirection + PermissionRequestMemoryScope = rpc.PermissionRequestMemoryScope PermissionRequestRead = rpc.PermissionRequestRead PermissionRequestShell = rpc.PermissionRequestShell PermissionRequestShellCommand = rpc.PermissionRequestShellCommand @@ -209,6 +247,7 @@ type ( PersistedBinaryResultType = rpc.PersistedBinaryResultType PlanChangedOperation = rpc.PlanChangedOperation PossibleURL = rpc.PossibleURL + PromptCacheBreakData = rpc.PromptCacheBreakData RawCitationLocation = rpc.RawCitationLocation RawPermissionPromptRequest = rpc.RawPermissionPromptRequest RawPermissionRequest = rpc.RawPermissionRequest @@ -220,6 +259,7 @@ type ( ReasoningSummary = rpc.ReasoningSummary SamplingCompletedData = rpc.SamplingCompletedData SamplingRequestedData = rpc.SamplingRequestedData + SandboxDecisionData = rpc.SandboxDecisionData ScheduleOrigin = rpc.ScheduleOrigin SessionAutoModeResolvedData = rpc.SessionAutoModeResolvedData SessionAutopilotObjectiveChangedData = rpc.SessionAutopilotObjectiveChangedData @@ -233,6 +273,7 @@ type ( SessionCanvasUnavailableData = rpc.SessionCanvasUnavailableData SessionCompactionCompleteData = rpc.SessionCompactionCompleteData SessionCompactionStartData = rpc.SessionCompactionStartData + SessionCompletionReceiptData = rpc.SessionCompletionReceiptData SessionContextChangedData = rpc.SessionContextChangedData SessionContextClearedData = rpc.SessionContextClearedData SessionCustomAgentsUpdatedData = rpc.SessionCustomAgentsUpdatedData @@ -243,6 +284,10 @@ type ( SessionEventType = rpc.SessionEventType SessionExtensionsAttachmentsPushedData = rpc.SessionExtensionsAttachmentsPushedData SessionExtensionsLoadedData = rpc.SessionExtensionsLoadedData + SessionFusionCompletedData = rpc.SessionFusionCompletedData + SessionFusionResolvedData = rpc.SessionFusionResolvedData + SessionFusionRouteFailedData = rpc.SessionFusionRouteFailedData + SessionFusionRouteStartedData = rpc.SessionFusionRouteStartedData SessionHandoffData = rpc.SessionHandoffData SessionIdleData = rpc.SessionIdleData SessionInfoData = rpc.SessionInfoData @@ -258,6 +303,7 @@ type ( SessionMode = rpc.SessionMode SessionModeChangedData = rpc.SessionModeChangedData SessionModelChangeData = rpc.SessionModelChangeData + SessionModeNoticeDeliveredData = rpc.SessionModeNoticeDeliveredData SessionPermissionsChangedData = rpc.SessionPermissionsChangedData SessionPlanChangedData = rpc.SessionPlanChangedData SessionRemoteSteerableChangedData = rpc.SessionRemoteSteerableChangedData @@ -279,6 +325,7 @@ type ( SessionUsageInfoData = rpc.SessionUsageInfoData SessionWarningData = rpc.SessionWarningData SessionWorkspaceFileChangedData = rpc.SessionWorkspaceFileChangedData + ShutdownAgentMetric = rpc.ShutdownAgentMetric ShutdownCodeChanges = rpc.ShutdownCodeChanges ShutdownModelMetric = rpc.ShutdownModelMetric ShutdownModelMetricRequests = rpc.ShutdownModelMetricRequests @@ -291,6 +338,7 @@ type ( SkillsLoadedSkill = rpc.SkillsLoadedSkill SkillSource = rpc.SkillSource SubagentCompletedData = rpc.SubagentCompletedData + SubagentConfiguredData = rpc.SubagentConfiguredData SubagentDeselectedData = rpc.SubagentDeselectedData SubagentFailedData = rpc.SubagentFailedData SubagentSelectedData = rpc.SubagentSelectedData @@ -311,6 +359,7 @@ type ( SystemNotificationShellDetachedCompleted = rpc.SystemNotificationShellDetachedCompleted SystemNotificationType = rpc.SystemNotificationType SystemNotificationUnclassified = rpc.SystemNotificationUnclassified + TaskCompleteData = rpc.TaskCompleteData TaskCompletionOutcome = rpc.TaskCompletionOutcome ToolExecutionCompleteContent = rpc.ToolExecutionCompleteContent ToolExecutionCompleteContentAudio = rpc.ToolExecutionCompleteContentAudio @@ -350,6 +399,8 @@ type ( ToolExecutionStartToolDescriptionMetaUIVisibility = rpc.ToolExecutionStartToolDescriptionMetaUIVisibility ToolSearchActivatedData = rpc.ToolSearchActivatedData ToolUserRequestedData = rpc.ToolUserRequestedData + UIEphemeralQueryData = rpc.UIEphemeralQueryData + UIEphemeralQueryPhase = rpc.UIEphemeralQueryPhase UserInputCompletedData = rpc.UserInputCompletedData UserInputRequestedData = rpc.UserInputRequestedData UserMessageAgentMode = rpc.UserMessageAgentMode @@ -358,6 +409,7 @@ type ( UserToolSessionApproval = rpc.UserToolSessionApproval UserToolSessionApprovalCommands = rpc.UserToolSessionApprovalCommands UserToolSessionApprovalCustomTool = rpc.UserToolSessionApprovalCustomTool + UserToolSessionApprovalExtensionEnvAccess = rpc.UserToolSessionApprovalExtensionEnvAccess UserToolSessionApprovalExtensionManagement = rpc.UserToolSessionApprovalExtensionManagement UserToolSessionApprovalExtensionPermissionAccess = rpc.UserToolSessionApprovalExtensionPermissionAccess UserToolSessionApprovalFactory = rpc.UserToolSessionApprovalFactory @@ -378,12 +430,32 @@ const ( AbortReasonRemoteCommand = rpc.AbortReasonRemoteCommand AbortReasonUserAbort = rpc.AbortReasonUserAbort AbortReasonUserInitiated = rpc.AbortReasonUserInitiated + AgentInterruptedActivityBackgroundAgent = rpc.AgentInterruptedActivityBackgroundAgent + AgentInterruptedActivityModelCall = rpc.AgentInterruptedActivityModelCall + AgentInterruptedActivityRetryBackoff = rpc.AgentInterruptedActivityRetryBackoff + AgentInterruptedActivityToolCall = rpc.AgentInterruptedActivityToolCall + AgentInterruptedCancelPhaseMidStream = rpc.AgentInterruptedCancelPhaseMidStream + AgentInterruptedCancelPhasePreFirstToken = rpc.AgentInterruptedCancelPhasePreFirstToken + AgentModelPolicyPreferred = rpc.AgentModelPolicyPreferred + AgentModelPolicyRequired = rpc.AgentModelPolicyRequired + AssistantMessageToolRequestCallerTypeProgram = rpc.AssistantMessageToolRequestCallerTypeProgram AssistantMessageToolRequestTypeCustom = rpc.AssistantMessageToolRequestTypeCustom AssistantMessageToolRequestTypeFunction = rpc.AssistantMessageToolRequestTypeFunction AssistantUsageAPIEndpointChatCompletions = rpc.AssistantUsageAPIEndpointChatCompletions AssistantUsageAPIEndpointResponses = rpc.AssistantUsageAPIEndpointResponses AssistantUsageAPIEndpointV1Messages = rpc.AssistantUsageAPIEndpointV1Messages AssistantUsageAPIEndpointWsResponses = rpc.AssistantUsageAPIEndpointWsResponses + AssistantUsageTransportHTTP = rpc.AssistantUsageTransportHTTP + AssistantUsageTransportWebsocket = rpc.AssistantUsageTransportWebsocket + AssistedApprovalJudgeFailureReasonAbort = rpc.AssistedApprovalJudgeFailureReasonAbort + AssistedApprovalJudgeFailureReasonEmptyResponse = rpc.AssistedApprovalJudgeFailureReasonEmptyResponse + AssistedApprovalJudgeFailureReasonModelError = rpc.AssistedApprovalJudgeFailureReasonModelError + AssistedApprovalJudgeFailureReasonParseError = rpc.AssistedApprovalJudgeFailureReasonParseError + AssistedApprovalJudgeFailureReasonTimeout = rpc.AssistedApprovalJudgeFailureReasonTimeout + AssistedApprovalRecommendationApprove = rpc.AssistedApprovalRecommendationApprove + AssistedApprovalRecommendationError = rpc.AssistedApprovalRecommendationError + AssistedApprovalRecommendationExcluded = rpc.AssistedApprovalRecommendationExcluded + AssistedApprovalRecommendationRequireApproval = rpc.AssistedApprovalRecommendationRequireApproval AttachmentGitHubReferenceTypeDiscussion = rpc.AttachmentGitHubReferenceTypeDiscussion AttachmentGitHubReferenceTypeIssue = rpc.AttachmentGitHubReferenceTypeIssue AttachmentGitHubReferenceTypePr = rpc.AttachmentGitHubReferenceTypePr @@ -402,15 +474,6 @@ 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 - AutoApprovalRecommendationRequireApproval = rpc.AutoApprovalRecommendationRequireApproval AutoModeResolvedReasoningBucketHigh = rpc.AutoModeResolvedReasoningBucketHigh AutoModeResolvedReasoningBucketLow = rpc.AutoModeResolvedReasoningBucketLow AutoModeResolvedReasoningBucketMedium = rpc.AutoModeResolvedReasoningBucketMedium @@ -439,6 +502,14 @@ const ( CompactionTriggerMemoryPressure = rpc.CompactionTriggerMemoryPressure CompactionTriggerModelSwitch = rpc.CompactionTriggerModelSwitch CompactionTriggerThreshold = rpc.CompactionTriggerThreshold + CompletionReceiptStopReasonAgentStopBlockLimit = rpc.CompletionReceiptStopReasonAgentStopBlockLimit + CompletionReceiptStopReasonNatural = rpc.CompletionReceiptStopReasonNatural + CompletionReceiptStopReasonTerminalTool = rpc.CompletionReceiptStopReasonTerminalTool + CompletionReceiptToolStatusDenied = rpc.CompletionReceiptToolStatusDenied + CompletionReceiptToolStatusFailure = rpc.CompletionReceiptToolStatusFailure + CompletionReceiptToolStatusRejected = rpc.CompletionReceiptToolStatusRejected + CompletionReceiptToolStatusSuccess = rpc.CompletionReceiptToolStatusSuccess + CompletionReceiptToolStatusTimeout = rpc.CompletionReceiptToolStatusTimeout ContextTierDefault = rpc.ContextTierDefault ContextTierLongContext = rpc.ContextTierLongContext ElicitationCompletedActionAccept = rpc.ElicitationCompletedActionAccept @@ -461,16 +532,49 @@ const ( ExtensionsLoadedExtensionStatusStarting = rpc.ExtensionsLoadedExtensionStatusStarting FactoryPermissionOperationAuthor = rpc.FactoryPermissionOperationAuthor FactoryPermissionOperationRun = rpc.FactoryPermissionOperationRun + FactoryRunSettledStatusCancelled = rpc.FactoryRunSettledStatusCancelled + FactoryRunSettledStatusCompleted = rpc.FactoryRunSettledStatusCompleted + FactoryRunSettledStatusError = rpc.FactoryRunSettledStatusError + FactoryRunSettledStatusHalted = rpc.FactoryRunSettledStatusHalted + FusionConversationScopeReview = rpc.FusionConversationScopeReview + FusionConversationScopeRoot = rpc.FusionConversationScopeRoot + FusionFollowUpActionReroute = rpc.FusionFollowUpActionReroute + FusionFollowUpActionReusePrimary = rpc.FusionFollowUpActionReusePrimary + FusionPatternCascade = rpc.FusionPatternCascade + FusionPatternCritique = rpc.FusionPatternCritique + FusionPatternSingle = rpc.FusionPatternSingle + FusionPhaseActivityKindModelOutput = rpc.FusionPhaseActivityKindModelOutput + FusionPhaseActivityKindToolCompleted = rpc.FusionPhaseActivityKindToolCompleted + FusionPhaseActivityKindToolStarted = rpc.FusionPhaseActivityKindToolStarted + FusionPhaseKindCritic = rpc.FusionPhaseKindCritic + FusionPhaseKindDraft = rpc.FusionPhaseKindDraft + FusionPhaseKindFollowUp = rpc.FusionPhaseKindFollowUp + FusionPhaseKindJudge = rpc.FusionPhaseKindJudge + FusionPhaseKindPrimary = rpc.FusionPhaseKindPrimary + FusionPhaseKindRepair = rpc.FusionPhaseKindRepair + FusionPhaseKindRevision = rpc.FusionPhaseKindRevision + FusionPhaseStatusCancelled = rpc.FusionPhaseStatusCancelled + FusionPhaseStatusFailed = rpc.FusionPhaseStatusFailed + FusionPhaseStatusSucceeded = rpc.FusionPhaseStatusSucceeded + FusionProjectionModeAppend = rpc.FusionProjectionModeAppend + FusionProjectionModeNone = rpc.FusionProjectionModeNone + FusionProjectionModeStaged = rpc.FusionProjectionModeStaged + FusionTurnKindCompaction = rpc.FusionTurnKindCompaction + FusionTurnKindUser = rpc.FusionTurnKindUser HandoffSourceTypeLocal = rpc.HandoffSourceTypeLocal HandoffSourceTypeRemote = rpc.HandoffSourceTypeRemote ManagedSettingsEnforcedActionBypassPermissionsBlocked = rpc.ManagedSettingsEnforcedActionBypassPermissionsBlocked ManagedSettingsEnforcedEscalationAllowAll = rpc.ManagedSettingsEnforcedEscalationAllowAll ManagedSettingsEnforcedEscalationApproveAll = rpc.ManagedSettingsEnforcedEscalationApproveAll - ManagedSettingsEnforcedEscalationAutoApproval = rpc.ManagedSettingsEnforcedEscalationAutoApproval + ManagedSettingsEnforcedEscalationAssistedApproval = rpc.ManagedSettingsEnforcedEscalationAssistedApproval + ManagedSettingsEnforcedEscalationServerWideMCPApproval = rpc.ManagedSettingsEnforcedEscalationServerWideMCPApproval ManagedSettingsEnforcedEscalationUnrestrictedPaths = rpc.ManagedSettingsEnforcedEscalationUnrestrictedPaths ManagedSettingsEnforcedEscalationUnrestrictedURLs = rpc.ManagedSettingsEnforcedEscalationUnrestrictedURLs + ManagedSettingsResolvedSourceClient = rpc.ManagedSettingsResolvedSourceClient ManagedSettingsResolvedSourceDevice = rpc.ManagedSettingsResolvedSourceDevice + ManagedSettingsResolvedSourceMixed = rpc.ManagedSettingsResolvedSourceMixed ManagedSettingsResolvedSourceNone = rpc.ManagedSettingsResolvedSourceNone + ManagedSettingsResolvedSourcePolicyHelper = rpc.ManagedSettingsResolvedSourcePolicyHelper ManagedSettingsResolvedSourceServer = rpc.ManagedSettingsResolvedSourceServer MCPHeadersRefreshCompletedOutcomeHeaders = rpc.MCPHeadersRefreshCompletedOutcomeHeaders MCPHeadersRefreshCompletedOutcomeNone = rpc.MCPHeadersRefreshCompletedOutcomeNone @@ -509,15 +613,31 @@ const ( ModelCallFailureSourceTopLevel = rpc.ModelCallFailureSourceTopLevel ModelCallFailureTransportHTTP = rpc.ModelCallFailureTransportHTTP ModelCallFailureTransportWebsocket = rpc.ModelCallFailureTransportWebsocket + ModelCallFinishedOutcomeCancelled = rpc.ModelCallFinishedOutcomeCancelled + ModelCallFinishedOutcomeError = rpc.ModelCallFinishedOutcomeError + ModelCallFinishedOutcomeRejected = rpc.ModelCallFinishedOutcomeRejected + ModelCallFinishedOutcomeSuccess = rpc.ModelCallFinishedOutcomeSuccess + ModelChangeSourceAgent = rpc.ModelChangeSourceAgent + ModelChangeSourceAutomatic = rpc.ModelChangeSourceAutomatic + ModelChangeSourceConfigCommand = rpc.ModelChangeSourceConfigCommand + ModelChangeSourceManagedSettings = rpc.ModelChangeSourceManagedSettings + ModelChangeSourceModelCommand = rpc.ModelChangeSourceModelCommand + ModelChangeSourceModelPicker = rpc.ModelChangeSourceModelPicker + ModelChangeSourcePlanMode = rpc.ModelChangeSourcePlanMode + ModelChangeSourceRepoSettings = rpc.ModelChangeSourceRepoSettings + ModelChangeSourceSDK = rpc.ModelChangeSourceSDK + ModelChangeSourceSettingsCommand = rpc.ModelChangeSourceSettingsCommand + ModelChangeSourceStartup = rpc.ModelChangeSourceStartup OmittedBinaryOmittedReasonAssetUnavailable = rpc.OmittedBinaryOmittedReasonAssetUnavailable OmittedBinaryOmittedReasonTooLarge = rpc.OmittedBinaryOmittedReasonTooLarge OmittedBinaryTypeImage = rpc.OmittedBinaryTypeImage OmittedBinaryTypeResource = rpc.OmittedBinaryTypeResource - PermissionAllowAllModeAuto = rpc.PermissionAllowAllModeAuto - PermissionAllowAllModeOff = rpc.PermissionAllowAllModeOff - PermissionAllowAllModeOn = rpc.PermissionAllowAllModeOn + PermissionModeAllowAll = rpc.PermissionModeAllowAll + PermissionModeAssisted = rpc.PermissionModeAssisted + PermissionModeManual = rpc.PermissionModeManual PermissionPromptRequestKindCommands = rpc.PermissionPromptRequestKindCommands PermissionPromptRequestKindCustomTool = rpc.PermissionPromptRequestKindCustomTool + PermissionPromptRequestKindExtensionEnvAccess = rpc.PermissionPromptRequestKindExtensionEnvAccess PermissionPromptRequestKindExtensionManagement = rpc.PermissionPromptRequestKindExtensionManagement PermissionPromptRequestKindExtensionPermissionAccess = rpc.PermissionPromptRequestKindExtensionPermissionAccess PermissionPromptRequestKindFactory = rpc.PermissionPromptRequestKindFactory @@ -531,7 +651,9 @@ const ( PermissionPromptRequestPathAccessKindRead = rpc.PermissionPromptRequestPathAccessKindRead PermissionPromptRequestPathAccessKindShell = rpc.PermissionPromptRequestPathAccessKindShell PermissionPromptRequestPathAccessKindWrite = rpc.PermissionPromptRequestPathAccessKindWrite + PermissionRecommendationApprove = rpc.PermissionRecommendationApprove PermissionRequestKindCustomTool = rpc.PermissionRequestKindCustomTool + PermissionRequestKindExtensionEnvAccess = rpc.PermissionRequestKindExtensionEnvAccess PermissionRequestKindExtensionManagement = rpc.PermissionRequestKindExtensionManagement PermissionRequestKindExtensionPermissionAccess = rpc.PermissionRequestKindExtensionPermissionAccess PermissionRequestKindFactory = rpc.PermissionRequestKindFactory @@ -546,6 +668,8 @@ const ( PermissionRequestMemoryActionVote = rpc.PermissionRequestMemoryActionVote PermissionRequestMemoryDirectionDownvote = rpc.PermissionRequestMemoryDirectionDownvote PermissionRequestMemoryDirectionUpvote = rpc.PermissionRequestMemoryDirectionUpvote + PermissionRequestMemoryScopeRepository = rpc.PermissionRequestMemoryScopeRepository + PermissionRequestMemoryScopeUser = rpc.PermissionRequestMemoryScopeUser PermissionResultKindApproved = rpc.PermissionResultKindApproved PermissionResultKindApprovedForLocation = rpc.PermissionResultKindApprovedForLocation PermissionResultKindApprovedForSession = rpc.PermissionResultKindApprovedForSession @@ -568,6 +692,11 @@ const ( ScheduleOriginModel = rpc.ScheduleOriginModel ScheduleOriginUser = rpc.ScheduleOriginUser SessionEventTypeAbort = rpc.SessionEventTypeAbort + SessionEventTypeAgentInterrupted = rpc.SessionEventTypeAgentInterrupted + SessionEventTypeAssistantFusionPhaseActivity = rpc.SessionEventTypeAssistantFusionPhaseActivity + SessionEventTypeAssistantFusionPhaseCompleted = rpc.SessionEventTypeAssistantFusionPhaseCompleted + SessionEventTypeAssistantFusionPhaseFailed = rpc.SessionEventTypeAssistantFusionPhaseFailed + SessionEventTypeAssistantFusionPhaseStarted = rpc.SessionEventTypeAssistantFusionPhaseStarted SessionEventTypeAssistantIdle = rpc.SessionEventTypeAssistantIdle SessionEventTypeAssistantIntent = rpc.SessionEventTypeAssistantIntent SessionEventTypeAssistantMessage = rpc.SessionEventTypeAssistantMessage @@ -595,6 +724,8 @@ const ( SessionEventTypeExitPlanModeRequested = rpc.SessionEventTypeExitPlanModeRequested SessionEventTypeExternalToolCompleted = rpc.SessionEventTypeExternalToolCompleted SessionEventTypeExternalToolRequested = rpc.SessionEventTypeExternalToolRequested + SessionEventTypeFactoryRunSettled = rpc.SessionEventTypeFactoryRunSettled + SessionEventTypeFactoryRunStarted = rpc.SessionEventTypeFactoryRunStarted SessionEventTypeFactoryRunUpdated = rpc.SessionEventTypeFactoryRunUpdated SessionEventTypeHookEnd = rpc.SessionEventTypeHookEnd SessionEventTypeHookProgress = rpc.SessionEventTypeHookProgress @@ -608,12 +739,15 @@ const ( SessionEventTypeMCPResourcesListChanged = rpc.SessionEventTypeMCPResourcesListChanged SessionEventTypeMCPToolsListChanged = rpc.SessionEventTypeMCPToolsListChanged SessionEventTypeModelCallFailure = rpc.SessionEventTypeModelCallFailure + SessionEventTypeModelCallFinished = rpc.SessionEventTypeModelCallFinished SessionEventTypeModelCallStart = rpc.SessionEventTypeModelCallStart SessionEventTypePendingMessagesModified = rpc.SessionEventTypePendingMessagesModified SessionEventTypePermissionCompleted = rpc.SessionEventTypePermissionCompleted SessionEventTypePermissionRequested = rpc.SessionEventTypePermissionRequested + SessionEventTypePromptCacheBreak = rpc.SessionEventTypePromptCacheBreak SessionEventTypeSamplingCompleted = rpc.SessionEventTypeSamplingCompleted SessionEventTypeSamplingRequested = rpc.SessionEventTypeSamplingRequested + SessionEventTypeSandboxDecision = rpc.SessionEventTypeSandboxDecision SessionEventTypeSessionAutoModeResolved = rpc.SessionEventTypeSessionAutoModeResolved SessionEventTypeSessionAutopilotObjectiveChanged = rpc.SessionEventTypeSessionAutopilotObjectiveChanged SessionEventTypeSessionBackgroundTasksChanged = rpc.SessionEventTypeSessionBackgroundTasksChanged @@ -626,6 +760,7 @@ const ( SessionEventTypeSessionCanvasUnavailable = rpc.SessionEventTypeSessionCanvasUnavailable SessionEventTypeSessionCompactionComplete = rpc.SessionEventTypeSessionCompactionComplete SessionEventTypeSessionCompactionStart = rpc.SessionEventTypeSessionCompactionStart + SessionEventTypeSessionCompletionReceipt = rpc.SessionEventTypeSessionCompletionReceipt SessionEventTypeSessionContextChanged = rpc.SessionEventTypeSessionContextChanged SessionEventTypeSessionContextCleared = rpc.SessionEventTypeSessionContextCleared SessionEventTypeSessionCustomAgentsUpdated = rpc.SessionEventTypeSessionCustomAgentsUpdated @@ -633,6 +768,10 @@ const ( SessionEventTypeSessionError = rpc.SessionEventTypeSessionError SessionEventTypeSessionExtensionsAttachmentsPushed = rpc.SessionEventTypeSessionExtensionsAttachmentsPushed SessionEventTypeSessionExtensionsLoaded = rpc.SessionEventTypeSessionExtensionsLoaded + SessionEventTypeSessionFusionCompleted = rpc.SessionEventTypeSessionFusionCompleted + SessionEventTypeSessionFusionResolved = rpc.SessionEventTypeSessionFusionResolved + SessionEventTypeSessionFusionRouteFailed = rpc.SessionEventTypeSessionFusionRouteFailed + SessionEventTypeSessionFusionRouteStarted = rpc.SessionEventTypeSessionFusionRouteStarted SessionEventTypeSessionHandoff = rpc.SessionEventTypeSessionHandoff SessionEventTypeSessionIdle = rpc.SessionEventTypeSessionIdle SessionEventTypeSessionInfo = rpc.SessionEventTypeSessionInfo @@ -644,6 +783,7 @@ const ( SessionEventTypeSessionMCPServerStatusChanged = rpc.SessionEventTypeSessionMCPServerStatusChanged SessionEventTypeSessionModeChanged = rpc.SessionEventTypeSessionModeChanged SessionEventTypeSessionModelChange = rpc.SessionEventTypeSessionModelChange + SessionEventTypeSessionModeNoticeDelivered = rpc.SessionEventTypeSessionModeNoticeDelivered SessionEventTypeSessionPermissionsChanged = rpc.SessionEventTypeSessionPermissionsChanged SessionEventTypeSessionPlanChanged = rpc.SessionEventTypeSessionPlanChanged SessionEventTypeSessionRemoteSteerableChanged = rpc.SessionEventTypeSessionRemoteSteerableChanged @@ -667,6 +807,7 @@ const ( SessionEventTypeSessionWorkspaceFileChanged = rpc.SessionEventTypeSessionWorkspaceFileChanged SessionEventTypeSkillInvoked = rpc.SessionEventTypeSkillInvoked SessionEventTypeSubagentCompleted = rpc.SessionEventTypeSubagentCompleted + SessionEventTypeSubagentConfigured = rpc.SessionEventTypeSubagentConfigured SessionEventTypeSubagentDeselected = rpc.SessionEventTypeSubagentDeselected SessionEventTypeSubagentFailed = rpc.SessionEventTypeSubagentFailed SessionEventTypeSubagentSelected = rpc.SessionEventTypeSubagentSelected @@ -679,6 +820,7 @@ const ( SessionEventTypeToolExecutionStart = rpc.SessionEventTypeToolExecutionStart SessionEventTypeToolSearchActivated = rpc.SessionEventTypeToolSearchActivated SessionEventTypeToolUserRequested = rpc.SessionEventTypeToolUserRequested + SessionEventTypeUIEphemeralQuery = rpc.SessionEventTypeUIEphemeralQuery SessionEventTypeUserInputCompleted = rpc.SessionEventTypeUserInputCompleted SessionEventTypeUserInputRequested = rpc.SessionEventTypeUserInputRequested SessionEventTypeUserMessage = rpc.SessionEventTypeUserMessage @@ -701,6 +843,7 @@ const ( SkillSourcePersonalCopilot = rpc.SkillSourcePersonalCopilot SkillSourcePlugin = rpc.SkillSourcePlugin SkillSourceProject = rpc.SkillSourceProject + SkillSourceSDK = rpc.SkillSourceSDK SystemMessageRoleDeveloper = rpc.SystemMessageRoleDeveloper SystemMessageRoleSystem = rpc.SystemMessageRoleSystem SystemNotificationAgentCompletedStatusCompleted = rpc.SystemNotificationAgentCompletedStatusCompleted @@ -733,6 +876,11 @@ const ( ToolExecutionCompleteToolDescriptionMetaUIVisibilityModel = rpc.ToolExecutionCompleteToolDescriptionMetaUIVisibilityModel ToolExecutionStartToolDescriptionMetaUIVisibilityApp = rpc.ToolExecutionStartToolDescriptionMetaUIVisibilityApp ToolExecutionStartToolDescriptionMetaUIVisibilityModel = rpc.ToolExecutionStartToolDescriptionMetaUIVisibilityModel + UIEphemeralQueryPhaseAborted = rpc.UIEphemeralQueryPhaseAborted + UIEphemeralQueryPhaseChunk = rpc.UIEphemeralQueryPhaseChunk + UIEphemeralQueryPhaseCompleted = rpc.UIEphemeralQueryPhaseCompleted + UIEphemeralQueryPhaseFailed = rpc.UIEphemeralQueryPhaseFailed + UIEphemeralQueryPhaseStarted = rpc.UIEphemeralQueryPhaseStarted UserMessageAgentModeAutopilot = rpc.UserMessageAgentModeAutopilot UserMessageAgentModeInteractive = rpc.UserMessageAgentModeInteractive UserMessageAgentModePlan = rpc.UserMessageAgentModePlan @@ -742,6 +890,7 @@ const ( UserMessageDeliverySteering = rpc.UserMessageDeliverySteering UserToolSessionApprovalKindCommands = rpc.UserToolSessionApprovalKindCommands UserToolSessionApprovalKindCustomTool = rpc.UserToolSessionApprovalKindCustomTool + UserToolSessionApprovalKindExtensionEnvAccess = rpc.UserToolSessionApprovalKindExtensionEnvAccess UserToolSessionApprovalKindExtensionManagement = rpc.UserToolSessionApprovalKindExtensionManagement UserToolSessionApprovalKindExtensionPermissionAccess = rpc.UserToolSessionApprovalKindExtensionPermissionAccess UserToolSessionApprovalKindFactory = rpc.UserToolSessionApprovalKindFactory diff --git a/java/.mvn/wrapper/maven-wrapper.properties b/java/.mvn/wrapper/maven-wrapper.properties index 8dea6c227c..216df05897 100644 --- a/java/.mvn/wrapper/maven-wrapper.properties +++ b/java/.mvn/wrapper/maven-wrapper.properties @@ -1,3 +1,3 @@ wrapperVersion=3.3.4 distributionType=only-script -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.16/apache-maven-3.9.16-bin.zip diff --git a/java/README.md b/java/README.md index c3428eee08..bb71d7ab86 100644 --- a/java/README.md +++ b/java/README.md @@ -20,7 +20,11 @@ Java SDK for programmatic control of GitHub Copilot CLI, enabling you to build A To use the SDK, you'll need: - Java 17 or later. **JDK 25 recommended**. The distributed jar is a multi-release jar (MR-JAR) and is compiled on JDK 25 with `maven.compiler.release` set to 17. This means, when run on JDK 25 and later, the SDK automatically uses virtual threads for its default internal executor. -- GitHub Copilot CLI 1.0.55-5 or later installed and in `PATH` (or provide custom `cliPath`) + +Managed stdio and TCP connections materialize the platform classifier's +`copilot-runtime[.exe]` and adjacent `runtime.node` by default. An explicit +`cliPath` or `COPILOT_CLI_PATH` environment variable overrides the bundled +runtime. ## Installation @@ -32,14 +36,14 @@ Replace `${copilot.sdk.version}` with the latest release from Maven Central. com.github copilot-sdk-java - 1.0.9-preview.3 + 1.0.13-preview.5 ``` ### Gradle ```groovy -implementation 'com.github:copilot-sdk-java:1.0.9-preview.3' +implementation 'com.github:copilot-sdk-java:1.0.13-preview.5' ``` #### Snapshot Builds @@ -58,7 +62,7 @@ Snapshot builds of the next development version are published to Maven Central S com.github copilot-sdk-java - 1.0.10-preview.3-SNAPSHOT + 1.0.14-preview.5-SNAPSHOT ``` @@ -67,7 +71,54 @@ 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.10-preview.3-SNAPSHOT' +implementation 'com.github:copilot-sdk-java:1.0.14-preview.5-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 supported on **linux-x64** (glibc), **linux-arm64** (glibc), **win32-x64**, **win32-arm64**, and **darwin-arm64**. + +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 @@ -129,6 +180,32 @@ 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. +`SessionConfig.setAskUserVariant(AskUserVariant.ELICITATION)` selects the +structured form-based `ask_user` tool when an elicitation handler is also set. +The default is `AskUserVariant.LEGACY`. Re-supply the option and handler through +`ResumeSessionConfig` on a cold resume. + +For rotating per-session GitHub credentials, use +`SessionConfig.setGitHubTokenProvider(...)` (or the equivalent +`ResumeSessionConfig` setter) instead of `setGitHubToken(...)`: + +```java +var config = new SessionConfig() + .setGitHubTokenProvider(args -> + acquireForHost(args.host()).thenApply(token -> + GitHubTokenProviderResult.token(token, 8 * 60 * 60))) + .setOnPermissionRequest(PermissionHandler.APPROVE_ALL); +``` + +The remaining lifetime is required and must be positive when the callback +completes; production GitHub tokens typically last eight hours. A static token +and a provider are mutually exclusive. + +Initial acquisition runs during session creation or resume. Cancellation, +provider errors, and invalid token responses reject that operation instead of +falling back to ambient authentication. Idle sessions refresh only before their +next credential-consuming operation; there is no background refresh timer. + ## 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. @@ -156,12 +233,12 @@ PermissionHandler handler = (request, invocation) -> { 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 @@ -264,6 +341,34 @@ Chain fluent modifiers to set tool options: For design context and decision rationale, see [ADR-006](docs/adr/adr-006-tool-definition-inline.md). +## Auto routing tiers + +Use `CapiSessionOptions.setAutoTier(...)` to select `AutoTier.EFFICIENCY`, +`AutoTier.BALANCE`, or `AutoTier.INTELLIGENCE`. This option is meaningful only +with model `auto` (Auto mode V2). +It requires a runtime version that supports `capi.autoTier`. + +```java +import com.github.copilot.rpc.AutoTier; +import com.github.copilot.rpc.CapiSessionOptions; +import com.github.copilot.rpc.PermissionHandler; +import com.github.copilot.rpc.SessionConfig; + +var config = new SessionConfig() + .setOnPermissionRequest(PermissionHandler.APPROVE_ALL) + .setModel("auto") + .setCapi(new CapiSessionOptions().setAutoTier(AutoTier.BALANCE)); +``` + +The same options work with `ResumeSessionConfig.setCapi(...)` and can be combined +with `setEnableWebSocketResponses(false)`. The SDK omits an unset (`null`) tier: +the runtime chooses its default on create and preserves the persisted/current +tier on resume. An explicit tier overrides the persisted tier on cold resume; +the runtime rejects a conflicting tier when the session is already resident +in memory. The SDK does not choose a default or manage tier persistence. +See [Auto tier persistence](../docs/features/session-persistence.md#auto-tier-persistence) +for the lifecycle rules. + ## 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. @@ -441,6 +546,65 @@ mvn verify -Dskip.test.harness=true 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 ``` +#### Development Setup for native embedding + +Run native-runtime Maven commands from the `java` directory. Native packaging requires Node.js in addition to JDK 25 and Maven because `copilot-native/scripts/fetch-native.mjs` retrieves the pinned runtime package from the corresponding GitHub release. + +On a native Linux glibc host, Maven activates `native-linux-x64` or `native-linux-arm64` for the matching architecture when `copilot.native.libc=glibc` is set. On Windows x64, Windows ARM64, and Apple Silicon macOS, Maven activates `native-win32-x64`, `native-win32-arm64`, or `native-darwin-arm64` automatically. The matching profile validates the host, runs the native script tests, fetches the pinned platform package from the corresponding `github/copilot-cli` release during `generate-resources`, packages the classifier JAR during `package`, and verifies its native contents. + +Before opting in, validate that Node.js reports glibc for the build host: + +```bash +node copilot-native/scripts/validate-native-host.mjs linux-x64 +mvn -pl copilot-native clean verify -Dcopilot.native.libc=glibc +``` + +The `inprocess` test profile performs the same validation and native packaging automatically, so the full in-process test command remains: + +```bash +mvn -Pinprocess clean verify +``` + +On Windows x64 or ARM64 PowerShell, initialize Java and run the same profile: + +```powershell +mvn -Pinprocess clean verify +``` + +The same command validates in-process mode on Apple Silicon macOS: + +```bash +node copilot-native/scripts/validate-native-host.mjs darwin-arm64 +mvn -Pinprocess clean verify +``` + +The same command validates in-process mode on Linux ARM64: + +```bash +node copilot-native/scripts/validate-native-host.mjs linux-arm64 +mvn -Pinprocess clean verify -Dcopilot.native.libc=glibc +``` + +On Intel macOS, Linux musl, and other unsupported hosts, do not set `copilot.native.libc=glibc`. A normal build produces only the OS-neutral primary, sources, and Javadoc JARs; it does not run native script tests, download or stage native files, or produce a platform classifier JAR. + +To build only the OS-neutral artifacts on any host, or override the glibc opt-in, disable native download and packaging: + +```bash +mvn -pl copilot-native clean package -DskipTests -Dcopilot.native.libc=glibc -Dcopilot.native.skip.download=true +``` + +The verified Linux x64 checks are: + +```bash +node --test copilot-native/scripts/fetch-native.test.mjs copilot-native/scripts/validate-native-host.test.mjs +mvn -pl copilot-native help:active-profiles -Dcopilot.native.libc=glibc -Dcopilot.native.skip.download=false +mvn -pl copilot-native test -Dcopilot.native.libc=glibc +mvn clean verify -Dcopilot.native.libc=glibc +mvn clean package -pl copilot-native -DskipTests -Dcopilot.native.libc=glibc -Dcopilot.native.skip.download=true +``` + +Each classifier JAR includes `runtime.node`, `platform.properties`, and `copilot-runtime` (or `copilot-runtime.exe`) under its `native/` directory. It does not contain the legacy `copilot` SEA. The placeholder JAR remains OS-neutral and contains no native binaries. Unsupported hosts retain the placeholder-only behavior. + ## 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 0000000000..0abe03ad18 --- /dev/null +++ b/java/copilot-native/pom.xml @@ -0,0 +1,967 @@ + + + + 4.0.0 + + + com.github + copilot-sdk-java-parent + 1.0.14-preview.5-SNAPSHOT + ../pom.xml + + + com.github + copilot-sdk-java-runtime + jar + + GitHub Copilot SDK :: Java :: Native Runtime + Native runtime artifacts for the GitHub Copilot Java SDK, with host-matched 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}/../.. + ${project.build.directory}/native-staging + + false + + + + + + + src/main/resources + true + + + + + + org.codehaus.mojo + exec-maven-plugin + + + validate-native-host + none + + exec + + + node + + ${project.basedir}/scripts/validate-native-host.mjs + ${copilot.native.classifier} + + + + + fetch-native + none + + exec + + + node + + ${project.basedir}/scripts/fetch-native.mjs + ${copilot.sdk.root} + ${copilot.native.staging} + ${copilot.native.classifier} + + + + + test-fetch-native + none + + exec + + + ${skipTests} + node + + --test + ${project.basedir}/scripts/fetch-native.test.mjs + ${project.basedir}/scripts/validate-native-host.test.mjs + ${project.basedir}/scripts/validate-native-artifact.test.mjs + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + jar-native + none + + 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 + none + + run + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.sonatype.central + central-publishing-maven-plugin + true + + central + true + + + + + + + + + inprocess + + linux-x64 + + + + + org.codehaus.mojo + exec-maven-plugin + + + validate-native-host + validate + + + fetch-native + generate-resources + + + test-fetch-native + test + + + + + org.apache.maven.plugins + maven-jar-plugin + + + jar-native + package + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + verify-native-jars + package + + + + + + + + + native-linux-x64 + + + Linux + amd64 + + + copilot.native.libc + glibc + + + + linux-x64 + + + + + org.codehaus.mojo + exec-maven-plugin + + + validate-native-host + validate + + + fetch-native + generate-resources + + + test-fetch-native + test + + + + + org.apache.maven.plugins + maven-jar-plugin + + + jar-native + package + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + verify-native-jars + package + + + + + + + + native-linux-arm64 + + + Linux + aarch64 + + + copilot.native.libc + glibc + + + + linux-arm64 + copilot + + + + + org.codehaus.mojo + exec-maven-plugin + + + validate-native-host + validate + + + fetch-native + generate-resources + + + test-fetch-native + test + + + + + org.apache.maven.plugins + maven-jar-plugin + + + jar-native + package + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + verify-native-jars + package + + + + + + + + native-win32-x64 + + + Windows + amd64 + + + + win32-x64 + + + + + org.codehaus.mojo + exec-maven-plugin + + + validate-native-host + validate + + + fetch-native + generate-resources + + + test-fetch-native + test + + + + + org.apache.maven.plugins + maven-jar-plugin + + + jar-native + package + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + verify-native-jars + package + + + + + + + + native-win32-arm64 + + + Windows + aarch64 + + + + win32-arm64 + copilot.exe + + + + + org.codehaus.mojo + exec-maven-plugin + + + validate-native-host + validate + + + fetch-native + generate-resources + + + test-fetch-native + test + + + + + org.apache.maven.plugins + maven-jar-plugin + + + jar-native + package + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + verify-native-jars + package + + + + + + + + native-darwin-arm64 + + + mac + aarch64 + + + + darwin-arm64 + + + + + org.codehaus.mojo + exec-maven-plugin + + + validate-native-host + validate + + + fetch-native + generate-resources + + + test-fetch-native + test + + + + + org.apache.maven.plugins + maven-jar-plugin + + + jar-native + package + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + verify-native-jars + package + + + + + + + + + attach-external-linux-arm64-classifier + + + copilot.native.external.linux.arm64.classifier.path + + + + + + org.codehaus.mojo + exec-maven-plugin + + + validate-external-linux-arm64-classifier + validate + + exec + + + node + + ${project.basedir}/scripts/validate-native-artifact.mjs + classifier + linux-arm64 + ${copilot.native.external.linux.arm64.classifier.path} + ${project.build.finalName}-linux-arm64.jar + ${copilot.sdk.root} + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + attach-external-linux-arm64-classifier + package + + attach-artifact + + + + + ${copilot.native.external.linux.arm64.classifier.path} + jar + linux-arm64 + + + + + + + + + + + + attach-external-win32-classifier + + + copilot.native.external.win32.classifier.path + + + + + + org.codehaus.mojo + exec-maven-plugin + + + validate-external-win32-classifier + validate + + exec + + + node + + ${project.basedir}/scripts/validate-native-artifact.mjs + classifier + win32-x64 + ${copilot.native.external.win32.classifier.path} + ${project.build.finalName}-win32-x64.jar + ${copilot.sdk.root} + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + attach-external-win32-classifier + package + + attach-artifact + + + + + ${copilot.native.external.win32.classifier.path} + jar + win32-x64 + + + + + + + + + + + + attach-external-win32-arm64-classifier + + + copilot.native.external.win32.arm64.classifier.path + + + + + + org.codehaus.mojo + exec-maven-plugin + + + validate-external-win32-arm64-classifier + validate + + exec + + + node + + ${project.basedir}/scripts/validate-native-artifact.mjs + classifier + win32-arm64 + ${copilot.native.external.win32.arm64.classifier.path} + ${project.build.finalName}-win32-arm64.jar + ${copilot.sdk.root} + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + attach-external-win32-arm64-classifier + package + + attach-artifact + + + + + ${copilot.native.external.win32.arm64.classifier.path} + jar + win32-arm64 + + + + + + + + + + + + attach-external-darwin-classifier + + + copilot.native.external.darwin.classifier.path + + + + + + org.codehaus.mojo + exec-maven-plugin + + + validate-external-darwin-classifier + validate + + exec + + + node + + ${project.basedir}/scripts/validate-native-artifact.mjs + classifier + darwin-arm64 + ${copilot.native.external.darwin.classifier.path} + ${project.build.finalName}-darwin-arm64.jar + ${copilot.sdk.root} + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + attach-external-darwin-classifier + package + + attach-artifact + + + + + ${copilot.native.external.darwin.classifier.path} + jar + darwin-arm64 + + + + + + + + + + + + attach-test-linux-classifier + + + copilot.native.test.linux.classifier.path + + + + + + org.codehaus.mojo + exec-maven-plugin + + + validate-test-linux-classifier + validate + + exec + + + node + + ${project.basedir}/scripts/validate-native-artifact.mjs + classifier + linux-x64 + ${copilot.native.test.linux.classifier.path} + ${project.build.finalName}-linux-x64.jar + ${copilot.sdk.root} + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + attach-test-linux-classifier + package + + attach-artifact + + + + + ${copilot.native.test.linux.classifier.path} + jar + linux-x64 + + + + + + + + + + + + local-publication-validation + + + copilot.native.test.local.publication + true + + + + + + org.sonatype.central + central-publishing-maven-plugin + + true + + + + + + + + skip-native-download + + + copilot.native.skip.download + true + + + + + + org.codehaus.mojo + exec-maven-plugin + + + validate-native-host + none + + + fetch-native + none + + + test-fetch-native + none + + + + + org.apache.maven.plugins + maven-jar-plugin + + + jar-native + none + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + verify-native-jars + none + + + + + + + + diff --git a/java/copilot-native/scripts/create-native-classifier-test-fixture.mjs b/java/copilot-native/scripts/create-native-classifier-test-fixture.mjs new file mode 100644 index 0000000000..08eccb644a --- /dev/null +++ b/java/copilot-native/scripts/create-native-classifier-test-fixture.mjs @@ -0,0 +1,119 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import fs from "node:fs"; +import path from "node:path"; +import { pathToFileURL } from "node:url"; + +import { readPinnedNativeVersion } from "./validate-native-artifact.mjs"; + +export function createNativeClassifierTestFixture({ + classifier, + outputPath, + repoRoot, +}) { + const runtimeFilename = classifier.startsWith("win32") + ? "copilot-runtime.exe" + : "copilot-runtime"; + const nativeVersion = readPinnedNativeVersion(repoRoot, classifier); + const prefix = `native/${classifier}`; + writeStoredZip(outputPath, [ + [`${prefix}/runtime.node`, "test runtime"], + [`${prefix}/${runtimeFilename}`, "test runtime wrapper"], + [ + `${prefix}/platform.properties`, + `classifier=${classifier}\nversion=${nativeVersion}\n`, + ], + ]); +} + +export function writeStoredZip(outputPath, entries) { + const localRecords = []; + const centralRecords = []; + let offset = 0; + + for (const [filename, contents] of entries) { + const filenameBuffer = Buffer.from(filename); + const contentsBuffer = Buffer.isBuffer(contents) + ? contents + : Buffer.from(contents); + const crc = crc32(contentsBuffer); + const localHeader = Buffer.alloc(30); + localHeader.writeUInt32LE(0x04034b50, 0); + localHeader.writeUInt16LE(20, 4); + localHeader.writeUInt32LE(crc, 14); + localHeader.writeUInt32LE(contentsBuffer.length, 18); + localHeader.writeUInt32LE(contentsBuffer.length, 22); + localHeader.writeUInt16LE(filenameBuffer.length, 26); + const localRecord = Buffer.concat([ + localHeader, + filenameBuffer, + contentsBuffer, + ]); + localRecords.push(localRecord); + + const centralHeader = Buffer.alloc(46); + centralHeader.writeUInt32LE(0x02014b50, 0); + centralHeader.writeUInt16LE(20, 4); + centralHeader.writeUInt16LE(20, 6); + centralHeader.writeUInt32LE(crc, 16); + centralHeader.writeUInt32LE(contentsBuffer.length, 20); + centralHeader.writeUInt32LE(contentsBuffer.length, 24); + centralHeader.writeUInt16LE(filenameBuffer.length, 28); + centralHeader.writeUInt32LE(offset, 42); + centralRecords.push(Buffer.concat([centralHeader, filenameBuffer])); + offset += localRecord.length; + } + + const centralDirectory = Buffer.concat(centralRecords); + const endOfCentralDirectory = Buffer.alloc(22); + endOfCentralDirectory.writeUInt32LE(0x06054b50, 0); + endOfCentralDirectory.writeUInt16LE(entries.length, 8); + endOfCentralDirectory.writeUInt16LE(entries.length, 10); + endOfCentralDirectory.writeUInt32LE(centralDirectory.length, 12); + endOfCentralDirectory.writeUInt32LE(offset, 16); + + fs.mkdirSync(path.dirname(outputPath), { recursive: true }); + fs.writeFileSync( + outputPath, + Buffer.concat([...localRecords, centralDirectory, endOfCentralDirectory]), + ); +} + +function crc32(buffer) { + let crc = 0xffffffff; + for (const byte of buffer) { + crc ^= byte; + for (let bit = 0; bit < 8; bit += 1) { + crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0); + } + } + return (crc ^ 0xffffffff) >>> 0; +} + +function main() { + const [classifier, outputPath, repoRoot] = process.argv.slice(2); + if (!classifier || !outputPath || !repoRoot) { + console.error( + "Usage: node create-native-classifier-test-fixture.mjs ", + ); + process.exitCode = 1; + return; + } + + try { + createNativeClassifierTestFixture({ classifier, outputPath, repoRoot }); + console.log(`Created ${classifier} test fixture: ${outputPath}`); + } catch (error) { + console.error(error.message); + process.exitCode = 1; + } +} + +if ( + process.argv[1] && + import.meta.url === pathToFileURL(process.argv[1]).href +) { + main(); +} diff --git a/java/copilot-native/scripts/fetch-native.mjs b/java/copilot-native/scripts/fetch-native.mjs new file mode 100644 index 0000000000..49d2180a73 --- /dev/null +++ b/java/copilot-native/scripts/fetch-native.mjs @@ -0,0 +1,261 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +/** + * Downloads the native runtime artifacts for one platform classifier. + * + * Steps: + * 1. Read the pinned version from `nodejs/package.json`. + * 2. Download the platform npm tarball and `SHA256SUMS.txt` from the matching release. + * 3. Verify the downloaded tarball against the release checksum. + * 4. Stage the hostless runtime tree, flattening the selected prebuild directory + * beside the package's retained top-level runtime assets. + * 5. Write an inventory consumed by the SDK's generic classpath extractor. + * 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 excludedTopLevel = new Set([ + 'app.js', + 'assets', + 'changelog.json', + 'copilot', + 'copilot.exe', + 'foundry-local-sdk', + 'index.js', + 'LICENSE.md', + 'napi-oop-runtime', + 'npm-loader.js', + 'package.json', + 'pvrecorder', + 'queries', + 'README.md', + 'sea-loader.js', + 'webview', +]); + +const [repoRoot, stagingDir, classifier] = process.argv.slice(2); + +if (!repoRoot || !stagingDir || !classifier) { + console.error('Usage: node fetch-native.mjs '); + process.exit(1); +} + +const packagePath = path.join(repoRoot, 'nodejs', 'package.json'); +const packageName = `@github/copilot-${classifier}`; +const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8')); +const version = packageJson.copilotCliVersion; +if (!version) { + console.error(`Could not find copilotCliVersion in ${packagePath}`); + 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 wrapperFilename = isWindows ? 'copilot-runtime.exe' : 'copilot-runtime'; +const wrapperPath = path.join(resourceDir, wrapperFilename); +const inventoryPath = path.join(resourceDir, 'runtime-assets.list'); +const platformPropertiesPath = path.join(resourceDir, 'platform.properties'); +const expectedPlatformProperties = `classifier=${classifier}\nversion=${version}\n`; +const stagingSchema = 'hostless-runtime-v3'; +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(wrapperPath) && + fs.existsSync(inventoryPath) && + fs.existsSync(platformPropertiesPath) && + fs.existsSync(stampPath) +) { + const stampLines = fs.readFileSync(stampPath, 'utf8').trim().split('\n'); + const stampSchema = stampLines[0] || ''; + const stampVersion = stampLines[1] || ''; + const stampTreeDigest = stampLines[3] || ''; + const currentTreeDigest = digestTree(resourceDir); + const currentPlatformProperties = fs.readFileSync(platformPropertiesPath, 'utf8'); + if ( + stampSchema === stagingSchema && + stampVersion === version && + stampTreeDigest === currentTreeDigest && + 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 assetName = `github-copilot-${version}-${classifier}.tgz`; +const releaseBase = ( + process.env.COPILOT_CLI_DOWNLOAD_BASE_URL ?? + 'https://github.com/github/copilot-cli/releases/download' +).replace(/\/+$/, ''); +let archive; +let expectedHash; +if (process.env.COPILOT_CLI_RELEASE_TARBALL) { + archive = fs.readFileSync(process.env.COPILOT_CLI_RELEASE_TARBALL); + expectedHash = process.env.COPILOT_CLI_RELEASE_SHA256; +} else { + const releaseUrl = `${releaseBase}/v${version}`; + const checksums = (await download(`${releaseUrl}/SHA256SUMS.txt`)).toString('utf8'); + expectedHash = findChecksum(checksums, assetName); + archive = await download(`${releaseUrl}/${assetName}`); +} +if (!expectedHash || !/^[a-fA-F0-9]{64}$/.test(expectedHash)) { + throw new Error(`Missing or invalid SHA-256 for ${assetName}`); +} +const actual = createHash('sha256').update(archive).digest('hex'); +if (actual !== expectedHash.toLowerCase()) { + console.error(`Integrity verification failed for ${assetName}`); + console.error(` expected: ${expectedHash}`); + console.error(` actual: ${actual}`); + process.exit(1); +} +console.log(`Integrity verified (${expectedHash.slice(0, 20)}...).`); + +const inventory = []; +const members = execFileSync('tar', ['-tzf', '-'], { encoding: 'utf8', input: archive }) + .split(/\r?\n/) + .filter(Boolean); +for (const member of members) { + const destinationRelative = hostlessRuntimePath(member, classifier); + if (destinationRelative === null) { + continue; + } + const listing = execFileSync('tar', ['-tvzf', '-', member], { + encoding: 'utf8', + input: archive, + }).trim(); + if (listing.startsWith('d')) { + continue; + } + if (!listing.startsWith('-')) { + throw new Error(`Unsupported runtime package entry: ${member}`); + } + const content = execFileSync('tar', ['-xOzf', '-', member], { + encoding: null, + input: archive, + maxBuffer: 512 * 1024 * 1024, + }); + const destination = path.resolve(resourceDir, destinationRelative); + const resourceRoot = `${path.resolve(resourceDir)}${path.sep}`; + if (!destination.startsWith(resourceRoot)) { + throw new Error(`Runtime package entry escapes staging directory: ${member}`); + } + fs.mkdirSync(path.dirname(destination), { recursive: true }); + fs.writeFileSync(destination, content); + const mode = listing.slice(0, 10).includes('x') ? 0o755 : 0o644; + fs.chmodSync(destination, mode); + inventory.push(`${mode.toString(8)}\t${destinationRelative.split(path.sep).join('/')}`); +} +inventory.sort(); +fs.writeFileSync(inventoryPath, `${inventory.join('\n')}\n`); + +if (!fs.existsSync(runtimePath) || !fs.existsSync(wrapperPath)) { + throw new Error(`Package ${packageName}@${version} is missing the runtime wrapper pair`); +} +fs.writeFileSync(platformPropertiesPath, expectedPlatformProperties); +const treeDigest = digestTree(resourceDir); +fs.writeFileSync(stampPath, `${stagingSchema}\n${version}\n${expectedHash}\n${treeDigest}\n`); + +console.log(`Staged ${runtimePath}`); + +function hostlessRuntimePath(packageRelative, platform) { + if (packageRelative.includes('\\')) { + return null; + } + const parts = packageRelative.split('/'); + if (parts[0] !== 'package' || parts.some((part) => !part || part === '..')) { + return null; + } + parts.shift(); + const topLevel = parts[0]; + const fileName = parts.at(-1); + if ( + excludedTopLevel.has(topLevel) || + (topLevel.startsWith('tree-sitter') && topLevel.endsWith('.wasm')) || + (topLevel.startsWith('voice-') && topLevel.endsWith('.js')) || + fileName === 'cli-native.node' || + parts.includes('mediaremote-adapter') || + fileName.startsWith('copilot-runtime-bin') + ) { + return null; + } + if (topLevel === 'prebuilds') { + if (parts[1] !== platform || parts.length < 3) { + return null; + } + return path.join(...parts.slice(2)); + } + return path.join(...parts); +} + +function walkFiles(directory) { + const files = []; + for (const entry of fs.readdirSync(directory, { withFileTypes: true })) { + const entryPath = path.join(directory, entry.name); + if (entry.isDirectory()) { + files.push(...walkFiles(entryPath)); + } else if (entry.isFile()) { + files.push(entryPath); + } + } + return files; +} + +function digestTree(directory) { + const hash = createHash('sha512'); + for (const file of walkFiles(directory).sort()) { + const relative = path.relative(directory, file).split(path.sep).join('/'); + hash.update(relative).update('\0').update(fs.readFileSync(file)).update('\0'); + } + return `sha512-${hash.digest('base64')}`; +} + +async function download(url) { + let lastError; + for (let attempt = 0; attempt < 3; attempt++) { + try { + // lgtm[js/file-access-to-http] The repository-pinned CLI version intentionally selects the release asset. + const response = await fetch(url); + if (response.ok) { + return Buffer.from(await response.arrayBuffer()); + } + await response.body?.cancel(); + lastError = new Error(`${response.status} ${response.statusText}`); + if (response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429) { + break; + } + } catch (error) { + lastError = error; + } + if (attempt < 2) { + await new Promise((resolve) => setTimeout(resolve, 2 ** attempt * 1000)); + } + } + throw new Error(`Failed to download ${url}: ${lastError}`); +} + +function findChecksum(checksums, assetName) { + for (const line of checksums.split(/\r?\n/)) { + const [hash, name] = line.trim().split(/\s+/, 2); + if (name?.replace(/^\*/, '') === assetName && /^[a-fA-F0-9]{64}$/.test(hash)) { + return hash.toLowerCase(); + } + } + throw new Error(`SHA256SUMS.txt does not contain ${assetName}`); +} 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 0000000000..3213d416f0 --- /dev/null +++ b/java/copilot-native/scripts/fetch-native.test.mjs @@ -0,0 +1,209 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { execFileSync, spawnSync } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; +import test from 'node:test'; + +const version = '1.0.79'; +const checksum = '0'.repeat(64); +const runtimeContent = 'runtime content'; +const wrapperContent = 'wrapper content'; +const stagingSchema = 'hostless-runtime-v3'; +const scriptPath = fileURLToPath(new URL('./fetch-native.mjs', import.meta.url)); + +for (const classifier of ['linux-x64', 'linux-arm64', 'win32-x64', 'win32-arm64', 'darwin-arm64']) { + test(`${classifier}: complete hostless artifacts use incremental fast path without a CLI`, (t) => { + const fixture = createFixture(t, classifier); + + const result = runScript(fixture); + + assert.equal(result.status, 0, result.stderr); + assert.match(result.stdout, /already staged/); + }); + + test(`${classifier}: missing runtime wrapper does not use incremental fast path`, (t) => { + const fixture = createFixture(t, classifier); + fs.rmSync(fixture.wrapperPath); + + const result = runScript(fixture); + + assertRestagingAttempted(fixture, result); + }); + + test(`${classifier}: v2 staging schema does not use incremental fast path`, (t) => { + const fixture = createFixture(t, classifier); + const stampPath = path.join(fixture.stagingDir, classifier, '.version'); + fs.writeFileSync(stampPath, fs.readFileSync(stampPath, 'utf8').replace(stagingSchema, 'hostless-runtime-v2')); + + const result = runScript(fixture); + + assertRestagingAttempted(fixture, result); + }); + + test(`${classifier}: missing platform metadata does not use incremental fast path`, (t) => { + const fixture = createFixture(t, classifier); + fs.rmSync(fixture.platformPropertiesPath); + + const result = runScript(fixture); + + assertRestagingAttempted(fixture, result); + }); + + test(`${classifier}: missing retained runtime asset does not use incremental fast path`, (t) => { + const fixture = createFixture(t, classifier); + fs.rmSync(fixture.ripgrepPath); + + const result = runScript(fixture); + + assertRestagingAttempted(fixture, result); + }); + + test(`${classifier}: complete matching artifacts use incremental fast path`, (t) => { + const fixture = createFixture(t, classifier); + + const result = runScript(fixture); + + assert.equal(result.status, 0, result.stderr); + assert.match(result.stdout, /already staged/); + }); +} + +test('stages retained package assets and excludes CLI-only content', (t) => { + const classifier = 'linux-x64'; + const fixture = createFixture(t, classifier); + const packageRoot = path.join(fixture.repoRoot, 'package-root', 'package'); + fs.mkdirSync(path.join(packageRoot, 'prebuilds', classifier), { recursive: true }); + fs.mkdirSync(path.join(packageRoot, 'ripgrep', 'bin', classifier), { recursive: true }); + fs.mkdirSync(path.join(packageRoot, 'definitions'), { recursive: true }); + fs.writeFileSync(path.join(packageRoot, 'copilot'), 'excluded'); + fs.writeFileSync(path.join(packageRoot, 'prebuilds', classifier, 'runtime.node'), runtimeContent); + fs.writeFileSync(path.join(packageRoot, 'prebuilds', classifier, 'copilot-runtime'), wrapperContent); + fs.writeFileSync(path.join(packageRoot, 'ripgrep', 'bin', classifier, 'rg'), 'ripgrep content'); + fs.chmodSync(path.join(packageRoot, 'ripgrep', 'bin', classifier, 'rg'), 0o755); + fs.writeFileSync(path.join(packageRoot, 'definitions', 'future.json'), '{}'); + fs.writeFileSync(path.join(packageRoot, 'app.js'), 'excluded'); + fs.writeFileSync(path.join(packageRoot, 'LICENSE.md'), 'excluded'); + fs.writeFileSync(path.join(packageRoot, 'README.md'), 'excluded'); + const tarball = path.join(fixture.repoRoot, 'fixture.tgz'); + execFileSync('tar', ['-czf', tarball, '-C', path.dirname(packageRoot), 'package']); + const packageChecksum = createHash('sha256').update(fs.readFileSync(tarball)).digest('hex'); + fs.writeFileSync( + path.join(fixture.repoRoot, 'nodejs', 'package.json'), + JSON.stringify({ copilotCliVersion: version }), + ); + fs.rmSync(path.join(fixture.stagingDir, classifier), { recursive: true, force: true }); + + const result = runScript(fixture, { + COPILOT_CLI_RELEASE_TARBALL: tarball, + COPILOT_CLI_RELEASE_SHA256: packageChecksum, + }); + + assert.equal(result.status, 0, result.stderr); + const resourceDir = path.join(fixture.stagingDir, classifier, 'native', classifier); + assert.equal(fs.readFileSync(path.join(resourceDir, 'ripgrep', 'bin', classifier, 'rg'), 'utf8'), 'ripgrep content'); + assert.equal(fs.readFileSync(path.join(resourceDir, 'definitions', 'future.json'), 'utf8'), '{}'); + assert.equal(fs.existsSync(path.join(resourceDir, 'app.js')), false); + assert.equal(fs.existsSync(path.join(resourceDir, 'copilot')), false); + assert.equal(fs.existsSync(path.join(resourceDir, 'LICENSE.md')), false); + assert.equal(fs.existsSync(path.join(resourceDir, 'README.md')), false); + assert.match(fs.readFileSync(path.join(resourceDir, 'runtime-assets.list'), 'utf8'), /ripgrep\/bin\/linux-x64\/rg/); +}); + +function createFixture(t, classifier) { + 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); + fs.mkdirSync(path.join(repoRoot, 'nodejs'), { recursive: true }); + fs.mkdirSync(resourceDir, { recursive: true }); + + fs.writeFileSync( + path.join(repoRoot, 'nodejs', 'package.json'), + JSON.stringify({ copilotCliVersion: version }), + ); + + const runtimePath = path.join(resourceDir, 'runtime.node'); + const wrapperPath = path.join( + resourceDir, + classifier.startsWith('win32') ? 'copilot-runtime.exe' : 'copilot-runtime', + ); + const platformPropertiesPath = path.join(resourceDir, 'platform.properties'); + const ripgrepPath = path.join(resourceDir, 'ripgrep', 'bin', classifier, 'rg'); + const inventoryPath = path.join(resourceDir, 'runtime-assets.list'); + fs.mkdirSync(path.dirname(ripgrepPath), { recursive: true }); + fs.writeFileSync(runtimePath, runtimeContent); + fs.writeFileSync(wrapperPath, wrapperContent); + fs.writeFileSync(ripgrepPath, 'ripgrep content'); + fs.writeFileSync( + inventoryPath, + `644\truntime.node\n755\tcopilot-runtime\n755\tripgrep/bin/${classifier}/rg\n`, + ); + fs.writeFileSync(platformPropertiesPath, `classifier=${classifier}\nversion=${version}\n`); + fs.writeFileSync( + path.join(stagingDir, classifier, '.version'), + `${stagingSchema}\n${version}\n${checksum}\n${digestTree(resourceDir)}\n`, + ); + + return { + root, + classifier, + repoRoot, + stagingDir, + runtimePath, + wrapperPath, + ripgrepPath, + platformPropertiesPath, + }; +} + +function runScript(fixture, extraEnv = {}) { + return spawnSync(process.execPath, [scriptPath, fixture.repoRoot, fixture.stagingDir, fixture.classifier], { + encoding: 'utf8', + env: { + ...process.env, + COPILOT_CLI_RELEASE_TARBALL: path.join(fixture.root, 'missing.tgz'), + COPILOT_CLI_RELEASE_SHA256: checksum, + ...extraEnv, + }, + }); +} + +function assertRestagingAttempted(fixture, result) { + assert.notEqual(result.status, 0, 'The unavailable release should make restaging fail'); +} + +function digestTree(directory) { + const hash = createHash('sha512'); + for (const file of walkFiles(directory).sort()) { + const relative = path.relative(directory, file).split(path.sep).join('/'); + hash.update(relative).update('\0').update(fs.readFileSync(file)).update('\0'); + } + + return `sha512-${hash.digest('base64')}`; +} + +function digest(content) { + return `sha512-${createHash('sha512').update(content).digest('base64')}`; +} + +function walkFiles(directory) { + const files = []; + for (const entry of fs.readdirSync(directory, { withFileTypes: true })) { + const entryPath = path.join(directory, entry.name); + if (entry.isDirectory()) { + files.push(...walkFiles(entryPath)); + } else { + files.push(entryPath); + } + } + return files; +} diff --git a/java/copilot-native/scripts/validate-local-publication.mjs b/java/copilot-native/scripts/validate-local-publication.mjs new file mode 100644 index 0000000000..c83f9682dd --- /dev/null +++ b/java/copilot-native/scripts/validate-local-publication.mjs @@ -0,0 +1,117 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import fs from "node:fs"; +import path from "node:path"; +import { pathToFileURL } from "node:url"; + +import { + validateNativeClassifierJar, + validatePlaceholderJar, +} from "./validate-native-artifact.mjs"; + +export function validateLocalPublication({ + artifactId, + repositoryPath, + repoRoot, + requireSignatures, + version, +}) { + const artifactDirectory = path.join( + repositoryPath, + "com", + "github", + artifactId, + version, + ); + const requiredArtifacts = [ + `${artifactId}-${version}.jar`, + `${artifactId}-${version}.pom`, + `${artifactId}-${version}-sources.jar`, + `${artifactId}-${version}-javadoc.jar`, + `${artifactId}-${version}-linux-x64.jar`, + `${artifactId}-${version}-linux-arm64.jar`, + `${artifactId}-${version}-win32-x64.jar`, + `${artifactId}-${version}-win32-arm64.jar`, + `${artifactId}-${version}-darwin-arm64.jar`, + ]; + const files = new Set(fs.readdirSync(artifactDirectory)); + + for (const artifact of requiredArtifacts) { + if (!files.has(artifact)) { + throw new Error(`Local publication is missing ${artifact}`); + } + if (requireSignatures && !files.has(`${artifact}.asc`)) { + throw new Error( + `Local release publication is missing signature ${artifact}.asc`, + ); + } + } + + const publishedJars = [...files] + .filter((file) => file.endsWith(".jar")) + .sort(); + const expectedJars = requiredArtifacts + .filter((file) => file.endsWith(".jar")) + .sort(); + if (publishedJars.join("\n") !== expectedJars.join("\n")) { + throw new Error( + `Local publication contains unexpected JARs:\n${publishedJars.join("\n")}`, + ); + } + + validatePlaceholderJar( + path.join(artifactDirectory, `${artifactId}-${version}.jar`), + ); + for (const classifier of [ + "linux-x64", + "linux-arm64", + "win32-x64", + "win32-arm64", + "darwin-arm64", + ]) { + const filename = `${artifactId}-${version}-${classifier}.jar`; + validateNativeClassifierJar({ + classifier, + jarPath: path.join(artifactDirectory, filename), + expectedFilename: filename, + repoRoot, + }); + } + + return artifactDirectory; +} + +function main() { + const [repositoryPath, artifactId, version, repoRoot, signatures] = + process.argv.slice(2); + if (!repositoryPath || !artifactId || !version || !repoRoot) { + console.error( + "Usage: node validate-local-publication.mjs [--signatures]", + ); + process.exitCode = 1; + return; + } + + try { + const artifactDirectory = validateLocalPublication({ + artifactId, + repositoryPath, + repoRoot, + requireSignatures: signatures === "--signatures", + version, + }); + console.log(`Validated local Maven publication: ${artifactDirectory}`); + } catch (error) { + console.error(error.message); + process.exitCode = 1; + } +} + +if ( + process.argv[1] && + import.meta.url === pathToFileURL(process.argv[1]).href +) { + main(); +} diff --git a/java/copilot-native/scripts/validate-native-artifact.mjs b/java/copilot-native/scripts/validate-native-artifact.mjs new file mode 100644 index 0000000000..466fa260a4 --- /dev/null +++ b/java/copilot-native/scripts/validate-native-artifact.mjs @@ -0,0 +1,338 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { createHash } from "node:crypto"; +import fs from "node:fs"; +import path from "node:path"; +import { pathToFileURL } from "node:url"; +import zlib from "node:zlib"; + +const END_OF_CENTRAL_DIRECTORY = 0x06054b50; +const CENTRAL_DIRECTORY_FILE_HEADER = 0x02014b50; +const LOCAL_FILE_HEADER = 0x04034b50; +const PLATFORM_CLASSIFIER = + /^(?:linux(?:musl)?-(?:x64|arm64)|darwin-(?:x64|arm64)|win32-(?:x64|arm64))$/; + +export function validateNativeClassifierJar({ + classifier, + jarPath, + expectedFilename, + repoRoot, +}) { + if (path.basename(jarPath) !== expectedFilename) { + throw new Error( + `External ${classifier} classifier must be named ${expectedFilename}; received ${path.basename(jarPath)}`, + ); + } + + const archive = readJar(jarPath); + const runtimeFilename = classifier.startsWith("win32") + ? "copilot-runtime.exe" + : "copilot-runtime"; + const resourcePrefix = `native/${classifier}/`; + const requiredEntries = [ + `${resourcePrefix}runtime.node`, + `${resourcePrefix}${runtimeFilename}`, + `${resourcePrefix}platform.properties`, + ]; + + for (const entryName of requiredEntries) { + const entry = archive.entries.get(entryName); + if (!entry || entry.uncompressedSize === 0) { + throw new Error( + `External ${classifier} classifier is missing nonempty ${entryName}`, + ); + } + } + + for (const entryName of archive.entries.keys()) { + const nativeClassifier = /^native\/([^/]+)\//.exec(entryName)?.[1]; + if (nativeClassifier && nativeClassifier !== classifier) { + throw new Error( + `External ${classifier} classifier must not contain ${entryName}`, + ); + } + } + + const properties = parseProperties( + readEntry(archive, `${resourcePrefix}platform.properties`), + ); + const expectedVersion = readPinnedNativeVersion(repoRoot, classifier); + if (properties.classifier !== classifier) { + throw new Error( + `External classifier metadata must contain classifier=${classifier}; received ${properties.classifier ?? "none"}`, + ); + } + if (properties.version !== expectedVersion) { + throw new Error( + `External ${classifier} classifier metadata must contain version=${expectedVersion}; received ${properties.version ?? "none"}`, + ); + } + + return { + classifier, + nativeVersion: expectedVersion, + sha256: undefined, + }; +} + +export function validatePlaceholderJar(jarPath) { + const archive = readJar(jarPath); + for (const entryName of archive.entries.keys()) { + const nativeClassifier = /^native\/([^/]+)\//.exec(entryName)?.[1]; + if (nativeClassifier && PLATFORM_CLASSIFIER.test(nativeClassifier)) { + throw new Error( + `Placeholder primary JAR must not contain platform-native resources; found ${entryName}`, + ); + } + } +} + +export function validateSha256Manifest({ + expectedFilename, + jarPath, + manifestPath, +}) { + let manifest; + try { + manifest = fs.readFileSync(manifestPath, "utf8").trim(); + } catch (error) { + throw new Error( + `Could not read SHA-256 manifest ${manifestPath}: ${error.message}`, + ); + } + const match = /^([a-fA-F0-9]{64}) {2}(.+)$/.exec(manifest); + if (!match || match[2] !== expectedFilename) { + throw new Error( + `SHA-256 manifest must contain one checksum for ${expectedFilename}: ${manifestPath}`, + ); + } + const actual = createHash("sha256") + .update(fs.readFileSync(jarPath)) + .digest("hex"); + if (actual !== match[1].toLowerCase()) { + throw new Error( + `SHA-256 mismatch for ${expectedFilename}; expected ${match[1].toLowerCase()}, received ${actual}`, + ); + } +} + +export function readPinnedNativeVersion(repoRoot, classifier) { + const packagePath = path.join(repoRoot, "nodejs", "package.json"); + let packageJson; + try { + packageJson = JSON.parse(fs.readFileSync(packagePath, "utf8")); + } catch (error) { + throw new Error( + `Could not read pinned Copilot CLI version from ${packagePath}: ${error.message}`, + ); + } + + const version = packageJson.copilotCliVersion; + if (!version) { + throw new Error(`Could not find copilotCliVersion in ${packagePath}`); + } + return version; +} + +function readJar(jarPath) { + let stat; + try { + stat = fs.statSync(jarPath); + } catch (error) { + throw new Error(`External classifier JAR does not exist: ${jarPath}`); + } + if (!stat.isFile() || path.extname(jarPath).toLowerCase() !== ".jar") { + throw new Error( + `External classifier must be a regular .jar file: ${jarPath}`, + ); + } + + const data = fs.readFileSync(jarPath); + const eocdOffset = findEndOfCentralDirectory(data); + const entryCount = data.readUInt16LE(eocdOffset + 10); + const centralDirectorySize = data.readUInt32LE(eocdOffset + 12); + const centralDirectoryOffset = data.readUInt32LE(eocdOffset + 16); + if (entryCount === 0xffff || centralDirectoryOffset === 0xffffffff) { + throw new Error(`ZIP64 classifier JARs are not supported: ${jarPath}`); + } + if (centralDirectoryOffset + centralDirectorySize > data.length) { + throw new Error( + `Classifier JAR has an invalid central directory: ${jarPath}`, + ); + } + + const entries = new Map(); + let offset = centralDirectoryOffset; + for (let index = 0; index < entryCount; index += 1) { + if (readUInt32(data, offset, jarPath) !== CENTRAL_DIRECTORY_FILE_HEADER) { + throw new Error( + `Classifier JAR has an invalid central directory entry: ${jarPath}`, + ); + } + const compressionMethod = data.readUInt16LE(offset + 10); + const compressedSize = data.readUInt32LE(offset + 20); + const uncompressedSize = data.readUInt32LE(offset + 24); + const filenameLength = data.readUInt16LE(offset + 28); + const extraLength = data.readUInt16LE(offset + 30); + const commentLength = data.readUInt16LE(offset + 32); + const localHeaderOffset = data.readUInt32LE(offset + 42); + const filenameOffset = offset + 46; + const nextOffset = + filenameOffset + filenameLength + extraLength + commentLength; + if (nextOffset > data.length) { + throw new Error( + `Classifier JAR has a truncated central directory entry: ${jarPath}`, + ); + } + + const filename = data.toString( + "utf8", + filenameOffset, + filenameOffset + filenameLength, + ); + if (entries.has(filename)) { + throw new Error( + `Classifier JAR contains duplicate entry ${filename}: ${jarPath}`, + ); + } + entries.set(filename, { + compressionMethod, + compressedSize, + uncompressedSize, + localHeaderOffset, + }); + offset = nextOffset; + } + + return { data, entries, jarPath }; +} + +function findEndOfCentralDirectory(data) { + const minimumSize = 22; + const minimumOffset = Math.max(0, data.length - minimumSize - 0xffff); + for ( + let offset = data.length - minimumSize; + offset >= minimumOffset; + offset -= 1 + ) { + if ( + data.readUInt32LE(offset) === END_OF_CENTRAL_DIRECTORY && + offset + minimumSize + data.readUInt16LE(offset + 20) === data.length + ) { + return offset; + } + } + throw new Error("Classifier JAR is not a valid ZIP archive"); +} + +function readEntry(archive, entryName) { + const entry = archive.entries.get(entryName); + if (!entry) { + throw new Error(`Classifier JAR is missing ${entryName}`); + } + if (entry.uncompressedSize > 1024 * 1024) { + throw new Error( + `Classifier JAR metadata entry is unexpectedly large: ${entryName}`, + ); + } + + const { data, jarPath } = archive; + const localOffset = entry.localHeaderOffset; + if (readUInt32(data, localOffset, jarPath) !== LOCAL_FILE_HEADER) { + throw new Error( + `Classifier JAR has an invalid local entry for ${entryName}`, + ); + } + const filenameLength = data.readUInt16LE(localOffset + 26); + const extraLength = data.readUInt16LE(localOffset + 28); + const contentsOffset = localOffset + 30 + filenameLength + extraLength; + const contentsEnd = contentsOffset + entry.compressedSize; + if (contentsEnd > data.length) { + throw new Error( + `Classifier JAR has a truncated local entry for ${entryName}`, + ); + } + + const compressed = data.subarray(contentsOffset, contentsEnd); + let contents; + if (entry.compressionMethod === 0) { + contents = compressed; + } else if (entry.compressionMethod === 8) { + contents = zlib.inflateRawSync(compressed); + } else { + throw new Error( + `Classifier JAR uses unsupported compression for ${entryName}: ${entry.compressionMethod}`, + ); + } + if (contents.length !== entry.uncompressedSize) { + throw new Error(`Classifier JAR has an invalid size for ${entryName}`); + } + return contents.toString("utf8"); +} + +function parseProperties(contents) { + const properties = {}; + for (const line of contents.split(/\r?\n/)) { + const separator = line.indexOf("="); + if (separator > 0) { + properties[line.slice(0, separator).trim()] = line + .slice(separator + 1) + .trim(); + } + } + return properties; +} + +function readUInt32(data, offset, jarPath) { + if (offset + 4 > data.length) { + throw new Error(`Classifier JAR is truncated: ${jarPath}`); + } + return data.readUInt32LE(offset); +} + +function main() { + const [mode, ...arguments_] = process.argv.slice(2); + try { + if (mode === "classifier" && arguments_.length === 4) { + const [classifier, jarPath, expectedFilename, repoRoot] = arguments_; + const result = validateNativeClassifierJar({ + classifier, + jarPath, + expectedFilename, + repoRoot, + }); + console.log( + `Validated ${result.classifier} classifier JAR (${result.nativeVersion}): ${jarPath}`, + ); + return; + } + if (mode === "placeholder" && arguments_.length === 1) { + validatePlaceholderJar(arguments_[0]); + console.log(`Validated native-free placeholder JAR: ${arguments_[0]}`); + return; + } + if (mode === "checksum" && arguments_.length === 3) { + const [jarPath, manifestPath, expectedFilename] = arguments_; + validateSha256Manifest({ expectedFilename, jarPath, manifestPath }); + console.log( + `Validated SHA-256 manifest for ${expectedFilename}: ${manifestPath}`, + ); + return; + } + throw new Error( + "Usage: node validate-native-artifact.mjs classifier | placeholder | checksum ", + ); + } catch (error) { + console.error(error.message); + process.exitCode = 1; + } +} + +if ( + process.argv[1] && + import.meta.url === pathToFileURL(process.argv[1]).href +) { + main(); +} diff --git a/java/copilot-native/scripts/validate-native-artifact.test.mjs b/java/copilot-native/scripts/validate-native-artifact.test.mjs new file mode 100644 index 0000000000..c07695a484 --- /dev/null +++ b/java/copilot-native/scripts/validate-native-artifact.test.mjs @@ -0,0 +1,549 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import assert from "node:assert/strict"; +import { createHash } from "node:crypto"; +import fs from "node:fs"; +import path from "node:path"; +import test from "node:test"; +import { fileURLToPath } from "node:url"; + +import { + createNativeClassifierTestFixture, + writeStoredZip, +} from "./create-native-classifier-test-fixture.mjs"; +import { + validateNativeClassifierJar, + validatePlaceholderJar, + validateSha256Manifest, +} from "./validate-native-artifact.mjs"; +import { validateLocalPublication } from "./validate-local-publication.mjs"; + +const classifier = "win32-x64"; +const artifactName = "copilot-sdk-java-runtime-1.2.3-win32-x64.jar"; +const moduleRoot = fileURLToPath(new URL("../", import.meta.url)); + +test("accepts a matching, complete Windows classifier", (t) => { + const fixture = createFixture(t); + createNativeClassifierTestFixture({ + classifier, + outputPath: fixture.jarPath, + repoRoot: fixture.repoRoot, + }); + + assert.deepEqual( + validateNativeClassifierJar({ + classifier, + jarPath: fixture.jarPath, + expectedFilename: artifactName, + repoRoot: fixture.repoRoot, + }), + { classifier, nativeVersion: "9.8.7", sha256: undefined }, + ); +}); + +test("accepts a matching, complete Windows ARM64 classifier", (t) => { + const fixture = createFixture(t); + const windowsArm64Classifier = "win32-arm64"; + const windowsArm64ArtifactName = + "copilot-sdk-java-runtime-1.2.3-win32-arm64.jar"; + const windowsArm64JarPath = path.join( + fixture.root, + windowsArm64ArtifactName, + ); + createNativeClassifierTestFixture({ + classifier: windowsArm64Classifier, + outputPath: windowsArm64JarPath, + repoRoot: fixture.repoRoot, + }); + + assert.deepEqual( + validateNativeClassifierJar({ + classifier: windowsArm64Classifier, + jarPath: windowsArm64JarPath, + expectedFilename: windowsArm64ArtifactName, + repoRoot: fixture.repoRoot, + }), + { + classifier: windowsArm64Classifier, + nativeVersion: "9.8.7", + sha256: undefined, + }, + ); +}); + +test("accepts a matching, complete Darwin classifier", (t) => { + const fixture = createFixture(t); + const darwinClassifier = "darwin-arm64"; + const darwinArtifactName = + "copilot-sdk-java-runtime-1.2.3-darwin-arm64.jar"; + const darwinJarPath = path.join(fixture.root, darwinArtifactName); + createNativeClassifierTestFixture({ + classifier: darwinClassifier, + outputPath: darwinJarPath, + repoRoot: fixture.repoRoot, + }); + + assert.deepEqual( + validateNativeClassifierJar({ + classifier: darwinClassifier, + jarPath: darwinJarPath, + expectedFilename: darwinArtifactName, + repoRoot: fixture.repoRoot, + }), + { + classifier: darwinClassifier, + nativeVersion: "9.8.7", + sha256: undefined, + }, + ); +}); + +test("accepts a matching, complete Linux ARM64 classifier", (t) => { + const fixture = createFixture(t); + const linuxArm64Classifier = "linux-arm64"; + const linuxArm64ArtifactName = + "copilot-sdk-java-runtime-1.2.3-linux-arm64.jar"; + const linuxArm64JarPath = path.join(fixture.root, linuxArm64ArtifactName); + createNativeClassifierTestFixture({ + classifier: linuxArm64Classifier, + outputPath: linuxArm64JarPath, + repoRoot: fixture.repoRoot, + }); + + assert.deepEqual( + validateNativeClassifierJar({ + classifier: linuxArm64Classifier, + jarPath: linuxArm64JarPath, + expectedFilename: linuxArm64ArtifactName, + repoRoot: fixture.repoRoot, + }), + { + classifier: linuxArm64Classifier, + nativeVersion: "9.8.7", + sha256: undefined, + }, + ); +}); + +test("rejects a wrong external filename before attachment", (t) => { + const fixture = createFixture(t); + const wrongName = path.join(fixture.root, "arbitrary.jar"); + createNativeClassifierTestFixture({ + classifier, + outputPath: wrongName, + repoRoot: fixture.repoRoot, + }); + + assert.throws( + () => + validateNativeClassifierJar({ + classifier, + jarPath: wrongName, + expectedFilename: artifactName, + repoRoot: fixture.repoRoot, + }), + /must be named/, + ); +}); + +test("rejects a missing classifier JAR with the expected filename", (t) => { + const fixture = createFixture(t); + + assert.throws( + () => + validateNativeClassifierJar({ + classifier, + jarPath: fixture.jarPath, + expectedFilename: artifactName, + repoRoot: fixture.repoRoot, + }), + /does not exist/, + ); +}); + +test("rejects missing native resources", (t) => { + const fixture = createFixture(t); + writeStoredZip(fixture.jarPath, [ + ["native/win32-x64/runtime.node", "runtime"], + [ + "native/win32-x64/platform.properties", + "classifier=win32-x64\nversion=9.8.7\n", + ], + ]); + + assert.throws( + () => + validateNativeClassifierJar({ + classifier, + jarPath: fixture.jarPath, + expectedFilename: artifactName, + repoRoot: fixture.repoRoot, + }), + /copilot-runtime\.exe/, + ); +}); + +test("rejects incorrect pinned package metadata", (t) => { + const fixture = createFixture(t); + writeStoredZip(fixture.jarPath, [ + ["native/win32-x64/runtime.node", "runtime"], + ["native/win32-x64/copilot-runtime.exe", "runtime wrapper"], + [ + "native/win32-x64/platform.properties", + "classifier=win32-x64\nversion=0.0.1\n", + ], + ]); + + assert.throws( + () => + validateNativeClassifierJar({ + classifier, + jarPath: fixture.jarPath, + expectedFilename: artifactName, + repoRoot: fixture.repoRoot, + }), + /version=9\.8\.7/, + ); +}); + +test("rejects Linux resources in a Windows classifier", (t) => { + const fixture = createFixture(t); + createNativeClassifierTestFixture({ + classifier, + outputPath: fixture.jarPath, + repoRoot: fixture.repoRoot, + }); + writeStoredZip(fixture.jarPath, [ + ["native/win32-x64/runtime.node", "runtime"], + ["native/win32-x64/copilot-runtime.exe", "runtime wrapper"], + [ + "native/win32-x64/platform.properties", + "classifier=win32-x64\nversion=9.8.7\n", + ], + ["native/linux-x64/runtime.node", "wrong platform"], + ]); + + assert.throws( + () => + validateNativeClassifierJar({ + classifier, + jarPath: fixture.jarPath, + expectedFilename: artifactName, + repoRoot: fixture.repoRoot, + }), + /must not contain/, + ); +}); + +test("rejects Windows resources in a Linux classifier", (t) => { + const fixture = createFixture(t); + const linuxClassifier = "linux-x64"; + const linuxArtifactName = + "copilot-sdk-java-runtime-1.2.3-linux-x64.jar"; + const linuxJarPath = path.join(fixture.root, linuxArtifactName); + writeStoredZip(linuxJarPath, [ + ["native/linux-x64/runtime.node", "runtime"], + ["native/linux-x64/copilot-runtime", "runtime wrapper"], + [ + "native/linux-x64/platform.properties", + "classifier=linux-x64\nversion=9.8.7\n", + ], + ["native/win32-x64/runtime.node", "wrong platform"], + ]); + + assert.throws( + () => + validateNativeClassifierJar({ + classifier: linuxClassifier, + jarPath: linuxJarPath, + expectedFilename: linuxArtifactName, + repoRoot: fixture.repoRoot, + }), + /must not contain/, + ); +}); + +test("rejects native resources in the placeholder JAR", (t) => { + const fixture = createFixture(t); + writeStoredZip(fixture.jarPath, [ + ["native/win32-x64/runtime.node", "runtime"], + ]); + + assert.throws( + () => validatePlaceholderJar(fixture.jarPath), + /must not contain/, + ); +}); + +test("rejects Darwin native resources in the placeholder JAR", (t) => { + const fixture = createFixture(t); + writeStoredZip(fixture.jarPath, [ + ["native/darwin-arm64/runtime.node", "runtime"], + ]); + + assert.throws( + () => validatePlaceholderJar(fixture.jarPath), + /must not contain/, + ); +}); + +test("accepts a native-free placeholder JAR", (t) => { + const fixture = createFixture(t); + writeStoredZip(fixture.jarPath, [ + ["META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n"], + ]); + + assert.doesNotThrow(() => validatePlaceholderJar(fixture.jarPath)); +}); + +test("rejects an invalid SHA-256 manifest", (t) => { + const fixture = createFixture(t); + createNativeClassifierTestFixture({ + classifier, + outputPath: fixture.jarPath, + repoRoot: fixture.repoRoot, + }); + const manifestPath = path.join(fixture.root, "classifier.sha256"); + fs.writeFileSync(manifestPath, `${"0".repeat(64)} ${artifactName}\n`); + + assert.throws( + () => + validateSha256Manifest({ + expectedFilename: artifactName, + jarPath: fixture.jarPath, + manifestPath, + }), + /SHA-256 mismatch/, + ); +}); + +test("accepts a matching SHA-256 manifest", (t) => { + const fixture = createFixture(t); + createNativeClassifierTestFixture({ + classifier, + outputPath: fixture.jarPath, + repoRoot: fixture.repoRoot, + }); + const manifestPath = path.join(fixture.root, "classifier.sha256"); + const digest = createHash("sha256") + .update(fs.readFileSync(fixture.jarPath)) + .digest("hex"); + fs.writeFileSync(manifestPath, `${digest} ${artifactName}\n`); + + assert.doesNotThrow(() => + validateSha256Manifest({ + expectedFilename: artifactName, + jarPath: fixture.jarPath, + manifestPath, + }), + ); +}); + +test("validates one complete signed local publication", (t) => { + const fixture = createFixture(t); + const artifactId = "copilot-sdk-java-runtime"; + const version = "1.2.3"; + const publicationDirectory = path.join( + fixture.root, + "repository", + "com", + "github", + artifactId, + version, + ); + fs.mkdirSync(publicationDirectory, { recursive: true }); + const primaryJar = `${artifactId}-${version}.jar`; + const linuxJar = `${artifactId}-${version}-linux-x64.jar`; + const linuxArm64Jar = `${artifactId}-${version}-linux-arm64.jar`; + const windowsJar = `${artifactId}-${version}-win32-x64.jar`; + const windowsArm64Jar = `${artifactId}-${version}-win32-arm64.jar`; + const darwinJar = `${artifactId}-${version}-darwin-arm64.jar`; + writeStoredZip(path.join(publicationDirectory, primaryJar), [ + ["META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n"], + ]); + writeStoredZip( + path.join(publicationDirectory, `${artifactId}-${version}-sources.jar`), + [], + ); + writeStoredZip( + path.join(publicationDirectory, `${artifactId}-${version}-javadoc.jar`), + [], + ); + createNativeClassifierTestFixture({ + classifier: "linux-x64", + outputPath: path.join(publicationDirectory, linuxJar), + repoRoot: fixture.repoRoot, + }); + createNativeClassifierTestFixture({ + classifier: "linux-arm64", + outputPath: path.join(publicationDirectory, linuxArm64Jar), + repoRoot: fixture.repoRoot, + }); + createNativeClassifierTestFixture({ + classifier, + outputPath: path.join(publicationDirectory, windowsJar), + repoRoot: fixture.repoRoot, + }); + createNativeClassifierTestFixture({ + classifier: "win32-arm64", + outputPath: path.join(publicationDirectory, windowsArm64Jar), + repoRoot: fixture.repoRoot, + }); + createNativeClassifierTestFixture({ + classifier: "darwin-arm64", + outputPath: path.join(publicationDirectory, darwinJar), + repoRoot: fixture.repoRoot, + }); + fs.writeFileSync( + path.join(publicationDirectory, `${artifactId}-${version}.pom`), + "", + ); + for (const artifact of [ + primaryJar, + `${artifactId}-${version}.pom`, + `${artifactId}-${version}-sources.jar`, + `${artifactId}-${version}-javadoc.jar`, + linuxJar, + linuxArm64Jar, + windowsJar, + windowsArm64Jar, + darwinJar, + ]) { + fs.writeFileSync( + path.join(publicationDirectory, `${artifact}.asc`), + "signature", + ); + } + + assert.equal( + validateLocalPublication({ + artifactId, + repositoryPath: path.join(fixture.root, "repository"), + repoRoot: fixture.repoRoot, + requireSignatures: true, + version, + }), + publicationDirectory, + ); +}); + +test("local publication validation rejects cross-classifier contamination", (t) => { + const fixture = createFixture(t); + const artifactId = "copilot-sdk-java-runtime"; + const version = "1.2.3"; + const publicationDirectory = path.join( + fixture.root, + "repository", + "com", + "github", + artifactId, + version, + ); + fs.mkdirSync(publicationDirectory, { recursive: true }); + + writeStoredZip( + path.join(publicationDirectory, `${artifactId}-${version}.jar`), + [["META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n"]], + ); + writeStoredZip( + path.join(publicationDirectory, `${artifactId}-${version}-sources.jar`), + [], + ); + writeStoredZip( + path.join(publicationDirectory, `${artifactId}-${version}-javadoc.jar`), + [], + ); + writeStoredZip( + path.join(publicationDirectory, `${artifactId}-${version}-linux-x64.jar`), + [ + ["native/linux-x64/runtime.node", "runtime"], + ["native/linux-x64/copilot-runtime", "runtime wrapper"], + [ + "native/linux-x64/platform.properties", + "classifier=linux-x64\nversion=9.8.7\n", + ], + ["native/win32-x64/runtime.node", "wrong platform"], + ], + ); + createNativeClassifierTestFixture({ + classifier: "linux-arm64", + outputPath: path.join( + publicationDirectory, + `${artifactId}-${version}-linux-arm64.jar`, + ), + repoRoot: fixture.repoRoot, + }); + createNativeClassifierTestFixture({ + classifier: "win32-x64", + outputPath: path.join( + publicationDirectory, + `${artifactId}-${version}-win32-x64.jar`, + ), + repoRoot: fixture.repoRoot, + }); + createNativeClassifierTestFixture({ + classifier: "win32-arm64", + outputPath: path.join( + publicationDirectory, + `${artifactId}-${version}-win32-arm64.jar`, + ), + repoRoot: fixture.repoRoot, + }); + createNativeClassifierTestFixture({ + classifier: "darwin-arm64", + outputPath: path.join( + publicationDirectory, + `${artifactId}-${version}-darwin-arm64.jar`, + ), + repoRoot: fixture.repoRoot, + }); + fs.writeFileSync( + path.join(publicationDirectory, `${artifactId}-${version}.pom`), + "", + ); + + assert.throws( + () => + validateLocalPublication({ + artifactId, + repositoryPath: path.join(fixture.root, "repository"), + repoRoot: fixture.repoRoot, + requireSignatures: false, + version, + }), + /must not contain/, + ); +}); + +function createFixture(t) { + const fixtureParent = path.join( + moduleRoot, + "target", + "validate-native-artifact-test-", + ); + fs.mkdirSync(fixtureParent, { recursive: true }); + const root = fs.mkdtempSync(path.join(fixtureParent, `${process.pid}-`)); + t.after(() => + fs.rmSync(root, { + recursive: true, + force: true, + maxRetries: 3, + retryDelay: 100, + }), + ); + + const repoRoot = path.join(root, "repo"); + fs.mkdirSync(path.join(repoRoot, "nodejs"), { recursive: true }); + fs.writeFileSync( + path.join(repoRoot, "nodejs", "package.json"), + JSON.stringify({ copilotCliVersion: "9.8.7" }), + ); + + return { + root, + repoRoot, + jarPath: path.join(root, artifactName), + }; +} diff --git a/java/copilot-native/scripts/validate-native-host.mjs b/java/copilot-native/scripts/validate-native-host.mjs new file mode 100644 index 0000000000..a0b7a70047 --- /dev/null +++ b/java/copilot-native/scripts/validate-native-host.mjs @@ -0,0 +1,77 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { pathToFileURL } from "node:url"; + +export function validateNativeHost(classifier, host) { + if (classifier === "linux-x64" || classifier === "linux-arm64") { + const expectedArch = classifier === "linux-x64" ? "x64" : "arm64"; + const displayArch = expectedArch === "x64" ? "x64" : "ARM64"; + if (host.platform !== "linux" || host.arch !== expectedArch) { + throw new Error( + `Native ${classifier} packaging requires Linux ${displayArch}; detected ${host.platform}-${host.arch}`, + ); + } + if (!host.glibcVersionRuntime) { + throw new Error( + `Native ${classifier} packaging requires glibc; musl and unknown libc hosts are unsupported`, + ); + } + return `Validated native build host: ${classifier} (glibc ${host.glibcVersionRuntime})`; + } + + if (classifier === "win32-x64" || classifier === "win32-arm64") { + const expectedArch = classifier === "win32-x64" ? "x64" : "arm64"; + const displayArch = expectedArch === "x64" ? "x64" : "ARM64"; + if (host.platform !== "win32" || host.arch !== expectedArch) { + throw new Error( + `Native ${classifier} packaging requires Windows ${displayArch}; detected ${host.platform}-${host.arch}`, + ); + } + return `Validated native build host: ${classifier}`; + } + + if (classifier === "darwin-arm64") { + if (host.platform !== "darwin" || host.arch !== "arm64") { + throw new Error( + `Native ${classifier} packaging requires macOS ARM64; detected ${host.platform}-${host.arch}`, + ); + } + return `Validated native build host: ${classifier}`; + } + + throw new Error(`Unsupported native build classifier: ${classifier}`); +} + +export function detectNativeHost() { + const report = process.report?.getReport(); + return { + platform: process.platform, + arch: process.arch, + glibcVersionRuntime: report?.header?.glibcVersionRuntime, + }; +} + +function main() { + const [classifier] = process.argv.slice(2); + if (!classifier) { + console.error("Usage: node validate-native-host.mjs "); + process.exitCode = 1; + return; + } + + try { + console.log(validateNativeHost(classifier, detectNativeHost())); + } catch (error) { + console.error(error.message); + process.exitCode = 1; + } +} + +if ( + process.argv[1] && + import.meta.url === pathToFileURL(process.argv[1]).href +) { + main(); +} diff --git a/java/copilot-native/scripts/validate-native-host.test.mjs b/java/copilot-native/scripts/validate-native-host.test.mjs new file mode 100644 index 0000000000..cb3429489c --- /dev/null +++ b/java/copilot-native/scripts/validate-native-host.test.mjs @@ -0,0 +1,195 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import assert from "node:assert/strict"; +import test from "node:test"; + +import { validateNativeHost } from "./validate-native-host.mjs"; + +test("accepts Linux x64 with glibc", () => { + assert.equal( + validateNativeHost("linux-x64", { + platform: "linux", + arch: "x64", + glibcVersionRuntime: "2.39", + }), + "Validated native build host: linux-x64 (glibc 2.39)", + ); +}); + +test("accepts Linux ARM64 with glibc", () => { + assert.equal( + validateNativeHost("linux-arm64", { + platform: "linux", + arch: "arm64", + glibcVersionRuntime: "2.39", + }), + "Validated native build host: linux-arm64 (glibc 2.39)", + ); +}); + +test("accepts Windows x64 without a libc requirement", () => { + assert.equal( + validateNativeHost("win32-x64", { + platform: "win32", + arch: "x64", + glibcVersionRuntime: undefined, + }), + "Validated native build host: win32-x64", + ); +}); + +test("accepts Windows ARM64 without a libc requirement", () => { + assert.equal( + validateNativeHost("win32-arm64", { + platform: "win32", + arch: "arm64", + glibcVersionRuntime: undefined, + }), + "Validated native build host: win32-arm64", + ); +}); + +test("accepts macOS ARM64 without a libc requirement", () => { + assert.equal( + validateNativeHost("darwin-arm64", { + platform: "darwin", + arch: "arm64", + glibcVersionRuntime: undefined, + }), + "Validated native build host: darwin-arm64", + ); +}); + +test("rejects Linux x64 with musl or unknown libc", () => { + assert.throws( + () => + validateNativeHost("linux-x64", { + platform: "linux", + arch: "x64", + glibcVersionRuntime: undefined, + }), + /requires glibc/, + ); +}); + +test("rejects Linux ARM64 with musl or unknown libc", () => { + assert.throws( + () => + validateNativeHost("linux-arm64", { + platform: "linux", + arch: "arm64", + glibcVersionRuntime: undefined, + }), + /requires glibc/, + ); +}); + +test("rejects a non-Linux host", () => { + assert.throws( + () => + validateNativeHost("linux-x64", { + platform: "darwin", + arch: "x64", + glibcVersionRuntime: undefined, + }), + /requires Linux x64/, + ); +}); + +test("rejects a non-x64 host", () => { + assert.throws( + () => + validateNativeHost("linux-x64", { + platform: "linux", + arch: "arm64", + glibcVersionRuntime: "2.39", + }), + /requires Linux x64/, + ); +}); + +test("rejects Linux x64 for the Linux ARM64 classifier", () => { + assert.throws( + () => + validateNativeHost("linux-arm64", { + platform: "linux", + arch: "x64", + glibcVersionRuntime: "2.39", + }), + /requires Linux ARM64/, + ); +}); + +test("rejects a non-Windows host for the Windows classifier", () => { + assert.throws( + () => + validateNativeHost("win32-x64", { + platform: "linux", + arch: "x64", + glibcVersionRuntime: "2.39", + }), + /requires Windows x64/, + ); +}); + +test("rejects Windows ARM64 for the Windows x64 classifier", () => { + assert.throws( + () => + validateNativeHost("win32-x64", { + platform: "win32", + arch: "arm64", + glibcVersionRuntime: undefined, + }), + /requires Windows x64/, + ); +}); + +test("rejects Windows x64 for the Windows ARM64 classifier", () => { + assert.throws( + () => + validateNativeHost("win32-arm64", { + platform: "win32", + arch: "x64", + glibcVersionRuntime: undefined, + }), + /requires Windows ARM64/, + ); +}); + +test("rejects a non-macOS host for the macOS classifier", () => { + assert.throws( + () => + validateNativeHost("darwin-arm64", { + platform: "linux", + arch: "arm64", + glibcVersionRuntime: "2.39", + }), + /requires macOS ARM64/, + ); +}); + +test("rejects macOS x64 for the macOS ARM64 classifier", () => { + assert.throws( + () => + validateNativeHost("darwin-arm64", { + platform: "darwin", + arch: "x64", + glibcVersionRuntime: undefined, + }), + /requires macOS ARM64/, + ); +}); + +test("rejects an unimplemented classifier", () => { + assert.throws( + () => + validateNativeHost("linuxmusl-x64", { + platform: "linux", + arch: "x64", + glibcVersionRuntime: undefined, + }), + /Unsupported native build classifier/, + ); +}); diff --git a/java/copilot-native/src/main/java/.gitkeep b/java/copilot-native/src/main/java/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/java/copilot-native/src/main/javadoc/.gitkeep b/java/copilot-native/src/main/javadoc/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 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 0000000000..0f32308984 --- /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 1322e199ef..3c13d451cf 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,48 @@ 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 Maven build binds native packaging only for a host matching an implemented classifier. Linux x64 and ARM64 glibc hosts can opt in with `copilot.native.libc=glibc`, while Windows x64, Windows ARM64, and Apple Silicon macOS hosts package `win32-x64`, `win32-arm64`, or `darwin-arm64` automatically. The `inprocess` test profile selects the matching implemented classifier automatically on all five hosts. Every path validates the host before downloading or packaging native files. Linux musl and other unsupported hosts build only the OS-neutral placeholder, sources, and Javadoc artifacts unless they explicitly request in-process tests, which fail during host validation. Additional classifier artifacts remain follow-up work. + +Maven Central release and snapshot workflows build each classifier on its matching native host from the same immutable source. The Linux ARM64, Windows x64, Windows ARM64, and macOS jobs each upload only their verified classifier and checksum manifest. The Ubuntu x64 job verifies and attaches all four, builds `linux-x64` with the glibc opt-in, and performs the only Maven deployment. Release signing therefore covers the neutral artifacts and all five classifiers in one deployment. ## 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 +222,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 +232,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 +323,7 @@ With all classifier JARs declared as dependencies: copilot-sdk-java ${copilot.version} - + com.github copilot-sdk-java-runtime @@ -311,66 +336,64 @@ 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. +* A validated supported-host profile fetches the matching platform tarball from the pinned `github/copilot-cli` release, verifies its release SHA-256, and packages the version-matched runtime files. +* The current release work publishes the `linux-x64`, `linux-arm64`, `win32-x64`, `win32-arm64`, and `darwin-arm64` classifiers. The planned classifier set expands to the other detected platforms. +* Adding an implemented platform requires validated host activation, a profile that supplies the classifier and platform CLI filename, and lifecycle bindings for the shared host validation, fetch, script test, package, and verification executions. +* `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 +404,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/mvnw.cmd b/java/mvnw.cmd index 92450f9327..5761d94892 100644 --- a/java/mvnw.cmd +++ b/java/mvnw.cmd @@ -1,189 +1,189 @@ -<# : batch portion -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.3.4 -@REM -@REM Optional ENV vars -@REM MVNW_REPOURL - repo url base for downloading maven distribution -@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven -@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output -@REM ---------------------------------------------------------------------------- - -@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) -@SET __MVNW_CMD__= -@SET __MVNW_ERROR__= -@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% -@SET PSModulePath= -@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( - IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) -) -@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% -@SET __MVNW_PSMODULEP_SAVE= -@SET __MVNW_ARG0_NAME__= -@SET MVNW_USERNAME= -@SET MVNW_PASSWORD= -@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) -@echo Cannot start maven from wrapper >&2 && exit /b 1 -@GOTO :EOF -: end batch / begin powershell #> - -$ErrorActionPreference = "Stop" -if ($env:MVNW_VERBOSE -eq "true") { - $VerbosePreference = "Continue" -} - -# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties -$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl -if (!$distributionUrl) { - Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" -} - -switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { - "maven-mvnd-*" { - $USE_MVND = $true - $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" - $MVN_CMD = "mvnd.cmd" - break - } - default { - $USE_MVND = $false - $MVN_CMD = $script -replace '^mvnw','mvn' - break - } -} - -# apply MVNW_REPOURL and calculate MAVEN_HOME -# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ -if ($env:MVNW_REPOURL) { - $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } - $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" -} -$distributionUrlName = $distributionUrl -replace '^.*/','' -$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' - -$MAVEN_M2_PATH = "$HOME/.m2" -if ($env:MAVEN_USER_HOME) { - $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" -} - -if (-not (Test-Path -Path $MAVEN_M2_PATH)) { - New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null -} - -$MAVEN_WRAPPER_DISTS = $null -if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { - $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" -} else { - $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" -} - -$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" -$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' -$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" - -if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { - Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" - Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" - exit $? -} - -if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { - Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" -} - -# prepare tmp dir -$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile -$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" -$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null -trap { - if ($TMP_DOWNLOAD_DIR.Exists) { - try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } - catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } - } -} - -New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null - -# Download and Install Apache Maven -Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." -Write-Verbose "Downloading from: $distributionUrl" -Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" - -$webclient = New-Object System.Net.WebClient -if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { - $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) -} -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null - -# If specified, validate the SHA-256 sum of the Maven distribution zip file -$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum -if ($distributionSha256Sum) { - if ($USE_MVND) { - Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." - } - Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash - if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { - Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." - } -} - -# unzip and move -Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null - -# Find the actual extracted directory name (handles snapshots where filename != directory name) -$actualDistributionDir = "" - -# First try the expected directory name (for regular distributions) -$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" -$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" -if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { - $actualDistributionDir = $distributionUrlNameMain -} - -# If not found, search for any directory with the Maven executable (for snapshots) -if (!$actualDistributionDir) { - Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { - $testPath = Join-Path $_.FullName "bin/$MVN_CMD" - if (Test-Path -Path $testPath -PathType Leaf) { - $actualDistributionDir = $_.Name - } - } -} - -if (!$actualDistributionDir) { - Write-Error "Could not find Maven distribution directory in extracted archive" -} - -Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" -Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null -try { - Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null -} catch { - if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { - Write-Error "fail to move MAVEN_HOME" - } -} finally { - try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } - catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } -} - -Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.4 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' + +$MAVEN_M2_PATH = "$HOME/.m2" +if ($env:MAVEN_USER_HOME) { + $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" +} + +if (-not (Test-Path -Path $MAVEN_M2_PATH)) { + New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null +} + +$MAVEN_WRAPPER_DISTS = $null +if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { + $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" +} else { + $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" +} + +$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +$actualDistributionDir = "" + +# First try the expected directory name (for regular distributions) +$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" +$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" +if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { + $actualDistributionDir = $distributionUrlNameMain +} + +# If not found, search for any directory with the Maven executable (for snapshots) +if (!$actualDistributionDir) { + Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { + $testPath = Join-Path $_.FullName "bin/$MVN_CMD" + if (Test-Path -Path $testPath -PathType Leaf) { + $actualDistributionDir = $_.Name + } + } +} + +if (!$actualDistributionDir) { + Write-Error "Could not find Maven distribution directory in extracted archive" +} + +Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/java/pom.xml b/java/pom.xml index efb54bbef3..285cb7bb9a 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -6,12 +6,12 @@ 4.0.0 com.github - copilot-sdk-java - 1.0.10-preview.3-SNAPSHOT - jar + copilot-sdk-java-parent + 1.0.14-preview.5-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,50 +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} - - notice - - - ^1.0.78 - + ^1.0.83-3 + + true - - - - 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 - - - - - 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 @@ -146,521 +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.3.1 + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.6.0 + com.github.spotbugs spotbugs-maven-plugin - 4.10.3.0 + 4.10.4.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.8.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.1 - - - 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 - --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 - 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.11.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 @@ -668,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 @@ -693,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 @@ -707,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 9f7eb40039..785049afa1 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"; @@ -66,6 +66,24 @@ function normalizeBrandCasingNode(node: unknown): void { if (node === null || typeof node !== "object") return; const obj = node as Record; + if (obj.title === "ProviderModelConfig" && obj.properties && typeof obj.properties === "object") { + const tokenFields = new Set([ + "maxPromptTokens", + "maxContextWindowTokens", + "maxOutputTokens", + ]); + for (const [key, value] of Object.entries(obj.properties as Record)) { + if ( + tokenFields.has(key) && + value !== null && + typeof value === "object" && + (value as Record).type === "number" + ) { + (value as Record).type = "integer"; + } + } + } + for (const defsKey of ["definitions", "$defs"] as const) { const defs = obj[defsKey]; if (defs && typeof defs === "object" && !Array.isArray(defs)) { @@ -149,7 +167,9 @@ function toCamelCase(name: string): string { } function toEnumConstant(value: string): string { - return value.toUpperCase().replace(/[-. /:]/g, "_").replace(/^_+/, "").replace(/_+/g, "_"); + const constant = value.toUpperCase().replace(/[^A-Z0-9]/g, "_").replace(/^_+/, "").replace(/_+/g, "_"); + if (constant.length === 0) return "_"; + return /^[0-9]/.test(constant) ? `_${constant}` : constant; } // ── Schema path resolution ─────────────────────────────────────────────────── @@ -232,6 +252,7 @@ interface JavaTypeResult { // Set before each schema generation pass; used by schemaTypeToJava and helpers. let currentDefinitions: Record = {}; const pendingStandaloneTypes = new Map(); +const generatedSessionEventTypeNames = new Set(); // Cross-schema definitions: keyed by schema filename (e.g. "session-events.schema.json"), // value is the definitions map from that schema. Populated by generateRpcTypes so that @@ -573,21 +594,15 @@ function schemaTypeToJava( if (crossSchemaMatch) { const [, schemaFile, typeName] = crossSchemaMatch; const externalDefs = crossSchemaDefinitions.get(schemaFile); - if (externalDefs) { - const resolved = externalDefs[typeName]; - if (resolved) { - // Save and swap currentDefinitions so recursive calls resolve against - // the external schema's definitions. - const savedDefs = currentDefinitions; - currentDefinitions = externalDefs; - const result = schemaTypeToJava(resolved, required, context, propName, nestedTypes); - currentDefinitions = savedDefs; - return result; - } + if ( + schemaFile === "session-events.schema.json" + && externalDefs?.[typeName] + && generatedSessionEventTypeNames.has(typeName) + ) { + imports.add(`com.github.copilot.generated.${typeName}`); + return { javaType: typeName, imports }; } - // Fallback: extract just the type name and warn - console.warn(`[codegen] Unresolved cross-schema $ref: ${schema.$ref}`); - return { javaType: typeName, imports }; + return { javaType: "Object", imports }; } const name = schema.$ref.replace(/^#\/definitions\//, ""); @@ -779,7 +794,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); @@ -792,6 +807,13 @@ async function generateSessionEvents(schemaPath: string): Promise { // Generate standalone types discovered via $ref resolution await generatePendingStandaloneTypes(packageName, packageDir, GENERATED_FROM_SESSION_EVENTS); + generatedSessionEventTypeNames.clear(); + for (const entry of await fs.readdir(path.join(REPO_ROOT, packageDir), { withFileTypes: true })) { + if (entry.isFile() && entry.name.endsWith(".java")) { + generatedSessionEventTypeNames.add(path.basename(entry.name, ".java")); + } + } + console.log(`✅ Generated ${variants.length + 1} session event files`); } @@ -1384,7 +1406,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][] = []; @@ -2224,7 +2246,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); @@ -2347,7 +2369,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 }); @@ -2362,8 +2384,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 c4a159b6e4..44a6d9fe9d 100644 --- a/java/scripts/codegen/package-lock.json +++ b/java/scripts/codegen/package-lock.json @@ -6,9 +6,9 @@ "": { "name": "copilot-sdk-java-codegen", "dependencies": { - "@github/copilot": "^1.0.78", + "@github/copilot": "^1.0.83-3", "json-schema": "^0.4.0", - "tsx": "^4.23.1" + "tsx": "^4.23.13" } }, "node_modules/@esbuild/aix-ppc64": { @@ -428,9 +428,9 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.78", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.78.tgz", - "integrity": "sha512-jn+8HLZC3R7d6K1/1g9L1iWNKzBVS3JdVcx40r3aWyS5r+MLV1OPNp0fo5OfRMCDIm3NmEaaoqypi9sQkCXuiQ==", + "version": "1.0.83-3", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.83-3.tgz", + "integrity": "sha512-4+5wVGC2IvLYog3kdfmY6rg+NIGJesjENVrTONZr6uic6zR+8Ksgy+sCWO86n6AARs09MXktAZNHbbrXz+hl7A==", "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.78", - "@github/copilot-darwin-x64": "1.0.78", - "@github/copilot-linux-arm64": "1.0.78", - "@github/copilot-linux-x64": "1.0.78", - "@github/copilot-linuxmusl-arm64": "1.0.78", - "@github/copilot-linuxmusl-x64": "1.0.78", - "@github/copilot-win32-arm64": "1.0.78", - "@github/copilot-win32-x64": "1.0.78" + "@github/copilot-darwin-arm64": "1.0.83-3", + "@github/copilot-darwin-x64": "1.0.83-3", + "@github/copilot-linux-arm64": "1.0.83-3", + "@github/copilot-linux-x64": "1.0.83-3", + "@github/copilot-linuxmusl-arm64": "1.0.83-3", + "@github/copilot-linuxmusl-x64": "1.0.83-3", + "@github/copilot-win32-arm64": "1.0.83-3", + "@github/copilot-win32-x64": "1.0.83-3" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.78", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.78.tgz", - "integrity": "sha512-P11+VyWg8ad0WlywGtO2d7AxqTLJv4hkUicFg6Ycth5lfk00aCu/74YOOZSPO6C2bBBJhAza7oAdmauM6KEojw==", + "version": "1.0.83-3", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.83-3.tgz", + "integrity": "sha512-pNI71CRL2WR6Wp+Nm+HOsSBcUIOoybcSZtMHqm2zwJGdzAjzv6MU2lLOFFeqhBh8UNQGltD4KtPU/pr+t6t4Uw==", "cpu": [ "arm64" ], @@ -466,9 +466,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.78", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.78.tgz", - "integrity": "sha512-stimP3WDFs2GU8nJzTJbtRpZViV4bsf80yg7QrFq+G4RISQ3Nihg/3/H0U6UQF1+txMJ/Ohmb5RFYxSw1Hj2sw==", + "version": "1.0.83-3", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.83-3.tgz", + "integrity": "sha512-9LKUwR7em12mz76s2ytWl/xkHyF13t0TLScAUcnNNj171/Kvg0lWNemwsmPK4m0QbbcmRUs7FyFFF79TmKBAmA==", "cpu": [ "x64" ], @@ -482,9 +482,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.78", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.78.tgz", - "integrity": "sha512-K31PRKGTm252V1Lof7ypjg283R2QSm3BgoCvZfX2taos4wqC3SaTozSQKwW3dgrAx7A3G3SGEoilVCNqfigdZA==", + "version": "1.0.83-3", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.83-3.tgz", + "integrity": "sha512-ouGA46t6flyUqUdutQL+94bnD+IwcCurR+5KS2JPHozbkeiR2BW4ed0ZZ5KT/6I13mTsjO9uu9LvWwfO5+PjiQ==", "cpu": [ "arm64" ], @@ -498,9 +498,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.78", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.78.tgz", - "integrity": "sha512-QK3oMtAn9dIv+1u1kx0xNpZNtZxdI+uZVIyLl7myp+Oh2Uj8BLagVv6a7uP0cDphO3TgfIdlvpepCe5MIcx0fw==", + "version": "1.0.83-3", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.83-3.tgz", + "integrity": "sha512-AiAf2yVrnP+Dw0M8RpacpOoK89sMFizPMuQfFPxAJUWS9hIw5mq4o4invKtUfiz0F7cjxaDJZz1JLUSuGEAQhw==", "cpu": [ "x64" ], @@ -514,9 +514,9 @@ } }, "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.78", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.78.tgz", - "integrity": "sha512-F/0cTMsz6ug4yiXn3RKaCAMsLR261U5Njb6G9Y/HeAI7ES/tKEo2t5SHuvgXaIH4mYiZsRvfDKdX7c0WgBX/Jg==", + "version": "1.0.83-3", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.83-3.tgz", + "integrity": "sha512-TmXPXi65OX/Wfd7JnU8RZjZxzc5kFZU/3Gvr/N1Y+G+cJJyB0NBmWk2PP+yD381ASYOOgeNgWitlYMw8tU7Ddg==", "cpu": [ "arm64" ], @@ -530,9 +530,9 @@ } }, "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.78", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.78.tgz", - "integrity": "sha512-YMaJaeBGbArGAFYel+yFaFW/0rFgh0Oqki2f2mUtlonTX/xHr8EB4+mTnMJkHYMFy4gOTC3OtSEEe1NaW/cBXQ==", + "version": "1.0.83-3", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.83-3.tgz", + "integrity": "sha512-Zlbya4anjkbI8LcbenwuBhxUUeVIrGJqeYh/6JUWwnisOiuuimqQ4zb2UU2pX3vxE03f2PbTcueOo/GkF6AS8A==", "cpu": [ "x64" ], @@ -546,9 +546,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.78", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.78.tgz", - "integrity": "sha512-ktDkFXaaecEKD3hpM6ydM9lKOdoCfsQsXCmzLzE7DCmSpbbMCdfPfWfZ7MOclmKmpZ5/MNfr4U2l8CUqGerzYA==", + "version": "1.0.83-3", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.83-3.tgz", + "integrity": "sha512-zNmVj3ZDmI3dFmBigfEMzEvMxyjBjL5+nTVxrt9fvTA+29jI0C6A+cdCqrad3fJ1RKgn2RbsZyhnpyViPNhNDw==", "cpu": [ "arm64" ], @@ -562,9 +562,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.78", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.78.tgz", - "integrity": "sha512-Gd8l2T4eqYEWlOEPd0SZznQ+YYgYrwOkE0QXodMkhCBbPdgu/uTzb7mnISWwnVAgqs7pONdF1GOpHkTo+ay8CQ==", + "version": "1.0.83-3", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.83-3.tgz", + "integrity": "sha512-pbw739Jdwjr4ovsjwpMI1hguZyOPwTy/fdVnrgBv1nazXxIFrwE3tq0FgzF0NnNcs4r5LXdbIBjKQP+HKFZagA==", "cpu": [ "x64" ], @@ -648,9 +648,9 @@ "license": "(AFL-2.1 OR BSD-3-Clause)" }, "node_modules/tsx": { - "version": "4.23.1", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.1.tgz", - "integrity": "sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==", + "version": "4.23.13", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.13.tgz", + "integrity": "sha512-BL5MGkRln6aDYhb0xbQlEAGw743BaZYWdbWtdJOBriYJboKgUUYCadFp2/FpBBZquBC/ezNBn7wMMPx7FDZUDw==", "license": "MIT", "dependencies": { "esbuild": "~0.28.0" diff --git a/java/scripts/codegen/package.json b/java/scripts/codegen/package.json index 7547468e84..a9f84732a2 100644 --- a/java/scripts/codegen/package.json +++ b/java/scripts/codegen/package.json @@ -7,8 +7,8 @@ "generate:java": "tsx java.ts" }, "dependencies": { - "@github/copilot": "^1.0.78", + "@github/copilot": "^1.0.83-3", "json-schema": "^0.4.0", - "tsx": "^4.23.1" + "tsx": "^4.23.13" } } diff --git a/java/scripts/test-update-documentation-versions.sh b/java/scripts/test-update-documentation-versions.sh index 213bda6c43..606e2f7f80 100755 --- a/java/scripts/test-update-documentation-versions.sh +++ b/java/scripts/test-update-documentation-versions.sh @@ -28,6 +28,10 @@ run_case() { " ${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 \$?" \ @@ -40,6 +44,7 @@ run_case() { 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" || diff --git a/java/scripts/update-documentation-versions.sh b/java/scripts/update-documentation-versions.sh index d2d23c4ff2..5280f55d55 100755 --- a/java/scripts/update-documentation-versions.sh +++ b/java/scripts/update-documentation-versions.sh @@ -39,9 +39,10 @@ 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 $snapshot_xml = ($content =~ s{$version-SNAPSHOT}{$ENV{DEV_VERSION}}g); +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{$version}{$ENV{VERSION}}g); +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" 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 9abc39eddd..a223dd8d20 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.9-preview.3 +//DEPS com.github:copilot-sdk-java:1.0.13-preview.5 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 0000000000..aa40b25189 --- /dev/null +++ b/java/sdk/pom.xml @@ -0,0 +1,874 @@ + + + + 4.0.0 + + + com.github + copilot-sdk-java-parent + 1.0.14-preview.5-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 + + + + false + + ${skip.test.harness} + + notice + + + + false + + 5.19.1 + + + + + + com.fasterxml.jackson.core + jackson-databind + 2.22.2 + + + com.fasterxml.jackson.core + jackson-annotations + 2.22 + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.22.2 + + + + + com.github.spotbugs + spotbugs-annotations + 4.10.4 + 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 + linux-x64 + + + + + + + com.github + copilot-sdk-java-runtime + ${project.version} + ${copilot.native.classifier} + test + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + false + 1 + none + + + **/AskUserTest.java + **/CompactionTest.java + + **/CopilotClientTest.java + **/CopilotSessionTest.java + **/ErrorHandlingTest.java + **/EventFidelityTest.java + **/ExecutorWiringTest.java + **/HooksTest.java + **/McpAndAgentsTest.java + **/McpOAuthResumeE2ETest.java + **/PermissionsTest.java + **/PreMcpToolCallHookTest.java + **/RpcServerE2ETest.java + **/RpcSessionStateExtrasE2ETest.java + **/SessionConfigE2ETest.java + **/SessionEventsE2ETest.java + **/SessionTodosChangedTest.java + **/SkillsTest.java + **/StreamingFidelityTest.java + **/SubagentHooksE2ETest.java + **/ToolResultsTest.java + **/ToolsTest.java + + + ${copilot.inprocess.cli.path} + inprocess + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + 1 + none + + + **/ErgonomicToolDefinitionIT.java + **/GitHubTelemetryForwardingIT.java + **/LowLevelToolDefinitionIT.java + **/RewindIT.java + **/SystemMessageSectionsIT.java + + + ${copilot.inprocess.cli.path} + inprocess + + + + + + + + native-linux-arm64 + + + Linux + aarch64 + + + + linux-arm64 + + + + native-win32-x64 + + + Windows + amd64 + + + + win32-x64 + + + + native-win32-arm64 + + + Windows + aarch64 + + + + win32-arm64 + + + + native-darwin-arm64 + + + mac + aarch64 + + + + darwin-arm64 + + + + + 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 100% 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 diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/AgentInterruptedActivity.java b/java/sdk/src/generated/java/com/github/copilot/generated/AgentInterruptedActivity.java new file mode 100644 index 0000000000..0cca731a6e --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AgentInterruptedActivity.java @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * 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 the agent was doing when the user interrupted it. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum AgentInterruptedActivity { + /** The {@code model_call} variant. */ + MODEL_CALL("model_call"), + /** The {@code retry_backoff} variant. */ + RETRY_BACKOFF("retry_backoff"), + /** The {@code tool_call} variant. */ + TOOL_CALL("tool_call"), + /** The {@code background_agent} variant. */ + BACKGROUND_AGENT("background_agent"); + + private final String value; + AgentInterruptedActivity(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static AgentInterruptedActivity fromValue(String value) { + for (AgentInterruptedActivity v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown AgentInterruptedActivity value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/AgentInterruptedCancelPhase.java b/java/sdk/src/generated/java/com/github/copilot/generated/AgentInterruptedCancelPhase.java new file mode 100644 index 0000000000..41a8bd11b8 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AgentInterruptedCancelPhase.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; + +/** + * Where the interruption landed relative to the first streamed token. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum AgentInterruptedCancelPhase { + /** The {@code pre_first_token} variant. */ + PRE_FIRST_TOKEN("pre_first_token"), + /** The {@code mid_stream} variant. */ + MID_STREAM("mid_stream"); + + private final String value; + AgentInterruptedCancelPhase(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static AgentInterruptedCancelPhase fromValue(String value) { + for (AgentInterruptedCancelPhase v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown AgentInterruptedCancelPhase value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/AgentInterruptedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/AgentInterruptedEvent.java new file mode 100644 index 0000000000..ef6772fca2 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AgentInterruptedEvent.java @@ -0,0 +1,66 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * Session event "agent.interrupted". Metadata for work the user interrupted while the agent was running + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class AgentInterruptedEvent extends SessionEvent { + + @Override + public String getType() { return "agent.interrupted"; } + + @JsonProperty("data") + private AgentInterruptedEventData data; + + public AgentInterruptedEventData getData() { return data; } + public void setData(AgentInterruptedEventData data) { this.data = data; } + + /** Data payload for {@link AgentInterruptedEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record AgentInterruptedEventData( + /** What the agent was doing when the user interrupted it */ + @JsonProperty("activity") AgentInterruptedActivity activity, + /** How long the interrupted work had been running, in milliseconds */ + @JsonProperty("elapsedMs") Double elapsedMs, + /** Zero-based agentic-loop iteration the interrupt landed in */ + @JsonProperty("turn") Long turn, + /** For an interrupted model call: the model the request targeted */ + @JsonProperty("model") String model, + /** For an interrupted model call: the provider endpoint the request targeted */ + @JsonProperty("apiEndpoint") String apiEndpoint, + /** For an interrupted model call: the transport the request used */ + @JsonProperty("transport") ModelCallFailureTransport transport, + /** For an interrupted model call: the reasoning effort the request asked for */ + @JsonProperty("reasoningEffort") String reasoningEffort, + /** For an interrupted model call: whether the user interrupted before any token arrived or while the response was streaming */ + @JsonProperty("cancelPhase") AgentInterruptedCancelPhase cancelPhase, + /** For a mid-stream interrupt: the observed time to first observable output, in milliseconds. Deliberately distinct from the `ttftMs` reported on a successful model call, which measures time to first stream event. */ + @JsonProperty("outputTtftMs") Double outputTtftMs, + /** Names of the tools that were still running. More than one when the model requested a parallel fan-out. */ + @JsonProperty("toolNames") List toolNames, + /** Tool call identifiers that were still running */ + @JsonProperty("toolCallIds") List toolCallIds, + /** Subset of `toolNames` whose tool metadata marks the tool name as safe to record unhashed in telemetry. */ + @JsonProperty("safeToolNames") List safeToolNames, + /** For an interrupted background-agent batch: how many background sub-agents the stop swept. Counts accepted cancellations, so an agent cancelled as a cascade of its interrupted parent is covered by that parent rather than counted again. */ + @JsonProperty("interruptedAgentCount") Long interruptedAgentCount + ) { + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/AgentModelPolicy.java b/java/sdk/src/generated/java/com/github/copilot/generated/AgentModelPolicy.java new file mode 100644 index 0000000000..06c80ba22e --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AgentModelPolicy.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; + +/** + * Whether configured models are advisory preferences or required constraints + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum AgentModelPolicy { + /** The {@code preferred} variant. */ + PREFERRED("preferred"), + /** The {@code required} variant. */ + REQUIRED("required"); + + private final String value; + AgentModelPolicy(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static AgentModelPolicy fromValue(String value) { + for (AgentModelPolicy v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown AgentModelPolicy value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/AssistantFusionPhaseActivityEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantFusionPhaseActivityEvent.java new file mode 100644 index 0000000000..64d8d58283 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantFusionPhaseActivityEvent.java @@ -0,0 +1,57 @@ +/*--------------------------------------------------------------------------------------------- + * 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 "assistant.fusion_phase_activity". Experimental content-safe activity signal for a running HydraFusion phase. + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class AssistantFusionPhaseActivityEvent extends SessionEvent { + + @Override + public String getType() { return "assistant.fusion_phase_activity"; } + + @JsonProperty("data") + private AssistantFusionPhaseActivityEventData data; + + public AssistantFusionPhaseActivityEventData getData() { return data; } + public void setData(AssistantFusionPhaseActivityEventData data) { this.data = data; } + + /** Data payload for {@link AssistantFusionPhaseActivityEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record AssistantFusionPhaseActivityEventData( + /** Identifier of the HydraFusion turn containing the phase. */ + @JsonProperty("fusionId") String fusionId, + /** Stable identifier for the concrete phase. */ + @JsonProperty("phaseId") String phaseId, + /** Kind of phase currently executing. */ + @JsonProperty("phaseKind") FusionPhaseKind phaseKind, + /** HydraFusion orchestration pattern containing the phase. */ + @JsonProperty("pattern") FusionPattern pattern, + /** Semantic role assigned to the phase. */ + @JsonProperty("role") String role, + /** Conversation scope in which the phase executes. */ + @JsonProperty("conversationScope") FusionConversationScope conversationScope, + /** Kind of real activity observed. */ + @JsonProperty("activity") FusionPhaseActivityKind activity, + /** Cumulative private response bytes observed for this model call. The event never includes response text. */ + @JsonProperty("totalResponseSizeBytes") Long totalResponseSizeBytes, + /** Opaque hashed correlation token for matching tool-started and tool-completed activity within this Fusion activity stream. It is not the tool call identifier exposed by tool lifecycle events. */ + @JsonProperty("toolCallId") String toolCallId + ) { + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/AssistantFusionPhaseCompletedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantFusionPhaseCompletedEvent.java new file mode 100644 index 0000000000..5ad9beee17 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantFusionPhaseCompletedEvent.java @@ -0,0 +1,67 @@ +/*--------------------------------------------------------------------------------------------- + * 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 "assistant.fusion_phase_completed". Experimental durable HydraFusion phase output and lossless replay checkpoint. + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class AssistantFusionPhaseCompletedEvent extends SessionEvent { + + @Override + public String getType() { return "assistant.fusion_phase_completed"; } + + @JsonProperty("data") + private AssistantFusionPhaseCompletedEventData data; + + public AssistantFusionPhaseCompletedEventData getData() { return data; } + public void setData(AssistantFusionPhaseCompletedEventData data) { this.data = data; } + + /** Data payload for {@link AssistantFusionPhaseCompletedEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record AssistantFusionPhaseCompletedEventData( + /** Identifier of the HydraFusion turn containing the phase. */ + @JsonProperty("fusionId") String fusionId, + /** Stable identifier for the completed phase. */ + @JsonProperty("phaseId") String phaseId, + /** Kind of phase that completed. */ + @JsonProperty("phaseKind") FusionPhaseKind phaseKind, + /** Semantic role assigned to the completed phase. */ + @JsonProperty("role") String role, + /** Conversation scope in which the phase executed. */ + @JsonProperty("conversationScope") FusionConversationScope conversationScope, + /** Concrete model that executed the phase. */ + @JsonProperty("model") String model, + /** Durable outcome status of the phase. */ + @JsonProperty("status") FusionPhaseStatus status, + /** Provider-normalized textual output produced by the phase. */ + @JsonProperty("content") String content, + /** Structured judge or critic verdict, when the phase produces one. */ + @JsonProperty("verdict") String verdict, + /** Elapsed execution time for the phase in milliseconds. */ + @JsonProperty("durationMs") Double durationMs, + /** Aggregate concrete-model usage consumed by the phase. */ + @JsonProperty("usage") FusionPhaseUsage usage, + /** Exact provider-normalized message used to reconstruct canonical model history. */ + @JsonProperty("projectionMessage") Object projectionMessage, + /** Projection action for the exact internal message. */ + @JsonProperty("projectionMode") FusionProjectionMode projectionMode, + /** Terminal request held outside canonical state until selected by the final commit. */ + @JsonProperty("stagedTerminal") FusionStagedTerminal stagedTerminal + ) { + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/AssistantFusionPhaseFailedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantFusionPhaseFailedEvent.java new file mode 100644 index 0000000000..17de3c7190 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantFusionPhaseFailedEvent.java @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------------------------- + * 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 "assistant.fusion_phase_failed". Experimental durable typed HydraFusion phase failure and degradation transition. + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class AssistantFusionPhaseFailedEvent extends SessionEvent { + + @Override + public String getType() { return "assistant.fusion_phase_failed"; } + + @JsonProperty("data") + private AssistantFusionPhaseFailedEventData data; + + public AssistantFusionPhaseFailedEventData getData() { return data; } + public void setData(AssistantFusionPhaseFailedEventData data) { this.data = data; } + + /** Data payload for {@link AssistantFusionPhaseFailedEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record AssistantFusionPhaseFailedEventData( + /** Identifier of the HydraFusion turn containing the phase. */ + @JsonProperty("fusionId") String fusionId, + /** Stable identifier for the failed phase. */ + @JsonProperty("phaseId") String phaseId, + /** Kind of phase that failed. */ + @JsonProperty("phaseKind") FusionPhaseKind phaseKind, + /** Semantic role assigned to the failed phase. */ + @JsonProperty("role") String role, + /** Conversation scope in which the phase executed. */ + @JsonProperty("conversationScope") FusionConversationScope conversationScope, + /** Concrete model that attempted the phase. */ + @JsonProperty("model") String model, + /** Durable outcome status of the phase. */ + @JsonProperty("status") FusionPhaseStatus status, + /** Stable machine-readable reason for the phase failure. */ + @JsonProperty("reason") String reason, + /** Elapsed execution time before the phase failed, in milliseconds. */ + @JsonProperty("durationMs") Double durationMs, + /** Aggregate concrete-model usage consumed before the failure. */ + @JsonProperty("usage") FusionPhaseUsage usage, + /** Provider or execution error detail, when available. */ + @JsonProperty("errorMessage") String errorMessage, + /** Identifier of the fallback phase used to continue the turn after degradation. */ + @JsonProperty("degradedToPhaseId") String degradedToPhaseId + ) { + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/AssistantFusionPhaseStartedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantFusionPhaseStartedEvent.java new file mode 100644 index 0000000000..88c4064a73 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantFusionPhaseStartedEvent.java @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------------------------- + * 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 "assistant.fusion_phase_started". Experimental transient HydraFusion phase/model/role signal. + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class AssistantFusionPhaseStartedEvent extends SessionEvent { + + @Override + public String getType() { return "assistant.fusion_phase_started"; } + + @JsonProperty("data") + private AssistantFusionPhaseStartedEventData data; + + public AssistantFusionPhaseStartedEventData getData() { return data; } + public void setData(AssistantFusionPhaseStartedEventData data) { this.data = data; } + + /** Data payload for {@link AssistantFusionPhaseStartedEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record AssistantFusionPhaseStartedEventData( + /** Identifier of the HydraFusion turn containing the phase. */ + @JsonProperty("fusionId") String fusionId, + /** Stable identifier for the concrete phase. */ + @JsonProperty("phaseId") String phaseId, + /** Kind of phase being executed. */ + @JsonProperty("phaseKind") FusionPhaseKind phaseKind, + /** HydraFusion orchestration pattern containing the phase. */ + @JsonProperty("pattern") FusionPattern pattern, + /** Semantic role assigned to the phase. */ + @JsonProperty("role") String role, + /** Conversation scope in which the phase executes. */ + @JsonProperty("conversationScope") FusionConversationScope conversationScope, + /** Concrete model executing the phase. */ + @JsonProperty("model") String model + ) { + } +} 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 87% 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 fee236ed2c..9ba4f05618 100644 --- a/java/src/generated/java/com/github/copilot/generated/AssistantMessageEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageEvent.java @@ -67,17 +67,22 @@ 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, + /** Per-request treatment/eligibility signal returned by the Copilot API in the `X-GitHub-Copilot-Request-TE` response header for the associated model call; `false` when the header was absent or unparseable. */ @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 */ @JsonProperty("serverTools") AssistantMessageServerTools serverTools, + /** Neutral provider-tagged reasoning content blocks preserved verbatim for round-tripping. `reasoningText` and `reasoningOpaque` are a lossy derived view of these blocks, retained for display. */ + @JsonProperty("reasoningBlocks") AssistantMessageReasoningBlocks reasoningBlocks, /** Identifier for the agent loop turn that produced this message, matching the corresponding assistant.turn_start event */ @JsonProperty("turnId") String turnId, /** Tool call ID of the parent tool invocation when this event originates from a sub-agent */ @JsonProperty("parentToolCallId") String parentToolCallId, /** Provider-agnostic citations linking spans of this message's content to the sources that support them. Experimental; only populated when citation emission is enabled. */ - @JsonProperty("citations") Citations citations + @JsonProperty("citations") Citations citations, + /** Experimental HydraFusion source attribution for this ordinary authoritative assistant message. */ + @JsonProperty("fusion") FusionAttribution fusion ) { } } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageReasoningBlocks.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageReasoningBlocks.java new file mode 100644 index 0000000000..ee9c5fd485 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageReasoningBlocks.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * Neutral provider-tagged reasoning content blocks preserved verbatim for round-tripping + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record AssistantMessageReasoningBlocks( + /** Model provider that produced these reasoning blocks. */ + @JsonProperty("provider") String provider, + /** Provider-native reasoning items or content blocks preserved verbatim, in order. A single response can carry several, and provider signatures or identifiers may depend on their exact content and ordering. */ + @JsonProperty("blocks") List blocks +) { +} 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 76% 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 index 72d6850376..6d67f6ecbe 100644 --- a/java/src/generated/java/com/github/copilot/generated/AssistantMessageServerTools.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageServerTools.java @@ -23,10 +23,15 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record AssistantMessageServerTools( + /** Model provider that produced this server-tool payload. */ @JsonProperty("provider") String provider, + /** Provider-native server-tool call and output items preserved verbatim for replay. */ @JsonProperty("items") List items, + /** Provider function-call namespaces keyed by function-call identifier. */ @JsonProperty("functionCallNamespaces") Map functionCallNamespaces, + /** Raw provider content blocks retained for verbatim round-tripping. */ @JsonProperty("rawContentBlocks") List rawContentBlocks, + /** Advisor model identifier associated with the server-tool payload. */ @JsonProperty("advisorModel") String advisorModel ) { } 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 90% 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 index 2013734012..bcc7c8206d 100644 --- a/java/src/generated/java/com/github/copilot/generated/AssistantMessageToolRequest.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageToolRequest.java @@ -36,6 +36,8 @@ public record AssistantMessageToolRequest( /** Original tool name on the MCP server, when the tool is an MCP tool */ @JsonProperty("mcpToolName") String mcpToolName, /** Resolved intention summary describing what this specific call does */ - @JsonProperty("intentionSummary") String intentionSummary + @JsonProperty("intentionSummary") String intentionSummary, + /** Hosted program that requested this client tool call */ + @JsonProperty("caller") AssistantMessageToolRequestCaller caller ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageToolRequestCaller.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageToolRequestCaller.java new file mode 100644 index 0000000000..cb3d05a649 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageToolRequestCaller.java @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * Hosted program that requested this client tool call + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record AssistantMessageToolRequestCaller( + /** Kind of hosted caller that requested the client tool call. */ + @JsonProperty("type") AssistantMessageToolRequestCallerType type, + /** Provider-assigned identifier for the hosted caller. */ + @JsonProperty("callerId") String callerId +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/PermissionAllowAllMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageToolRequestCallerType.java similarity index 60% rename from java/src/generated/java/com/github/copilot/generated/PermissionAllowAllMode.java rename to java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageToolRequestCallerType.java index d05b936e6a..05be5068d9 100644 --- a/java/src/generated/java/com/github/copilot/generated/PermissionAllowAllMode.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageToolRequestCallerType.java @@ -10,28 +10,24 @@ import javax.annotation.processing.Generated; /** - * Allow-all mode for the session. + * Hosted program caller type * * @since 1.0.0 */ @javax.annotation.processing.Generated("copilot-sdk-codegen") -public enum PermissionAllowAllMode { - /** The {@code off} variant. */ - OFF("off"), - /** The {@code on} variant. */ - ON("on"), - /** The {@code auto} variant. */ - AUTO("auto"); +public enum AssistantMessageToolRequestCallerType { + /** The {@code program} variant. */ + PROGRAM("program"); private final String value; - PermissionAllowAllMode(String value) { this.value = value; } + AssistantMessageToolRequestCallerType(String value) { this.value = value; } @com.fasterxml.jackson.annotation.JsonValue public String getValue() { return value; } @com.fasterxml.jackson.annotation.JsonCreator - public static PermissionAllowAllMode fromValue(String value) { - for (PermissionAllowAllMode v : values()) { + public static AssistantMessageToolRequestCallerType fromValue(String value) { + for (AssistantMessageToolRequestCallerType v : values()) { if (v.value.equals(value)) return v; } - throw new IllegalArgumentException("Unknown PermissionAllowAllMode value: " + value); + throw new IllegalArgumentException("Unknown AssistantMessageToolRequestCallerType value: " + value); } } 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 88% 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 52996aeeed..627dea8ef5 100644 --- a/java/src/generated/java/com/github/copilot/generated/AssistantReasoningEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantReasoningEvent.java @@ -38,6 +38,7 @@ public record AssistantReasoningEventData( @JsonProperty("reasoningId") String reasoningId, /** The complete extended thinking text from the model */ @JsonProperty("content") String content, + /** Per-request treatment/eligibility signal returned by the Copilot API in the `X-GitHub-Copilot-Request-TE` response header for the associated model call; `false` when the header was absent or unparseable. */ @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 73% 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 85bd81d3c9..d0956f5a77 100644 --- a/java/src/generated/java/com/github/copilot/generated/AssistantUsageEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantUsageEvent.java @@ -56,18 +56,35 @@ public record AssistantUsageEventData( @JsonProperty("duration") Long duration, /** Time to first token in milliseconds. Only available for streaming requests */ @JsonProperty("timeToFirstTokenMs") Double timeToFirstTokenMs, + /** Time to first observable model output in milliseconds. Includes text, reasoning, and tool-call output; only available for streaming requests that produce observable output. */ + @JsonProperty("outputTtftMs") Double outputTtftMs, /** Average inter-token latency in milliseconds. Only available for streaming requests */ @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, + /** Whether this model call used a bring-your-own-key provider */ + @JsonProperty("isByok") Boolean isByok, + /** Whether Auto mode was selected for this model call */ + @JsonProperty("isAuto") Boolean isAuto, + /** Effective maximum prompt-token limit used for this model call */ + @JsonProperty("maxPromptTokens") Long maxPromptTokens, + /** Requested maximum output tokens used for this model call */ + @JsonProperty("maxOutputTokens") Long maxOutputTokens, + /** Number of accepted speculative prediction tokens */ + @JsonProperty("acceptedPredictionTokens") Long acceptedPredictionTokens, + /** Number of rejected speculative prediction tokens */ + @JsonProperty("rejectedPredictionTokens") Long rejectedPredictionTokens, + /** Transport used for this model call (http or websocket) */ + @JsonProperty("transport") AssistantUsageTransport transport, /** 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, + /** Per-request treatment/eligibility signal returned by the Copilot API in the `X-GitHub-Copilot-Request-TE` response header for the associated model call; `false` when the header was absent or unparseable. */ @JsonProperty("rte") Boolean rte, /** API endpoint used for this model call, matching CAPI supported_endpoints vocabulary */ @JsonProperty("apiEndpoint") AssistantUsageApiEndpoint apiEndpoint, @@ -79,10 +96,18 @@ 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, + /** Reasoning summary mode used for this model call, if applicable */ + @JsonProperty("reasoningSummary") ReasoningSummary reasoningSummary, /** 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, + /** How the prompt-cache frontier was determined for this call */ + @JsonProperty("frontierSource") String frontierSource, + /** Effective prompt-cache lifetime in seconds for this call */ + @JsonProperty("cacheTtlSeconds") Long cacheTtlSeconds, + /** Whether the provider reported prompt-cache usage details for this call */ + @JsonProperty("cacheDetailsReported") Boolean cacheDetailsReported, /** Number of tool calls returned by the model */ @JsonProperty("numToolCalls") Long numToolCalls, /** Tool-call counts keyed by tool name */ @@ -90,7 +115,9 @@ public record AssistantUsageEventData( /** 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. */ - @JsonProperty("contentFilterTriggered") Boolean contentFilterTriggered + @JsonProperty("contentFilterTriggered") Boolean contentFilterTriggered, + /** Experimental HydraFusion attribution for this concrete model call's usage. */ + @JsonProperty("fusion") FusionAttribution fusion ) { } } 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/sdk/src/generated/java/com/github/copilot/generated/AssistantUsageTransport.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantUsageTransport.java new file mode 100644 index 0000000000..9d69b50f0d --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantUsageTransport.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; + +/** + * Transport used for a successful model call + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum AssistantUsageTransport { + /** The {@code http} variant. */ + HTTP("http"), + /** The {@code websocket} variant. */ + WEBSOCKET("websocket"); + + private final String value; + AssistantUsageTransport(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static AssistantUsageTransport fromValue(String value) { + for (AssistantUsageTransport v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown AssistantUsageTransport value: " + value); + } +} 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/sdk/src/generated/java/com/github/copilot/generated/AutoTier.java b/java/sdk/src/generated/java/com/github/copilot/generated/AutoTier.java new file mode 100644 index 0000000000..254543160a --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AutoTier.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; + +/** + * Routing preference used when the session model is `auto`. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum AutoTier { + /** The {@code efficiency} variant. */ + EFFICIENCY("efficiency"), + /** The {@code balance} variant. */ + BALANCE("balance"), + /** The {@code intelligence} variant. */ + INTELLIGENCE("intelligence"); + + private final String value; + AutoTier(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static AutoTier fromValue(String value) { + for (AutoTier v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown AutoTier value: " + value); + } +} 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/src/generated/java/com/github/copilot/generated/CompactionTrigger.java b/java/sdk/src/generated/java/com/github/copilot/generated/CompactionTrigger.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/CompactionTrigger.java rename to java/sdk/src/generated/java/com/github/copilot/generated/CompactionTrigger.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/CompletionReceiptEventRange.java b/java/sdk/src/generated/java/com/github/copilot/generated/CompletionReceiptEventRange.java new file mode 100644 index 0000000000..7b05d1c9cd --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/CompletionReceiptEventRange.java @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * Inclusive durable event range summarized by a completion receipt. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record CompletionReceiptEventRange( + /** Identifier of the user message that starts the covered exchange. */ + @JsonProperty("startEventId") String startEventId, + /** Identifier of the assistant turn-end event that ends the covered exchange. Always equals the receipt's sourceEventId, so either field is a valid join key. */ + @JsonProperty("endEventId") String endEventId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/CompletionReceiptFinalTool.java b/java/sdk/src/generated/java/com/github/copilot/generated/CompletionReceiptFinalTool.java new file mode 100644 index 0000000000..74d78d7ee1 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/CompletionReceiptFinalTool.java @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * Final structured tool completion in the covered event range. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record CompletionReceiptFinalTool( + /** Unique identifier of the completed tool call. */ + @JsonProperty("toolCallId") String toolCallId, + /** Tool name from the matching tool execution start event, when available. */ + @JsonProperty("toolName") String toolName, + /** Structured success or failure status from the tool completion event. */ + @JsonProperty("status") CompletionReceiptToolStatus status, + /** Process exit code from a structured shell result, when available. */ + @JsonProperty("exitCode") Long exitCode +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/CompletionReceiptStopReason.java b/java/sdk/src/generated/java/com/github/copilot/generated/CompletionReceiptStopReason.java new file mode 100644 index 0000000000..49874b0275 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/CompletionReceiptStopReason.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; + +/** + * Runtime reason the completion decision was accepted. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum CompletionReceiptStopReason { + /** The {@code natural} variant. */ + NATURAL("natural"), + /** The {@code terminal_tool} variant. */ + TERMINAL_TOOL("terminal_tool"), + /** The {@code agent_stop_block_limit} variant. */ + AGENT_STOP_BLOCK_LIMIT("agent_stop_block_limit"); + + private final String value; + CompletionReceiptStopReason(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static CompletionReceiptStopReason fromValue(String value) { + for (CompletionReceiptStopReason v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown CompletionReceiptStopReason value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/CompletionReceiptToolStatus.java b/java/sdk/src/generated/java/com/github/copilot/generated/CompletionReceiptToolStatus.java new file mode 100644 index 0000000000..e82a1e6986 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/CompletionReceiptToolStatus.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; + +/** + * Structured terminal status from a tool completion event. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum CompletionReceiptToolStatus { + /** The {@code success} variant. */ + SUCCESS("success"), + /** The {@code failure} variant. */ + FAILURE("failure"), + /** The {@code timeout} variant. */ + TIMEOUT("timeout"), + /** The {@code rejected} variant. */ + REJECTED("rejected"), + /** The {@code denied} variant. */ + DENIED("denied"); + + private final String value; + CompletionReceiptToolStatus(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static CompletionReceiptToolStatus fromValue(String value) { + for (CompletionReceiptToolStatus v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown CompletionReceiptToolStatus 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 81% 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 index c2f195e486..762f0b1ac8 100644 --- a/java/src/generated/java/com/github/copilot/generated/CustomAgentsUpdatedAgent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/CustomAgentsUpdatedAgent.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * A single loaded custom agent in `session.custom_agents_updated`, with identity, source, tools, invocability, and model override. + * A single loaded custom agent in `session.custom_agents_updated`, with identity, source, tools, invocability, and authored model configuration. * * @since 1.0.0 */ @@ -37,6 +37,10 @@ public record CustomAgentsUpdatedAgent( /** Whether the agent can be selected by the user */ @JsonProperty("userInvocable") Boolean userInvocable, /** Model override for this agent, if set */ - @JsonProperty("model") String model + @JsonProperty("model") String model, + /** Authored model ids in priority order, if configured */ + @JsonProperty("models") List models, + /** Whether authored models are preferences or required constraints */ + @JsonProperty("modelPolicy") AgentModelPolicy modelPolicy ) { } 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 93% 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 index 4242b4b656..7f20d35bf2 100644 --- a/java/src/generated/java/com/github/copilot/generated/ExitPlanModeRequestedEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/ExitPlanModeRequestedEvent.java @@ -44,7 +44,9 @@ public record ExitPlanModeRequestedEventData( /** Available actions the user can take */ @JsonProperty("actions") List actions, /** Recommended action to preselect for the user */ - @JsonProperty("recommendedAction") ExitPlanModeAction recommendedAction + @JsonProperty("recommendedAction") ExitPlanModeAction recommendedAction, + /** Model the session had selected when the plan was authored, when one is known */ + @JsonProperty("model") String model ) { } } 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 91% 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 index 903f01f1e8..e2d5bda5a9 100644 --- a/java/src/generated/java/com/github/copilot/generated/ExternalToolRequestedEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/ExternalToolRequestedEvent.java @@ -42,6 +42,8 @@ public record ExternalToolRequestedEventData( @JsonProperty("toolCallId") String toolCallId, /** Name of the external tool to invoke */ @JsonProperty("toolName") String toolName, + /** Stable provider identity captured with an extension-owned tool definition; hosts use it to route the request to the same provider that was offered to the model */ + @JsonProperty("providerId") String providerId, /** Arguments to pass to the external tool */ @JsonProperty("arguments") Object arguments, /** Active session working directory, when known. */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/FactoryRunSettledEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/FactoryRunSettledEvent.java new file mode 100644 index 0000000000..58ffa9cb3c --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/FactoryRunSettledEvent.java @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * 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_settled". Ephemeral signal that a factory run reached a terminal status. + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class FactoryRunSettledEvent extends SessionEvent { + + @Override + public String getType() { return "factory.run_settled"; } + + @JsonProperty("data") + private FactoryRunSettledEventData data; + + public FactoryRunSettledEventData getData() { return data; } + public void setData(FactoryRunSettledEventData data) { this.data = data; } + + /** Data payload for {@link FactoryRunSettledEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record FactoryRunSettledEventData( + /** Identifier of the factory run that settled. */ + @JsonProperty("runId") String runId, + /** Terminal status the run committed. */ + @JsonProperty("status") FactoryRunSettledStatus status, + /** Subagents this run consumed against its limits. */ + @JsonProperty("consumedSubagents") Long consumedSubagents, + /** AI credits this run consumed, in nano-AIU. */ + @JsonProperty("consumedNanoAiu") Long consumedNanoAiu, + /** Active milliseconds accumulated across every attempt of this run. */ + @JsonProperty("elapsedMs") Long elapsedMs, + /** Typed failure class recorded on the run, when it failed with one (e.g. `factory_limit_reached`). */ + @JsonProperty("failureType") String failureType + ) { + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/FactoryRunSettledStatus.java b/java/sdk/src/generated/java/com/github/copilot/generated/FactoryRunSettledStatus.java new file mode 100644 index 0000000000..bbeffbbf48 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/FactoryRunSettledStatus.java @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * Terminal status a factory run committed. A settled run is never `pending` or `running`, so those two members of the run-status domain are deliberately absent. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum FactoryRunSettledStatus { + /** The {@code completed} variant. */ + COMPLETED("completed"), + /** The {@code halted} variant. */ + HALTED("halted"), + /** The {@code cancelled} variant. */ + CANCELLED("cancelled"), + /** The {@code error} variant. */ + ERROR("error"); + + private final String value; + FactoryRunSettledStatus(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static FactoryRunSettledStatus fromValue(String value) { + for (FactoryRunSettledStatus v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown FactoryRunSettledStatus value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/FactoryRunStartedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/FactoryRunStartedEvent.java new file mode 100644 index 0000000000..691c7185d4 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/FactoryRunStartedEvent.java @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * 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_started". Ephemeral signal that a factory run attempt began executing. + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class FactoryRunStartedEvent extends SessionEvent { + + @Override + public String getType() { return "factory.run_started"; } + + @JsonProperty("data") + private FactoryRunStartedEventData data; + + public FactoryRunStartedEventData getData() { return data; } + public void setData(FactoryRunStartedEventData data) { this.data = data; } + + /** Data payload for {@link FactoryRunStartedEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record FactoryRunStartedEventData( + /** Identifier of the factory run that started. */ + @JsonProperty("runId") String runId, + /** Name of the factory this run executes. Low cardinality by construction. */ + @JsonProperty("factoryName") String factoryName, + /** Attempt number this start committed; a resumed run increments it. */ + @JsonProperty("attempt") Long attempt + ) { + } +} diff --git a/java/src/generated/java/com/github/copilot/generated/FactoryRunUpdatedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/FactoryRunUpdatedEvent.java similarity index 97% rename from java/src/generated/java/com/github/copilot/generated/FactoryRunUpdatedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/FactoryRunUpdatedEvent.java index e9abb1053a..33b542058b 100644 --- a/java/src/generated/java/com/github/copilot/generated/FactoryRunUpdatedEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/FactoryRunUpdatedEvent.java @@ -34,6 +34,7 @@ public final class FactoryRunUpdatedEvent extends SessionEvent { @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public record FactoryRunUpdatedEventData( + /** Factory run identifier. */ @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/FusionAttribution.java b/java/sdk/src/generated/java/com/github/copilot/generated/FusionAttribution.java new file mode 100644 index 0000000000..8e4ae7e937 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/FusionAttribution.java @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * Experimental attribution linking an ordinary event to the HydraFusion turn, phase, and concrete source that produced it. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record FusionAttribution( + /** Stable identifier for the HydraFusion turn that produced the event. */ + @JsonProperty("fusionId") String fusionId, + /** Idempotency identifier for the authoritative commit, when the event belongs to the selected output. */ + @JsonProperty("commitId") String commitId, + /** Synthetic HydraFusion model selected for the session. */ + @JsonProperty("syntheticModel") String syntheticModel, + /** HydraFusion routing policy used for the turn. */ + @JsonProperty("policy") String policy, + /** HydraFusion orchestration pattern selected for the turn. */ + @JsonProperty("pattern") String pattern, + /** Identifier of the concrete phase that produced the event. */ + @JsonProperty("phaseId") String phaseId, + /** Kind of concrete phase that produced the event. */ + @JsonProperty("phaseKind") String phaseKind, + /** Semantic role assigned to the concrete phase. */ + @JsonProperty("role") String role, + /** Concrete model that produced the attributed event. */ + @JsonProperty("sourceModel") String sourceModel, + /** Conversation scope in which the concrete phase executed. */ + @JsonProperty("conversationScope") String conversationScope, + /** Phase whose output supplied the authoritative content, when different from the executing phase. */ + @JsonProperty("sourcePhaseId") String sourcePhaseId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/FusionConversationScope.java b/java/sdk/src/generated/java/com/github/copilot/generated/FusionConversationScope.java new file mode 100644 index 0000000000..d38381ed48 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/FusionConversationScope.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; + +/** + * Conversation scope in which a HydraFusion phase executes. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum FusionConversationScope { + /** The {@code root} variant. */ + ROOT("root"), + /** The {@code review} variant. */ + REVIEW("review"); + + private final String value; + FusionConversationScope(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static FusionConversationScope fromValue(String value) { + for (FusionConversationScope v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown FusionConversationScope value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/FusionFollowUpAction.java b/java/sdk/src/generated/java/com/github/copilot/generated/FusionFollowUpAction.java new file mode 100644 index 0000000000..e54bf4604f --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/FusionFollowUpAction.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; + +/** + * Server-recommended routing behavior for a later HydraFusion turn. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum FusionFollowUpAction { + /** The {@code reuse_primary} variant. */ + REUSE_PRIMARY("reuse_primary"), + /** The {@code reroute} variant. */ + REROUTE("reroute"); + + private final String value; + FusionFollowUpAction(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static FusionFollowUpAction fromValue(String value) { + for (FusionFollowUpAction v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown FusionFollowUpAction value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/FusionFollowUpRecommendation.java b/java/sdk/src/generated/java/com/github/copilot/generated/FusionFollowUpRecommendation.java new file mode 100644 index 0000000000..101d497fb2 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/FusionFollowUpRecommendation.java @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * Durable server recommendation for subsequent HydraFusion turns. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record FusionFollowUpRecommendation( + /** Recommended routing action for the next user-message turn. */ + @JsonProperty("userTurn") FusionFollowUpAction userTurn, + /** Recommended routing action for the next compaction turn. */ + @JsonProperty("compactionTurn") FusionFollowUpAction compactionTurn +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/FusionPattern.java b/java/sdk/src/generated/java/com/github/copilot/generated/FusionPattern.java new file mode 100644 index 0000000000..d37ef4ee83 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/FusionPattern.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; + +/** + * Validated HydraFusion execution pattern. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum FusionPattern { + /** The {@code single} variant. */ + SINGLE("single"), + /** The {@code cascade} variant. */ + CASCADE("cascade"), + /** The {@code critique} variant. */ + CRITIQUE("critique"); + + private final String value; + FusionPattern(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static FusionPattern fromValue(String value) { + for (FusionPattern v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown FusionPattern value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/FusionPhaseActivityKind.java b/java/sdk/src/generated/java/com/github/copilot/generated/FusionPhaseActivityKind.java new file mode 100644 index 0000000000..a95a08cf91 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/FusionPhaseActivityKind.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; + +/** + * Content-safe activity observed while a HydraFusion phase is running. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum FusionPhaseActivityKind { + /** The {@code model_output} variant. */ + MODEL_OUTPUT("model_output"), + /** The {@code tool_started} variant. */ + TOOL_STARTED("tool_started"), + /** The {@code tool_completed} variant. */ + TOOL_COMPLETED("tool_completed"); + + private final String value; + FusionPhaseActivityKind(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static FusionPhaseActivityKind fromValue(String value) { + for (FusionPhaseActivityKind v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown FusionPhaseActivityKind value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/FusionPhaseKind.java b/java/sdk/src/generated/java/com/github/copilot/generated/FusionPhaseKind.java new file mode 100644 index 0000000000..ead79b75fc --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/FusionPhaseKind.java @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * HydraFusion phase kind. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum FusionPhaseKind { + /** The {@code primary} variant. */ + PRIMARY("primary"), + /** The {@code judge} variant. */ + JUDGE("judge"), + /** The {@code repair} variant. */ + REPAIR("repair"), + /** The {@code draft} variant. */ + DRAFT("draft"), + /** The {@code critic} variant. */ + CRITIC("critic"), + /** The {@code revision} variant. */ + REVISION("revision"), + /** The {@code follow_up} variant. */ + FOLLOW_UP("follow_up"); + + private final String value; + FusionPhaseKind(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static FusionPhaseKind fromValue(String value) { + for (FusionPhaseKind v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown FusionPhaseKind value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/FusionPhasePlanStep.java b/java/sdk/src/generated/java/com/github/copilot/generated/FusionPhasePlanStep.java new file mode 100644 index 0000000000..cfb07a789a --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/FusionPhasePlanStep.java @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * Presentation-neutral phase planned for a HydraFusion turn. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record FusionPhasePlanStep( + /** Kind of phase that may execute. */ + @JsonProperty("kind") FusionPhaseKind kind, + /** Semantic role assigned to the phase. */ + @JsonProperty("role") String role, + /** Conversation scope in which the phase executes. */ + @JsonProperty("scope") FusionConversationScope scope, + /** Whether the phase executes only when an earlier phase requests it. */ + @JsonProperty("conditional") Boolean conditional +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/FusionPhaseStatus.java b/java/sdk/src/generated/java/com/github/copilot/generated/FusionPhaseStatus.java new file mode 100644 index 0000000000..f670332b5c --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/FusionPhaseStatus.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; + +/** + * Durable outcome status of a HydraFusion phase. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum FusionPhaseStatus { + /** The {@code succeeded} variant. */ + SUCCEEDED("succeeded"), + /** The {@code failed} variant. */ + FAILED("failed"), + /** The {@code cancelled} variant. */ + CANCELLED("cancelled"); + + private final String value; + FusionPhaseStatus(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static FusionPhaseStatus fromValue(String value) { + for (FusionPhaseStatus v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown FusionPhaseStatus value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/FusionPhaseUsage.java b/java/sdk/src/generated/java/com/github/copilot/generated/FusionPhaseUsage.java new file mode 100644 index 0000000000..4a707bb69e --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/FusionPhaseUsage.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 com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Aggregate concrete-model usage for one HydraFusion phase. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record FusionPhaseUsage( + /** Number of concrete model requests made by the phase. */ + @JsonProperty("requestCount") Long requestCount, + /** Total input tokens consumed by the phase. */ + @JsonProperty("inputTokens") Long inputTokens, + /** Total output tokens produced by the phase. */ + @JsonProperty("outputTokens") Long outputTokens, + /** Total cached input tokens reported for the phase. */ + @JsonProperty("cachedTokens") Long cachedTokens, + /** Total tokens written to prompt cache during the phase. */ + @JsonProperty("cacheWriteTokens") Long cacheWriteTokens, + /** Total normalized AI-unit cost reported for the phase, in nano-AIU. */ + @JsonProperty("totalNanoAiu") Double totalNanoAiu +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/FusionProjectionMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/FusionProjectionMode.java new file mode 100644 index 0000000000..9681efc0ba --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/FusionProjectionMode.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; + +/** + * How a durable phase checkpoint contributes its exact message to canonical root history. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum FusionProjectionMode { + /** The {@code append} variant. */ + APPEND("append"), + /** The {@code staged} variant. */ + STAGED("staged"), + /** The {@code none} variant. */ + NONE("none"); + + private final String value; + FusionProjectionMode(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static FusionProjectionMode fromValue(String value) { + for (FusionProjectionMode v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown FusionProjectionMode value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/FusionScores.java b/java/sdk/src/generated/java/com/github/copilot/generated/FusionScores.java new file mode 100644 index 0000000000..eefb8190a1 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/FusionScores.java @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * Validated HydraFusion routing capability scores. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record FusionScores( + /** Reasoning capability score returned by the authenticated router. */ + @JsonProperty("reasoning") Double reasoning, + /** Code-generation capability score returned by the authenticated router. */ + @JsonProperty("codeGen") Double codeGen, + /** Debugging capability score returned by the authenticated router. */ + @JsonProperty("debugging") Double debugging, + /** Tool-use capability score returned by the authenticated router. */ + @JsonProperty("toolUse") Double toolUse +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/FusionStagedTerminal.java b/java/sdk/src/generated/java/com/github/copilot/generated/FusionStagedTerminal.java new file mode 100644 index 0000000000..262c8c664b --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/FusionStagedTerminal.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * Internal durable terminal request staged by a HydraFusion phase until an idempotent final commit selects it. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record FusionStagedTerminal( + @JsonProperty("assistantMessage") Object assistantMessage, + @JsonProperty("toolName") String toolName, + @JsonProperty("toolCallId") String toolCallId, + @JsonProperty("arguments") String arguments, + @JsonProperty("phaseId") String phaseId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/FusionTurnKind.java b/java/sdk/src/generated/java/com/github/copilot/generated/FusionTurnKind.java new file mode 100644 index 0000000000..24e39b976c --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/FusionTurnKind.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; + +/** + * Kind of turn for which HydraFusion routing is running. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum FusionTurnKind { + /** The {@code user} variant. */ + USER("user"), + /** The {@code compaction} variant. */ + COMPACTION("compaction"); + + private final String value; + FusionTurnKind(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static FusionTurnKind fromValue(String value) { + for (FusionTurnKind v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown FusionTurnKind value: " + value); + } +} diff --git a/java/src/generated/java/com/github/copilot/generated/GitHubMcpToolConfig.java b/java/sdk/src/generated/java/com/github/copilot/generated/GitHubMcpToolConfig.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/GitHubMcpToolConfig.java rename to java/sdk/src/generated/java/com/github/copilot/generated/GitHubMcpToolConfig.java 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 89% 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 index cd081dc87f..8d4afe7a1d 100644 --- a/java/src/generated/java/com/github/copilot/generated/HookEndEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/HookEndEvent.java @@ -43,7 +43,9 @@ public record HookEndEventData( /** Whether the hook completed successfully */ @JsonProperty("success") Boolean success, /** Error details when the hook failed */ - @JsonProperty("error") HookEndError error + @JsonProperty("error") HookEndError error, + /** Tool call ID of the parent tool invocation when this event originates from a sub-agent */ + @JsonProperty("parentToolCallId") String parentToolCallId ) { } } 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 70% 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 index 4c5de1a1d6..030e08caf8 100644 --- a/java/src/generated/java/com/github/copilot/generated/HookStartEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/HookStartEvent.java @@ -38,8 +38,10 @@ public record HookStartEventData( @JsonProperty("hookInvocationId") String hookInvocationId, /** Type of hook being invoked (e.g., "preToolUse", "postToolUse", "sessionStart") */ @JsonProperty("hookType") String hookType, - /** Input data passed to the hook */ - @JsonProperty("input") Object input + /** Input data passed to the hook. For postToolUse hooks the retained copy served by session.eventLog.read (and by a resumed session) elides the tool result's inline `contents`/`uiResource` and replaces an over-long `textResultForLlm` with a `[copilot:elided ...]` marker, to keep a multi-megabyte payload out of the durable event log; the live subscription stream still delivers the full value. Read the adjacent tool.execution_complete event for the tool result itself. */ + @JsonProperty("input") Object input, + /** Tool call ID of the parent tool invocation when this event originates from a sub-agent */ + @JsonProperty("parentToolCallId") String parentToolCallId ) { } } 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 85% 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 index cdeea72b4f..619fb326e1 100644 --- a/java/src/generated/java/com/github/copilot/generated/ManagedSettingsEnforcedEscalation.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/ManagedSettingsEnforcedEscalation.java @@ -20,12 +20,14 @@ public enum ManagedSettingsEnforcedEscalation { ALLOW_ALL("allow_all"), /** The {@code approve_all} variant. */ APPROVE_ALL("approve_all"), - /** The {@code auto_approval} variant. */ - AUTO_APPROVAL("auto_approval"), + /** The {@code assisted_approval} variant. */ + ASSISTED_APPROVAL("assisted_approval"), /** The {@code unrestricted_paths} variant. */ UNRESTRICTED_PATHS("unrestricted_paths"), /** The {@code unrestricted_urls} variant. */ - UNRESTRICTED_URLS("unrestricted_urls"); + UNRESTRICTED_URLS("unrestricted_urls"), + /** The {@code server_wide_mcp_approval} variant. */ + SERVER_WIDE_MCP_APPROVAL("server_wide_mcp_approval"); private final String value; ManagedSettingsEnforcedEscalation(String value) { this.value = value; } 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 79% 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 1ffa100635..90ce9de668 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,12 @@ public enum ManagedSettingsResolvedSource { SERVER("server"), /** The {@code device} variant. */ DEVICE("device"), + /** The {@code client} variant. */ + CLIENT("client"), + /** The {@code policyHelper} variant. */ + POLICYHELPER("policyHelper"), + /** 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 100% 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 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 100% 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 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 88% 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 f6c399b4c3..d3049cd472 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, + /** Per-request treatment/eligibility signal returned by the Copilot API in the `X-GitHub-Copilot-Request-TE` response header for the associated model call; `false` when the header was absent or unparseable. */ @JsonProperty("rte") Boolean rte, /** HTTP status code from the failed request */ @JsonProperty("statusCode") Long statusCode, @@ -66,6 +67,8 @@ public record ModelCallFailureEventData( @JsonProperty("isAuto") Boolean isAuto, /** Reasoning effort level used for the failed model call, if applicable */ @JsonProperty("reasoningEffort") String reasoningEffort, + /** Authoritative interaction classification for the failed call, matching `assistant.usage.interactionType` (for example `conversation-agent`, `conversation-subagent`, or `conversation-sampling`). Absent when the producer cannot classify the interaction. */ + @JsonProperty("interactionType") String interactionType, /** Where the failed model call originated */ @JsonProperty("source") ModelCallFailureSource source, /** Raw provider/runtime error message for restricted telemetry */ @@ -79,7 +82,9 @@ public record ModelCallFailureEventData( /** Per-quota usage snapshots parsed from the failed response's quota headers, keyed by quota identifier. Present when the error response carried quota headers (e.g. a 402 once the additional spend limit is reached) so the UI can refresh the quota display on failure. */ @JsonProperty("quotaSnapshots") Map quotaSnapshots, /** 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. */ - @JsonProperty("requestFingerprint") ModelCallFailureRequestFingerprint requestFingerprint + @JsonProperty("requestFingerprint") ModelCallFailureRequestFingerprint requestFingerprint, + /** Experimental HydraFusion attribution for this failed concrete model call. */ + @JsonProperty("fusion") FusionAttribution fusion ) { } } 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/sdk/src/generated/java/com/github/copilot/generated/ModelCallFinishedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallFinishedEvent.java new file mode 100644 index 0000000000..a1b424e807 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallFinishedEvent.java @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * 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 "model.call_finished". Final lifecycle outcome for one logical model dispatch. A logical dispatch may include internal reconnect or fallback work, so event count is not provider HTTP-request count. + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class ModelCallFinishedEvent extends SessionEvent { + + @Override + public String getType() { return "model.call_finished"; } + + @JsonProperty("data") + private ModelCallFinishedEventData data; + + public ModelCallFinishedEventData getData() { return data; } + public void setData(ModelCallFinishedEventData data) { this.data = data; } + + /** Data payload for {@link ModelCallFinishedEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record ModelCallFinishedEventData( + /** Agent-loop iteration within the interaction that initiated the model dispatch */ + @JsonProperty("turnId") String turnId, + /** Identifier of the user interaction that owns the model dispatch, matching assistant.turn_start.interactionId when available */ + @JsonProperty("interactionId") String interactionId, + /** Monotonic elapsed time spent in the logical model dispatch, including any internal transport reconnect or fallback and excluding orchestrator retry backoff, tool execution, confirmations, and post-response processing */ + @JsonProperty("dispatchDurationMs") Double dispatchDurationMs, + /** Final outcome after post-response acceptance processing */ + @JsonProperty("outcome") ModelCallFinishedOutcome outcome, + /** Whether an accepted successful response requested the exact name and command semantics of a built-in file edit tool, including an external tool explicitly replacing that built-in name. Absent when the logical dispatch did not produce an accepted response. */ + @JsonProperty("containsBuiltInFileEditRequest") Boolean containsBuiltInFileEditRequest, + /** Version of the built-in file-edit semantic classifier used for this event */ + @JsonProperty("editClassifierVersion") Long editClassifierVersion + ) { + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallFinishedOutcome.java b/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallFinishedOutcome.java new file mode 100644 index 0000000000..8b86ed0281 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallFinishedOutcome.java @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * Final outcome of one logical model dispatch after response acceptance processing + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum ModelCallFinishedOutcome { + /** The {@code success} variant. */ + SUCCESS("success"), + /** The {@code error} variant. */ + ERROR("error"), + /** The {@code cancelled} variant. */ + CANCELLED("cancelled"), + /** The {@code rejected} variant. */ + REJECTED("rejected"); + + private final String value; + ModelCallFinishedOutcome(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static ModelCallFinishedOutcome fromValue(String value) { + for (ModelCallFinishedOutcome v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown ModelCallFinishedOutcome value: " + value); + } +} 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 92% 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 9f00e2ac2f..a68e455420 100644 --- a/java/src/generated/java/com/github/copilot/generated/ModelCallStartEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallStartEvent.java @@ -39,7 +39,9 @@ public record ModelCallStartEventData( /** Model identifier used for this API call, when known */ @JsonProperty("model") String model, /** Previous response or interaction identifier included in the model request, when present */ - @JsonProperty("previousResponseId") String previousResponseId + @JsonProperty("previousResponseId") String previousResponseId, + /** Experimental HydraFusion attribution for this concrete model call. */ + @JsonProperty("fusion") FusionAttribution fusion ) { } } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/ModelChangeSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/ModelChangeSource.java new file mode 100644 index 0000000000..0d54709de8 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/ModelChangeSource.java @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * Origin of an effective session model change. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum ModelChangeSource { + /** The {@code model_command} variant. */ + MODEL_COMMAND("model_command"), + /** The {@code settings_command} variant. */ + SETTINGS_COMMAND("settings_command"), + /** The {@code config_command} variant. */ + CONFIG_COMMAND("config_command"), + /** The {@code model_picker} variant. */ + MODEL_PICKER("model_picker"), + /** The {@code managed_settings} variant. */ + MANAGED_SETTINGS("managed_settings"), + /** The {@code repo_settings} variant. */ + REPO_SETTINGS("repo_settings"), + /** The {@code startup} variant. */ + STARTUP("startup"), + /** The {@code agent} variant. */ + AGENT("agent"), + /** The {@code plan_mode} variant. */ + PLAN_MODE("plan_mode"), + /** The {@code automatic} variant. */ + AUTOMATIC("automatic"), + /** The {@code sdk} variant. */ + SDK("sdk"); + + private final String value; + ModelChangeSource(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static ModelChangeSource fromValue(String value) { + for (ModelChangeSource v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown ModelChangeSource value: " + value); + } +} 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/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/sdk/src/generated/java/com/github/copilot/generated/PermissionMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/PermissionMode.java new file mode 100644 index 0000000000..421ad76139 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/PermissionMode.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; + +/** + * Permission mode for the session. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum PermissionMode { + /** The {@code manual} variant. */ + MANUAL("manual"), + /** The {@code assisted} variant. */ + ASSISTED("assisted"), + /** The {@code allow-all} variant. */ + ALLOW_ALL("allow-all"); + + private final String value; + PermissionMode(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static PermissionMode fromValue(String value) { + for (PermissionMode v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown PermissionMode value: " + value); + } +} 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 93% 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 b7aae9ec80..a854d5ffa4 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, + /** Agent mode captured from the owning turn when permission evaluation began. */ + @JsonProperty("agentMode") SessionMode agentMode, /** 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 */ 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/sdk/src/generated/java/com/github/copilot/generated/PromptCacheBreakEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/PromptCacheBreakEvent.java new file mode 100644 index 0000000000..7ad39dec97 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/PromptCacheBreakEvent.java @@ -0,0 +1,86 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * Session event "prompt_cache_break". A detected loss of a previously cached prompt prefix + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class PromptCacheBreakEvent extends SessionEvent { + + @Override + public String getType() { return "prompt_cache_break"; } + + @JsonProperty("data") + private PromptCacheBreakEventData data; + + public PromptCacheBreakEventData getData() { return data; } + public void setData(PromptCacheBreakEventData data) { this.data = data; } + + /** Data payload for {@link PromptCacheBreakEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record PromptCacheBreakEventData( + /** The highest-precedence reason for the cache break */ + @JsonProperty("primaryReason") String primaryReason, + /** All reasons that contributed to the cache break, ordered by precedence */ + @JsonProperty("contributingReasons") List contributingReasons, + /** Request state that established the prior cache frontier */ + @JsonProperty("beforeRequest") Object beforeRequest, + /** Request state whose cached prefix fell short */ + @JsonProperty("afterRequest") Object afterRequest, + /** Number of cached prefix tokens that survived */ + @JsonProperty("survivedTokens") Long survivedTokens, + /** Prior cached prompt frontier in tokens */ + @JsonProperty("frontierTokens") Long frontierTokens, + /** Cached prefix tokens lost since the prior call */ + @JsonProperty("shortfallTokens") Long shortfallTokens, + /** Fraction of the prior cache frontier that survived */ + @JsonProperty("retentionRatio") Double retentionRatio, + /** Model that held the prior cache frontier, when the call changed models */ + @JsonProperty("modelFrom") String modelFrom, + /** Model this call targeted, when the call changed models */ + @JsonProperty("modelTo") String modelTo, + /** Telemetry-safe names of tools added since the prior call */ + @JsonProperty("toolsAdded") List toolsAdded, + /** Telemetry-safe names of tools removed since the prior call */ + @JsonProperty("toolsRemoved") List toolsRemoved, + /** Telemetry-safe names of tools whose definition changed since the prior call */ + @JsonProperty("toolsRedefined") List toolsRedefined, + /** Raw names of tools added since the prior call, restricted because a tool name can be user-authored */ + @JsonProperty("toolsAddedRaw") List toolsAddedRaw, + /** Raw names of tools removed since the prior call, restricted because a tool name can be user-authored */ + @JsonProperty("toolsRemovedRaw") List toolsRemovedRaw, + /** Raw names of tools redefined since the prior call, restricted because a tool name can be user-authored */ + @JsonProperty("toolsRedefinedRaw") List toolsRedefinedRaw, + /** Whether the tool list kept its members but changed their order */ + @JsonProperty("toolsReordered") Boolean toolsReordered, + /** Names of the system-prompt segments whose content changed */ + @JsonProperty("systemSegmentsChanged") List systemSegmentsChanged, + /** Names of the cache-configuration fields that changed */ + @JsonProperty("cacheConfigChangedFields") List cacheConfigChangedFields, + /** Index of the first conversation message whose content changed */ + @JsonProperty("rewriteMessageIndex") Long rewriteMessageIndex, + /** Shape of the history rewrite, for example whether the history grew or shrank */ + @JsonProperty("rewriteShape") String rewriteShape, + /** Subsystems that announced a history rewrite before this call, for example compaction or truncation */ + @JsonProperty("rewriteSource") List rewriteSource, + /** Name of the sub-agent whose conversation broke, stamped by the parent bridge */ + @JsonProperty("agentName") String agentName + ) { + } +} 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/SandboxDecisionEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SandboxDecisionEvent.java new file mode 100644 index 0000000000..f87be0fe9d --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SandboxDecisionEvent.java @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * 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 "sandbox.decision". Payload of `sandbox.decision`, a bounded governance record of what the process sandbox was configured to do and whether it took effect. Discriminated by `kind`. + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SandboxDecisionEvent extends SessionEvent { + + @Override + public String getType() { return "sandbox.decision"; } + + @JsonProperty("data") + private SandboxDecisionEventData data; + + public SandboxDecisionEventData getData() { return data; } + public void setData(SandboxDecisionEventData data) { this.data = data; } + + /** Data payload for {@link SandboxDecisionEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record SandboxDecisionEventData() { + } +} diff --git a/java/src/generated/java/com/github/copilot/generated/ScheduleOrigin.java b/java/sdk/src/generated/java/com/github/copilot/generated/ScheduleOrigin.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ScheduleOrigin.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ScheduleOrigin.java 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 100% 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 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 96% 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 d05110abce..1925f6d893 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionCompactionCompleteEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionCompactionCompleteEvent.java @@ -52,6 +52,8 @@ public record SessionCompactionCompleteEventData( @JsonProperty("customInstructions") String customInstructions, /** LLM-generated summary of the compacted conversation history */ @JsonProperty("summaryContent") String summaryContent, + /** Canonical model identifier used for model-specific behavior when replaying compaction */ + @JsonProperty("behaviorModelId") String behaviorModelId, /** Checkpoint snapshot number created for recovery */ @JsonProperty("checkpointNumber") Long checkpointNumber, /** File path where the checkpoint was stored */ 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 100% 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 diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionCompletionReceiptEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionCompletionReceiptEvent.java new file mode 100644 index 0000000000..15670c992a --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionCompletionReceiptEvent.java @@ -0,0 +1,55 @@ +/*--------------------------------------------------------------------------------------------- + * 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.completion_receipt". Behavior-neutral record of structured runtime facts present when an agent completion decision is accepted. + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SessionCompletionReceiptEvent extends SessionEvent { + + @Override + public String getType() { return "session.completion_receipt"; } + + @JsonProperty("data") + private SessionCompletionReceiptEventData data; + + public SessionCompletionReceiptEventData getData() { return data; } + public void setData(SessionCompletionReceiptEventData data) { this.data = data; } + + /** Data payload for {@link SessionCompletionReceiptEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record SessionCompletionReceiptEventData( + /** Version of the completion receipt payload. */ + @JsonProperty("schemaVersion") Long schemaVersion, + /** One-based accepted completion receipt ordinal in the durable session history. */ + @JsonProperty("attempt") Long attempt, + /** Identifier of the assistant turn-end event that supplied the accepted completion boundary. This is the receipt's idempotency key, and always equals eventRange.endEventId. */ + @JsonProperty("sourceEventId") String sourceEventId, + /** Inclusive durable event range summarized by this receipt. */ + @JsonProperty("eventRange") CompletionReceiptEventRange eventRange, + /** Runtime reason the completion decision was accepted. */ + @JsonProperty("stopReason") CompletionReceiptStopReason stopReason, + /** Final structured tool completion in the covered range, when one exists. */ + @JsonProperty("finalTool") CompletionReceiptFinalTool finalTool, + /** Number of successful structured tool completions in the covered range. */ + @JsonProperty("successfulToolCount") Long successfulToolCount, + /** Number of failed structured tool completions in the covered range. */ + @JsonProperty("failedToolCount") Long failedToolCount + ) { + } +} 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 100% 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 diff --git a/java/src/generated/java/com/github/copilot/generated/SessionContextClearedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionContextClearedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionContextClearedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionContextClearedEvent.java 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 87% 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 582ecd3d4f..193aa56b8f 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionEvent.java @@ -39,6 +39,7 @@ @JsonSubTypes.Type(value = SessionWarningEvent.class, name = "session.warning"), @JsonSubTypes.Type(value = SessionModelChangeEvent.class, name = "session.model_change"), @JsonSubTypes.Type(value = SessionModeChangedEvent.class, name = "session.mode_changed"), + @JsonSubTypes.Type(value = SessionModeNoticeDeliveredEvent.class, name = "session.mode_notice_delivered"), @JsonSubTypes.Type(value = SessionSessionLimitsChangedEvent.class, name = "session.session_limits_changed"), @JsonSubTypes.Type(value = SessionPermissionsChangedEvent.class, name = "session.permissions_changed"), @JsonSubTypes.Type(value = SessionPlanChangedEvent.class, name = "session.plan_changed"), @@ -55,11 +56,21 @@ @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"), + @JsonSubTypes.Type(value = SessionCompletionReceiptEvent.class, name = "session.completion_receipt"), + @JsonSubTypes.Type(value = SessionFusionRouteStartedEvent.class, name = "session.fusion_route_started"), + @JsonSubTypes.Type(value = SessionFusionRouteFailedEvent.class, name = "session.fusion_route_failed"), + @JsonSubTypes.Type(value = SessionFusionResolvedEvent.class, name = "session.fusion_resolved"), + @JsonSubTypes.Type(value = SessionFusionCompletedEvent.class, name = "session.fusion_completed"), @JsonSubTypes.Type(value = UserMessageEvent.class, name = "user.message"), @JsonSubTypes.Type(value = PendingMessagesModifiedEvent.class, name = "pending_messages.modified"), @JsonSubTypes.Type(value = AssistantTurnStartEvent.class, name = "assistant.turn_start"), @JsonSubTypes.Type(value = AssistantTurnRetryEvent.class, name = "assistant.turn_retry"), + @JsonSubTypes.Type(value = AgentInterruptedEvent.class, name = "agent.interrupted"), @JsonSubTypes.Type(value = AssistantIntentEvent.class, name = "assistant.intent"), + @JsonSubTypes.Type(value = AssistantFusionPhaseStartedEvent.class, name = "assistant.fusion_phase_started"), + @JsonSubTypes.Type(value = AssistantFusionPhaseActivityEvent.class, name = "assistant.fusion_phase_activity"), + @JsonSubTypes.Type(value = AssistantFusionPhaseCompletedEvent.class, name = "assistant.fusion_phase_completed"), + @JsonSubTypes.Type(value = AssistantFusionPhaseFailedEvent.class, name = "assistant.fusion_phase_failed"), @JsonSubTypes.Type(value = AssistantServerToolProgressEvent.class, name = "assistant.server_tool_progress"), @JsonSubTypes.Type(value = AssistantReasoningEvent.class, name = "assistant.reasoning"), @JsonSubTypes.Type(value = AssistantReasoningDeltaEvent.class, name = "assistant.reasoning_delta"), @@ -71,7 +82,9 @@ @JsonSubTypes.Type(value = AssistantTurnEndEvent.class, name = "assistant.turn_end"), @JsonSubTypes.Type(value = AssistantIdleEvent.class, name = "assistant.idle"), @JsonSubTypes.Type(value = AssistantUsageEvent.class, name = "assistant.usage"), + @JsonSubTypes.Type(value = PromptCacheBreakEvent.class, name = "prompt_cache_break"), @JsonSubTypes.Type(value = ModelCallFailureEvent.class, name = "model.call_failure"), + @JsonSubTypes.Type(value = ModelCallFinishedEvent.class, name = "model.call_finished"), @JsonSubTypes.Type(value = ModelCallStartEvent.class, name = "model.call_start"), @JsonSubTypes.Type(value = AbortEvent.class, name = "abort"), @JsonSubTypes.Type(value = ToolUserRequestedEvent.class, name = "tool.user_requested"), @@ -81,7 +94,9 @@ @JsonSubTypes.Type(value = ToolExecutionCompleteEvent.class, name = "tool.execution_complete"), @JsonSubTypes.Type(value = ToolSearchActivatedEvent.class, name = "tool_search.activated"), @JsonSubTypes.Type(value = SkillInvokedEvent.class, name = "skill.invoked"), + @JsonSubTypes.Type(value = SandboxDecisionEvent.class, name = "sandbox.decision"), @JsonSubTypes.Type(value = SubagentStartedEvent.class, name = "subagent.started"), + @JsonSubTypes.Type(value = SubagentConfiguredEvent.class, name = "subagent.configured"), @JsonSubTypes.Type(value = SubagentCompletedEvent.class, name = "subagent.completed"), @JsonSubTypes.Type(value = SubagentFailedEvent.class, name = "subagent.failed"), @JsonSubTypes.Type(value = SubagentSelectedEvent.class, name = "subagent.selected"), @@ -105,6 +120,7 @@ @JsonSubTypes.Type(value = McpHeadersRefreshRequiredEvent.class, name = "mcp.headers_refresh_required"), @JsonSubTypes.Type(value = McpHeadersRefreshCompletedEvent.class, name = "mcp.headers_refresh_completed"), @JsonSubTypes.Type(value = SessionCustomNotificationEvent.class, name = "session.custom_notification"), + @JsonSubTypes.Type(value = UiEphemeralQueryEvent.class, name = "ui.ephemeral_query"), @JsonSubTypes.Type(value = ExternalToolRequestedEvent.class, name = "external_tool.requested"), @JsonSubTypes.Type(value = ExternalToolCompletedEvent.class, name = "external_tool.completed"), @JsonSubTypes.Type(value = CommandQueuedEvent.class, name = "command.queued"), @@ -124,6 +140,8 @@ @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 = FactoryRunStartedEvent.class, name = "factory.run_started"), + @JsonSubTypes.Type(value = FactoryRunSettledEvent.class, name = "factory.run_settled"), @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"), @@ -157,6 +175,7 @@ public abstract sealed class SessionEvent permits SessionWarningEvent, SessionModelChangeEvent, SessionModeChangedEvent, + SessionModeNoticeDeliveredEvent, SessionSessionLimitsChangedEvent, SessionPermissionsChangedEvent, SessionPlanChangedEvent, @@ -173,11 +192,21 @@ public abstract sealed class SessionEvent permits SessionCompactionStartEvent, SessionCompactionCompleteEvent, SessionTaskCompleteEvent, + SessionCompletionReceiptEvent, + SessionFusionRouteStartedEvent, + SessionFusionRouteFailedEvent, + SessionFusionResolvedEvent, + SessionFusionCompletedEvent, UserMessageEvent, PendingMessagesModifiedEvent, AssistantTurnStartEvent, AssistantTurnRetryEvent, + AgentInterruptedEvent, AssistantIntentEvent, + AssistantFusionPhaseStartedEvent, + AssistantFusionPhaseActivityEvent, + AssistantFusionPhaseCompletedEvent, + AssistantFusionPhaseFailedEvent, AssistantServerToolProgressEvent, AssistantReasoningEvent, AssistantReasoningDeltaEvent, @@ -189,7 +218,9 @@ public abstract sealed class SessionEvent permits AssistantTurnEndEvent, AssistantIdleEvent, AssistantUsageEvent, + PromptCacheBreakEvent, ModelCallFailureEvent, + ModelCallFinishedEvent, ModelCallStartEvent, AbortEvent, ToolUserRequestedEvent, @@ -199,7 +230,9 @@ public abstract sealed class SessionEvent permits ToolExecutionCompleteEvent, ToolSearchActivatedEvent, SkillInvokedEvent, + SandboxDecisionEvent, SubagentStartedEvent, + SubagentConfiguredEvent, SubagentCompletedEvent, SubagentFailedEvent, SubagentSelectedEvent, @@ -223,6 +256,7 @@ public abstract sealed class SessionEvent permits McpHeadersRefreshRequiredEvent, McpHeadersRefreshCompletedEvent, SessionCustomNotificationEvent, + UiEphemeralQueryEvent, ExternalToolRequestedEvent, ExternalToolCompletedEvent, CommandQueuedEvent, @@ -242,6 +276,8 @@ public abstract sealed class SessionEvent permits SessionToolsUpdatedEvent, SessionBackgroundTasksChangedEvent, FactoryRunUpdatedEvent, + FactoryRunStartedEvent, + FactoryRunSettledEvent, 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/sdk/src/generated/java/com/github/copilot/generated/SessionFusionCompletedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionCompletedEvent.java new file mode 100644 index 0000000000..6742cdc3e3 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionCompletedEvent.java @@ -0,0 +1,75 @@ +/*--------------------------------------------------------------------------------------------- + * 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.fusion_completed". Experimental durable aggregate outcome of a HydraFusion turn. + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SessionFusionCompletedEvent extends SessionEvent { + + @Override + public String getType() { return "session.fusion_completed"; } + + @JsonProperty("data") + private SessionFusionCompletedEventData data; + + public SessionFusionCompletedEventData getData() { return data; } + public void setData(SessionFusionCompletedEventData data) { this.data = data; } + + /** Data payload for {@link SessionFusionCompletedEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record SessionFusionCompletedEventData( + /** Stable identifier for the completed HydraFusion turn. */ + @JsonProperty("fusionId") String fusionId, + /** Idempotency identifier for the authoritative final commit. */ + @JsonProperty("commitId") String commitId, + /** Identifier of the session turn associated with the completion. */ + @JsonProperty("turnId") String turnId, + /** Synthetic HydraFusion model selected for the session. */ + @JsonProperty("syntheticModel") String syntheticModel, + /** HydraFusion orchestration pattern executed for the turn. */ + @JsonProperty("pattern") FusionPattern pattern, + /** Stable aggregate outcome of the HydraFusion turn. */ + @JsonProperty("outcome") String outcome, + /** Phase whose output supplied the authoritative final content. */ + @JsonProperty("finalSourcePhaseId") String finalSourcePhaseId, + /** Concrete model that supplied the authoritative final content. */ + @JsonProperty("finalSourceModel") String finalSourceModel, + /** Concrete model recommended for eligible follow-up turns. */ + @JsonProperty("followUpModel") String followUpModel, + /** Reason the turn used a degraded route, when applicable. */ + @JsonProperty("degradedReason") String degradedReason, + /** Number of concrete phases attempted by the turn. */ + @JsonProperty("phaseCount") Long phaseCount, + /** Total concrete model requests made across all phases. */ + @JsonProperty("requestCount") Long requestCount, + /** Total input tokens consumed across all phases. */ + @JsonProperty("inputTokens") Long inputTokens, + /** Total output tokens produced across all phases. */ + @JsonProperty("outputTokens") Long outputTokens, + /** Total cached input tokens reported across all phases. */ + @JsonProperty("cachedTokens") Long cachedTokens, + /** Total tokens written to prompt cache across all phases. */ + @JsonProperty("cacheWriteTokens") Long cacheWriteTokens, + /** Total normalized AI-unit cost reported across all phases, in nano-AIU. */ + @JsonProperty("totalNanoAiu") Double totalNanoAiu, + /** Total elapsed execution time for the HydraFusion turn in milliseconds. */ + @JsonProperty("durationMs") Double durationMs + ) { + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionResolvedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionResolvedEvent.java new file mode 100644 index 0000000000..d63bee33b3 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionResolvedEvent.java @@ -0,0 +1,82 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * Session event "session.fusion_resolved". Experimental durable validated HydraFusion route and turn policy. + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SessionFusionResolvedEvent extends SessionEvent { + + @Override + public String getType() { return "session.fusion_resolved"; } + + @JsonProperty("data") + private SessionFusionResolvedEventData data; + + public SessionFusionResolvedEventData getData() { return data; } + public void setData(SessionFusionResolvedEventData data) { this.data = data; } + + /** Data payload for {@link SessionFusionResolvedEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record SessionFusionResolvedEventData( + /** Stable identifier for the resolved HydraFusion turn. */ + @JsonProperty("fusionId") String fusionId, + /** Identifier of the session turn associated with the route. */ + @JsonProperty("turnId") String turnId, + /** Version of the validated HydraFusion event contract. */ + @JsonProperty("contractVersion") Long contractVersion, + /** Synthetic HydraFusion model selected for the session. */ + @JsonProperty("syntheticModel") String syntheticModel, + /** HydraFusion routing policy used to resolve the plan. */ + @JsonProperty("policy") String policy, + /** Router implementation that supplied the plan. */ + @JsonProperty("routeSource") String routeSource, + /** Version of the validated execution-plan format. */ + @JsonProperty("planVersion") String planVersion, + /** Version of the local routing policy. */ + @JsonProperty("policyVersion") String policyVersion, + /** Version of the executable model universe used for selection. */ + @JsonProperty("modelUniverseVersion") String modelUniverseVersion, + /** Identifier of the local policy rule that matched. */ + @JsonProperty("ruleId") String ruleId, + /** Zero-based index of the local policy rule that matched. */ + @JsonProperty("ruleIndex") Long ruleIndex, + /** Human-readable name of the local policy rule that matched. */ + @JsonProperty("ruleName") String ruleName, + /** Validated capability scores used to select the route. */ + @JsonProperty("scores") FusionScores scores, + /** Validated orchestration pattern selected for the turn. */ + @JsonProperty("pattern") FusionPattern pattern, + /** Presentation-neutral phase plan for clients that render workflow progress. */ + @JsonProperty("phasePlan") List phasePlan, + /** Concrete model selected for the primary solver phase. */ + @JsonProperty("primaryModel") String primaryModel, + /** Concrete model selected for the review or judge phase, when required. */ + @JsonProperty("secondaryModel") String secondaryModel, + /** Concrete model used when the planned primary model cannot execute. */ + @JsonProperty("fallbackModel") String fallbackModel, + /** Concrete model recommended for eligible follow-up turns. */ + @JsonProperty("followUpModel") String followUpModel, + /** Router recommendation controlling reuse or rerouting on later turns. */ + @JsonProperty("followUp") FusionFollowUpRecommendation followUp, + /** Elapsed time in milliseconds required to resolve and validate the route. */ + @JsonProperty("routingLatencyMs") Double routingLatencyMs + ) { + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionRouteFailedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionRouteFailedEvent.java new file mode 100644 index 0000000000..25698b336b --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionRouteFailedEvent.java @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------------------------- + * 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.fusion_route_failed". Experimental durable HydraFusion routing failure and the deterministic concrete fallback selected for the turn. + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SessionFusionRouteFailedEvent extends SessionEvent { + + @Override + public String getType() { return "session.fusion_route_failed"; } + + @JsonProperty("data") + private SessionFusionRouteFailedEventData data; + + public SessionFusionRouteFailedEventData getData() { return data; } + public void setData(SessionFusionRouteFailedEventData data) { this.data = data; } + + /** Data payload for {@link SessionFusionRouteFailedEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record SessionFusionRouteFailedEventData( + /** Identifier of the routing attempt that failed. */ + @JsonProperty("attemptId") String attemptId, + /** Synthetic HydraFusion model selected for the session. */ + @JsonProperty("syntheticModel") String syntheticModel, + /** HydraFusion routing policy requested for the turn. */ + @JsonProperty("policy") String policy, + /** Stable machine-readable reason for the routing failure. */ + @JsonProperty("reason") String reason, + /** Provider or validation error detail, when available. */ + @JsonProperty("errorMessage") String errorMessage, + /** Concrete model selected as the deterministic fallback. */ + @JsonProperty("fallbackModel") String fallbackModel, + /** Elapsed routing time in milliseconds before the failure. */ + @JsonProperty("routingLatencyMs") Double routingLatencyMs + ) { + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionRouteStartedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionRouteStartedEvent.java new file mode 100644 index 0000000000..139cb4f011 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionFusionRouteStartedEvent.java @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * 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.fusion_route_started". Experimental transient signal that HydraFusion routing has started for an eligible turn. + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SessionFusionRouteStartedEvent extends SessionEvent { + + @Override + public String getType() { return "session.fusion_route_started"; } + + @JsonProperty("data") + private SessionFusionRouteStartedEventData data; + + public SessionFusionRouteStartedEventData getData() { return data; } + public void setData(SessionFusionRouteStartedEventData data) { this.data = data; } + + /** Data payload for {@link SessionFusionRouteStartedEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record SessionFusionRouteStartedEventData( + /** Identifier for this routing attempt before a durable Fusion turn exists. */ + @JsonProperty("attemptId") String attemptId, + /** Kind of turn being routed. */ + @JsonProperty("turnKind") FusionTurnKind turnKind, + /** Synthetic HydraFusion model selected for the session. */ + @JsonProperty("syntheticModel") String syntheticModel, + /** HydraFusion routing policy requested for the turn. */ + @JsonProperty("policy") String policy + ) { + } +} 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 81% 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 index e51a26e5ae..ae509c4b42 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionIdleEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionIdleEvent.java @@ -35,7 +35,9 @@ public final class SessionIdleEvent extends SessionEvent { @JsonInclude(JsonInclude.Include.NON_NULL) public record SessionIdleEventData( /** True when the preceding agentic loop was cancelled via abort signal */ - @JsonProperty("aborted") Boolean aborted + @JsonProperty("aborted") Boolean aborted, + /** The session mode the agent was operating in when it went idle, when the mode is known. Lets turn-scoped consumers distinguish an autopilot continuation boundary (where the agent keeps working after this idle) from a genuine turn completion. */ + @JsonProperty("mode") SessionMode mode ) { } } 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 58% 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 7c7a4df738..e5428d26db 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, then the policy helper, per ordinary key, while permissions compose restrictively across device, server, policy-helper, 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,17 +35,23 @@ 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`, `client`, or `policyHelper` 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 the policy-helper managed-settings layer was present. The policy helper is the weakest channel: it fills keys no enterprise source set and can never replace one. */ + @JsonProperty("policyHelperManaged") Boolean policyHelperManaged, /** 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 the effective sandbox policy forces the sandbox on *only* because managed policy could not be determined, rather than because the policy requires it. Lets clients tell a user whose `--no-sandbox` was overridden that the sandbox stayed on as a fail-closed fallback, instead of attributing it to an administrator who set no such policy. */ + @JsonProperty("sandboxEnabledByUndeterminedPolicy") Boolean sandboxEnabledByUndeterminedPolicy, /** 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 server and device each supplied a permission allowlist, so enforcement intersects them and the flattened settings payload omits `permissions.allow`. */ + /** 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, 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 100% 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 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/sdk/src/generated/java/com/github/copilot/generated/SessionModeNoticeDeliveredEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionModeNoticeDeliveredEvent.java new file mode 100644 index 0000000000..9e24109f9a --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionModeNoticeDeliveredEvent.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.mode_notice_delivered". Records that a mode transition notice reached the model so cache-stable mode tools can remain offered across resume. + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SessionModeNoticeDeliveredEvent extends SessionEvent { + + @Override + public String getType() { return "session.mode_notice_delivered"; } + + @JsonProperty("data") + private SessionModeNoticeDeliveredEventData data; + + public SessionModeNoticeDeliveredEventData getData() { return data; } + public void setData(SessionModeNoticeDeliveredEventData data) { this.data = data; } + + /** Data payload for {@link SessionModeNoticeDeliveredEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record SessionModeNoticeDeliveredEventData( + /** Mode established by the delivered transition notice */ + @JsonProperty("mode") SessionMode mode, + /** Model-visible transition notice persisted for a mid-turn delivery */ + @JsonProperty("content") String content + ) { + } +} 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 94% 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 e53c1594ae..868d78fe0d 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionModelChangeEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionModelChangeEvent.java @@ -53,7 +53,9 @@ public record SessionModelChangeEventData( /** 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. `"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 + @JsonProperty("cause") String cause, + /** Origin of the effective model change, when known. */ + @JsonProperty("source") ModelChangeSource source ) { } } 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 66% 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 index c1f82f5af7..ef332617ac 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionPermissionsChangedEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionPermissionsChangedEvent.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Session event "session.permissions_changed". Permissions change details carrying the aggregate allow-all transition. + * Session event "session.permissions_changed". Permission-mode transition details. * @since 1.0.0 */ @JsonIgnoreProperties(ignoreUnknown = true) @@ -34,14 +34,12 @@ public final class SessionPermissionsChangedEvent extends SessionEvent { @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public record SessionPermissionsChangedEventData( - /** Aggregate allow-all flag before the change */ - @JsonProperty("previousAllowAllPermissions") Boolean previousAllowAllPermissions, - /** Aggregate allow-all flag after the change */ - @JsonProperty("allowAllPermissions") Boolean allowAllPermissions, - /** Allow-all mode before the change */ - @JsonProperty("previousAllowAllPermissionMode") PermissionAllowAllMode previousAllowAllPermissionMode, - /** Allow-all mode after the change */ - @JsonProperty("allowAllPermissionMode") PermissionAllowAllMode allowAllPermissionMode + /** Permission mode before the change */ + @JsonProperty("previousMode") PermissionMode previousMode, + /** Permission mode after the change */ + @JsonProperty("mode") PermissionMode mode, + /** Explicit LLM judge model override used by assisted mode; omitted when the provider default applies */ + @JsonProperty("assistedApprovalModel") String assistedApprovalModel ) { } } 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 97% 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 a3f39d7696..de54f8a3ce 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionResumeEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionResumeEvent.java @@ -51,6 +51,8 @@ public record SessionResumeEventData( @JsonProperty("verbosity") Verbosity verbosity, /** Context tier currently selected at resume time; null when no tier is active */ @JsonProperty("contextTier") ContextTier contextTier, + /** Auto routing preference active at resume time */ + @JsonProperty("autoTier") AutoTier autoTier, /** Session limits currently configured at resume time; null when no limits are active */ @JsonProperty("sessionLimits") SessionLimitsConfig sessionLimits, /** Updated working directory and git context at resume time */ 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 100% 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 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 94% 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 index 84c3023066..cf8600e388 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionShutdownEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionShutdownEvent.java @@ -55,6 +55,8 @@ public record SessionShutdownEventData( @JsonProperty("codeChanges") ShutdownCodeChanges codeChanges, /** Per-model usage breakdown, keyed by model identifier */ @JsonProperty("modelMetrics") Map modelMetrics, + /** Per-agent usage breakdown, keyed by agent instance identifier. The main conversation uses the stable key `main`. */ + @JsonProperty("agentMetrics") Map agentMetrics, /** Model that was selected at the time of shutdown */ @JsonProperty("currentModel") String currentModel, /** Total tokens in context window at shutdown */ 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 bf8b4e91cf..b977ae036f 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionStartEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionStartEvent.java @@ -55,6 +55,8 @@ public record SessionStartEventData( @JsonProperty("verbosity") Verbosity verbosity, /** Context tier selected at session creation time for models with tiered context pricing; null when no tier is selected (e.g., non-tiered model) */ @JsonProperty("contextTier") ContextTier contextTier, + /** Auto routing preference selected at session creation time */ + @JsonProperty("autoTier") AutoTier autoTier, /** Session limits configured at session creation time, if any */ @JsonProperty("sessionLimits") SessionLimitsConfig sessionLimits, /** Working directory and git context at session start */ 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 100% 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 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 90% 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 index 1a400c0130..bdbc1add95 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionUsageCheckpointEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionUsageCheckpointEvent.java @@ -40,7 +40,9 @@ public record SessionUsageCheckpointEventData( /** Total number of premium API requests used at checkpoint time */ @JsonProperty("totalPremiumRequests") Double totalPremiumRequests, /** Internal per-model prompt-cache state used to restore expiration tracking on resume */ - @JsonProperty("modelCacheState") List modelCacheState + @JsonProperty("modelCacheState") List modelCacheState, + /** Internal per-conversation prompt-cache-break detector baselines restored on resume */ + @JsonProperty("promptCacheBreakState") List promptCacheBreakState ) { } } 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/sdk/src/generated/java/com/github/copilot/generated/ShutdownAgentMetric.java b/java/sdk/src/generated/java/com/github/copilot/generated/ShutdownAgentMetric.java new file mode 100644 index 0000000000..a6ed890ba7 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/ShutdownAgentMetric.java @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * 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.Map; +import javax.annotation.processing.Generated; + +/** + * Usage attributed to one agent instance at session shutdown. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record ShutdownAgentMetric( + /** Configured agent name, when this is a subagent */ + @JsonProperty("agentName") String agentName, + /** Human-readable label for this subagent invocation, copied from the originating `subagent.started` event. For task-tool subagents this is the invocation's task description rather than the agent's configured display name, so group by `agentName` for stable per-agent labels. */ + @JsonProperty("agentDisplayName") String agentDisplayName, + /** Time spent in model API calls by this agent, in milliseconds */ + @JsonProperty("totalApiDurationMs") Long totalApiDurationMs, + /** Accumulated nano-AI units cost for this agent */ + @JsonProperty("totalNanoAiu") Double totalNanoAiu, + /** Per-model usage for this agent, keyed by model identifier */ + @JsonProperty("modelMetrics") Map modelMetrics +) { +} 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 88% 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 index 6ad04f9699..e8675a6bbc 100644 --- a/java/src/generated/java/com/github/copilot/generated/SkillInvokedEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SkillInvokedEvent.java @@ -39,13 +39,15 @@ public record SkillInvokedEventData( @JsonProperty("name") String name, /** Model identifier active when the skill was invoked, when known */ @JsonProperty("model") String model, - /** File path to the SKILL.md definition */ + /** File path to the SKILL.md definition, or an empty string for an SDK-provided skill without a filesystem identity */ @JsonProperty("path") String path, /** Full content of the skill file, injected into the conversation for the model */ @JsonProperty("content") String content, /** Tool names that should be auto-approved when this skill is active */ @JsonProperty("allowedTools") List allowedTools, - /** Source identifier for where the skill was discovered. Known values include: project (workspace skill), inherited (parent-directory skill), personal-copilot (~/.copilot/skills), personal-agents (~/.agents/skills), custom (configured directory), plugin (installed plugin), builtin (bundled runtime skill), and remote (org/enterprise skill) */ + /** Whether model invocation is disabled for this skill */ + @JsonProperty("disableModelInvocation") Boolean disableModelInvocation, + /** Source identifier for where the skill was discovered. Known values include: project (workspace skill), inherited (parent-directory skill), personal-copilot (~/.copilot/skills), personal-agents (~/.agents/skills), custom (configured directory), plugin (installed plugin), builtin (bundled runtime skill), remote (org/enterprise skill), and sdk (SDK-provided skill) */ @JsonProperty("source") String source, /** Name of the plugin this skill originated from, when applicable */ @JsonProperty("pluginName") String pluginName, 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 94% 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 index b681faaae8..622dc5d88a 100644 --- a/java/src/generated/java/com/github/copilot/generated/SkillSource.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SkillSource.java @@ -10,7 +10,7 @@ import javax.annotation.processing.Generated; /** - * Source location type (e.g., project, personal-copilot, plugin, builtin) + * Source location type (e.g., project, personal-copilot, plugin, builtin, sdk) * * @since 1.0.0 */ @@ -29,7 +29,9 @@ public enum SkillSource { /** The {@code custom} variant. */ CUSTOM("custom"), /** The {@code builtin} variant. */ - BUILTIN("builtin"); + BUILTIN("builtin"), + /** The {@code sdk} variant. */ + SDK("sdk"); private final String value; SkillSource(String value) { this.value = value; } 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 90% 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 d3196c6bbf..2335a9d9ea 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,9 +23,11 @@ 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) */ + /** Source location type (e.g., project, personal-copilot, plugin, builtin, remote, sdk) */ @JsonProperty("source") SkillSource source, /** Whether the skill can be invoked by the user as a slash command */ @JsonProperty("userInvocable") Boolean userInvocable, 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 60% 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 f32613579d..26459bb955 100644 --- a/java/src/generated/java/com/github/copilot/generated/SubagentCompletedEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SubagentCompletedEvent.java @@ -42,12 +42,26 @@ public record SubagentCompletedEventData( @JsonProperty("agentDisplayName") String agentDisplayName, /** Model used by the sub-agent */ @JsonProperty("model") String model, + /** First model for which the sub-agent started an inference request, when one was dispatched */ + @JsonProperty("firstDispatchedModel") String firstDispatchedModel, + /** Concrete model the user configured for this sub-agent via `/subagents`, when present */ + @JsonProperty("configuredModelPreference") String configuredModelPreference, + /** Explicit model supplied by the parent agent on the task call, when present */ + @JsonProperty("explicitModelOverride") String explicitModelOverride, + /** Whether the explicit task-call model matched the user's configured preference */ + @JsonProperty("explicitModelMatchesPreference") Boolean explicitModelMatchesPreference, + /** Why an explicit task-call model did not become the effective model */ + @JsonProperty("modelOverrideReason") String modelOverrideReason, + /** Whether the first model actually dispatched matched the user's configured preference */ + @JsonProperty("configuredModelMatchesActual") Boolean configuredModelMatchesActual, /** Total number of tool calls made by the sub-agent */ @JsonProperty("totalToolCalls") Long totalToolCalls, /** 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/sdk/src/generated/java/com/github/copilot/generated/SubagentConfiguredEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SubagentConfiguredEvent.java new file mode 100644 index 0000000000..235066fade --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SubagentConfiguredEvent.java @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * 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 "subagent.configured". Resolved runtime configuration for a configured sub-agent + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SubagentConfiguredEvent extends SessionEvent { + + @Override + public String getType() { return "subagent.configured"; } + + @JsonProperty("data") + private SubagentConfiguredEventData data; + + public SubagentConfiguredEventData getData() { return data; } + public void setData(SubagentConfiguredEventData data) { this.data = data; } + + /** Data payload for {@link SubagentConfiguredEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record SubagentConfiguredEventData( + /** Resolved model the sub-agent will run with */ + @JsonProperty("model") String model, + /** Resolved reasoning effort, when configured for the model */ + @JsonProperty("reasoningEffort") String reasoningEffort, + /** Resolved context tier, when configured for the model */ + @JsonProperty("contextTier") String contextTier, + /** Whether the sub-agent accepts follow-up turns */ + @JsonProperty("multiTurn") Boolean multiTurn + ) { + } +} 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 69% 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 index 6a48544ce9..54464f8ddf 100644 --- a/java/src/generated/java/com/github/copilot/generated/SubagentFailedEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SubagentFailedEvent.java @@ -44,6 +44,18 @@ public record SubagentFailedEventData( @JsonProperty("error") String error, /** Model selected for the sub-agent, when known */ @JsonProperty("model") String model, + /** First model for which the sub-agent started an inference request, when one was dispatched */ + @JsonProperty("firstDispatchedModel") String firstDispatchedModel, + /** Concrete model the user configured for this sub-agent via `/subagents`, when present */ + @JsonProperty("configuredModelPreference") String configuredModelPreference, + /** Explicit model supplied by the parent agent on the task call, when present */ + @JsonProperty("explicitModelOverride") String explicitModelOverride, + /** Whether the explicit task-call model matched the user's configured preference */ + @JsonProperty("explicitModelMatchesPreference") Boolean explicitModelMatchesPreference, + /** Why an explicit task-call model did not become the effective model */ + @JsonProperty("modelOverrideReason") String modelOverrideReason, + /** Whether the first model actually dispatched matched the user's configured preference */ + @JsonProperty("configuredModelMatchesActual") Boolean configuredModelMatchesActual, /** Total number of tool calls made before the sub-agent failed */ @JsonProperty("totalToolCalls") Long totalToolCalls, /** Total tokens (input + output) consumed before the sub-agent failed */ 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 72% 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 index fb94f97cfa..c246fac1ea 100644 --- a/java/src/generated/java/com/github/copilot/generated/SubagentStartedEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SubagentStartedEvent.java @@ -43,7 +43,17 @@ public record SubagentStartedEventData( /** Description of what the sub-agent does */ @JsonProperty("agentDescription") String agentDescription, /** Model the sub-agent will run with, when known at start. */ - @JsonProperty("model") String model + @JsonProperty("model") String model, + /** Root id of the factory run that spawned this sub-agent, when it was spawned by one. */ + @JsonProperty("factoryRunId") String factoryRunId, + /** Task-registry ID of the spawning sub-agent. Absent when the root session spawned this child. */ + @JsonProperty("parentId") String parentId, + /** Whether this sub-agent can be resumed. Currently always false. */ + @JsonProperty("resumable") Boolean resumable, + /** Type of the sub-agent selected at spawn time. */ + @JsonProperty("agentType") String agentType, + /** Whether the sub-agent runs synchronously or in the background. */ + @JsonProperty("executionMode") String executionMode ) { } } 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 100% 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 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/src/generated/java/com/github/copilot/generated/TaskCompletionOutcome.java b/java/sdk/src/generated/java/com/github/copilot/generated/TaskCompletionOutcome.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/TaskCompletionOutcome.java rename to java/sdk/src/generated/java/com/github/copilot/generated/TaskCompletionOutcome.java 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 90% 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 a265b5305e..4cb949c80b 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, + /** Per-request treatment/eligibility signal returned by the Copilot API in the `X-GitHub-Copilot-Request-TE` response header for the associated model call; `false` when the header was absent or unparseable. */ @JsonProperty("rte") Boolean rte, /** Whether this tool call was explicitly requested by the user rather than the assistant */ @JsonProperty("isUserRequested") Boolean isUserRequested, @@ -61,7 +62,9 @@ public record ToolExecutionCompleteEventData( /** Whether this tool execution ran inside a sandbox container */ @JsonProperty("sandboxed") Boolean sandboxed, /** Tool call ID of the parent tool invocation when this event originates from a sub-agent */ - @JsonProperty("parentToolCallId") String parentToolCallId + @JsonProperty("parentToolCallId") String parentToolCallId, + /** Experimental HydraFusion attribution for this tool completion. */ + @JsonProperty("fusion") FusionAttribution fusion ) { } } 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 90% 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 index 6679b85aec..500eb17554 100644 --- a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUI.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUI.java @@ -25,7 +25,9 @@ public record ToolExecutionCompleteUIResourceMetaUI( @JsonProperty("csp") ToolExecutionCompleteUIResourceMetaUICsp csp, /** Browser permission metadata for an MCP Apps UI resource, including camera, microphone, geolocation, and clipboard-write. */ @JsonProperty("permissions") ToolExecutionCompleteUIResourceMetaUIPermissions permissions, + /** Optional dedicated origin for the rendered MCP Apps UI resource. */ @JsonProperty("domain") String domain, + /** Whether the host should render a border around the MCP Apps UI resource. */ @JsonProperty("prefersBorder") Boolean prefersBorder ) { } 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 81% 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 index 41e799cf03..4b9cfb133b 100644 --- a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUICsp.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUICsp.java @@ -22,9 +22,13 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record ToolExecutionCompleteUIResourceMetaUICsp( + /** Domains the UI resource may connect to. */ @JsonProperty("connectDomains") List connectDomains, + /** Domains from which the UI resource may load scripts, styles, images, and other resources. */ @JsonProperty("resourceDomains") List resourceDomains, + /** Domains the UI resource may embed as nested frames. */ @JsonProperty("frameDomains") List frameDomains, + /** Domains the UI resource may use as document base URIs. */ @JsonProperty("baseUriDomains") List baseUriDomains ) { } 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 90% 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 782e93931f..36691ca41e 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, + /** Per-request treatment/eligibility signal returned by the Copilot API in the `X-GitHub-Copilot-Request-TE` response header for the associated model call; `false` when the header was absent or unparseable. */ @JsonProperty("rte") Boolean rte, /** Name of the MCP server hosting this tool, when the tool is an MCP tool */ @JsonProperty("mcpServerName") String mcpServerName, @@ -56,7 +57,9 @@ public record ToolExecutionStartEventData( /** Tool definition metadata, present for MCP tools with MCP Apps support */ @JsonProperty("toolDescription") ToolExecutionStartToolDescription toolDescription, /** Tool call ID of the parent tool invocation when this event originates from a sub-agent */ - @JsonProperty("parentToolCallId") String parentToolCallId + @JsonProperty("parentToolCallId") String parentToolCallId, + /** Experimental HydraFusion attribution for this tool execution. */ + @JsonProperty("fusion") FusionAttribution fusion ) { } } 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 100% 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 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/sdk/src/generated/java/com/github/copilot/generated/UIEphemeralQueryPhase.java b/java/sdk/src/generated/java/com/github/copilot/generated/UIEphemeralQueryPhase.java new file mode 100644 index 0000000000..7798c159eb --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/UIEphemeralQueryPhase.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; + +/** + * Lifecycle phase for a Rust-owned ephemeral query stream. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum UIEphemeralQueryPhase { + /** The {@code started} variant. */ + STARTED("started"), + /** The {@code chunk} variant. */ + CHUNK("chunk"), + /** The {@code completed} variant. */ + COMPLETED("completed"), + /** The {@code failed} variant. */ + FAILED("failed"), + /** The {@code aborted} variant. */ + ABORTED("aborted"); + + private final String value; + UIEphemeralQueryPhase(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static UIEphemeralQueryPhase fromValue(String value) { + for (UIEphemeralQueryPhase v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown UIEphemeralQueryPhase value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/UiEphemeralQueryEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/UiEphemeralQueryEvent.java new file mode 100644 index 0000000000..b2f390e823 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/UiEphemeralQueryEvent.java @@ -0,0 +1,49 @@ +/*--------------------------------------------------------------------------------------------- + * 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 "ui.ephemeral_query". Ordered output and terminal state for a transient query that does not modify conversation history. + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class UiEphemeralQueryEvent extends SessionEvent { + + @Override + public String getType() { return "ui.ephemeral_query"; } + + @JsonProperty("data") + private UiEphemeralQueryEventData data; + + public UiEphemeralQueryEventData getData() { return data; } + public void setData(UiEphemeralQueryEventData data) { this.data = data; } + + /** Data payload for {@link UiEphemeralQueryEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record UiEphemeralQueryEventData( + /** Runtime-minted query identifier. */ + @JsonProperty("requestId") String requestId, + /** Current query lifecycle phase. */ + @JsonProperty("phase") UIEphemeralQueryPhase phase, + /** Ordered text delta, present for the `chunk` phase. */ + @JsonProperty("chunk") String chunk, + /** Full response text, present for the `completed` phase. */ + @JsonProperty("answer") String answer, + /** Model or transport failure message, present for the `failed` phase. */ + @JsonProperty("error") String error + ) { + } +} 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 91% 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 bc579968b3..43a886ea01 100644 --- a/java/src/generated/java/com/github/copilot/generated/UserMessageEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/UserMessageEvent.java @@ -37,6 +37,8 @@ public final class UserMessageEvent extends SessionEvent { public record UserMessageEventData( /** The user's message text as displayed in the timeline */ @JsonProperty("content") String content, + /** Stable identity of the logical user message, matching the ID returned by send and retained by pending queue snapshots */ + @JsonProperty("messageId") String messageId, /** Transformed version of the message sent to the model, with XML wrapping, timestamps, and other augmentations for prompt caching */ @JsonProperty("transformedContent") String transformedContent, /** Files, selections, or GitHub references attached to the message */ @@ -55,6 +57,8 @@ public record UserMessageEventData( @JsonProperty("isAutopilotContinuation") Boolean isAutopilotContinuation, /** CAPI interaction ID for correlating this user message with its turn */ @JsonProperty("interactionId") String interactionId, + /** The agent-loop turn ID that consumed this message; absent when no agent-loop turn consumed it */ + @JsonProperty("turnId") String turnId, /** Parent agent task ID for background telemetry correlated to this user turn */ @JsonProperty("parentAgentTaskId") String parentAgentTaskId ) { 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 100% 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 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 100% 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 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 89% 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 index eecdad01ee..3f93dd7c60 100644 --- 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 @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Authenticated account entry returned by `account.getAllUsers`, with auth info and an optional associated token. + * Authenticated account entry returned by `account.getAllUsers`. * * @since 1.0.0 */ @@ -23,6 +23,8 @@ public record AccountAllUsers( /** Authentication information for this user */ @JsonProperty("authInfo") Object authInfo, + /** Opaque identifier accepted by account and model selection APIs */ + @JsonProperty("selectionId") String selectionId, /** Associated token, if available */ @JsonProperty("token") String token ) { 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/src/generated/java/com/github/copilot/generated/rpc/AccountGetQuotaParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountGetQuotaParams.java similarity index 77% rename from java/src/generated/java/com/github/copilot/generated/rpc/AccountGetQuotaParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountGetQuotaParams.java index cea523551f..9863b5d9c9 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/AccountGetQuotaParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountGetQuotaParams.java @@ -24,7 +24,9 @@ @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. */ + /** Opaque account identifier returned by `account.getAllUsers`. When omitted, the current account is used. */ + @JsonProperty("selectionId") String selectionId, + /** GitHub token accepted for compatibility with existing SDK clients. When provided, resolves this token instead of using the current account. */ @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 84% 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 index bd8e697341..ca724fc675 100644 --- 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 @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Credentials to store after successful authentication + * Credentials to validate and store. Omit login to resolve the authenticated user from the token. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 @@ -26,7 +26,7 @@ public record AccountLoginParams( /** GitHub host URL */ @JsonProperty("host") String host, - /** User login/username */ + /** User login/username. When omitted, the runtime validates the token and resolves the login from GitHub. */ @JsonProperty("login") String login, /** GitHub authentication token */ @JsonProperty("token") String token 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 89% 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 index b5e93e1859..b57aef87c4 100644 --- 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 @@ -24,6 +24,8 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record AccountLogoutParams( + /** Opaque account identifier returned by `account.getAllUsers` */ + @JsonProperty("selectionId") String selectionId, /** Authentication information for the user to log out */ @JsonProperty("authInfo") Object authInfo ) { 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 86% 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 f239c82e61..3d9f9c2d7e 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; /** - * Agent metadata, including identifiers, display details, source, tools, model, MCP servers, skills, and file path. + * Agent metadata, including identifiers, display details, source, tools, model, models, MCP servers, skills, and file path. * * @since 1.0.0 */ @@ -41,6 +41,10 @@ public record AgentInfo( @JsonProperty("tools") List tools, /** Authored preferred model id for this agent. Runtime model selection may choose a different model; omitted means no authored preference. */ @JsonProperty("model") String model, + /** Authored preferred model ids for this agent, in priority order. Runtime model selection chooses the first available model; omitted means no authored preference. */ + @JsonProperty("models") List models, + /** Whether authored models are preferences or required constraints. */ + @JsonProperty("modelPolicy") AgentModelPolicy modelPolicy, /** 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. */ 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/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentModelPolicy.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentModelPolicy.java new file mode 100644 index 0000000000..99158516f0 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentModelPolicy.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; + +/** + * Whether configured models are advisory preferences or required constraints + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum AgentModelPolicy { + /** The {@code preferred} variant. */ + PREFERRED("preferred"), + /** The {@code required} variant. */ + REQUIRED("required"); + + private final String value; + AgentModelPolicy(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static AgentModelPolicy fromValue(String value) { + for (AgentModelPolicy v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown AgentModelPolicy value: " + value); + } +} 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 97% 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 index 3e44993b96..c9e2ed2320 100644 --- 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 @@ -31,8 +31,6 @@ public record AgentRegistryLiveTargetEntry( @JsonProperty("host") String host, /** TCP port the entry's JSON-RPC server is listening on */ @JsonProperty("port") Long port, - /** Connection token (null when the target is unauthenticated) */ - @JsonProperty("token") String token, /** Session ID of the foreground session for this entry */ @JsonProperty("sessionId") String sessionId, /** Friendly session name (when set) */ @@ -56,6 +54,8 @@ public record AgentRegistryLiveTargetEntry( /** Copilot CLI version that wrote the entry */ @JsonProperty("copilotVersion") String copilotVersion, /** Wall-clock milliseconds since the watcher last observed this entry (heartbeat freshness) */ - @JsonProperty("lastSeenMs") Long lastSeenMs + @JsonProperty("lastSeenMs") Long lastSeenMs, + /** Connection token (null when the target is unauthenticated) */ + @JsonProperty("token") String token ) { } 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/sdk/src/generated/java/com/github/copilot/generated/rpc/ApiKeyAuthInfo.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ApiKeyAuthInfo.java new file mode 100644 index 0000000000..fecbfff415 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ApiKeyAuthInfo.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; + +/** + * Authentication-info input variant for API-key authentication to a non-GitHub LLM provider, carrying the secret `apiKey` and host. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class ApiKeyAuthInfo extends AuthInfo { + + @JsonProperty("type") + private final String type = "api-key"; + + @Override + public String getType() { return type; } + + /** The API key. Treat as a secret. */ + @JsonProperty("apiKey") + private String apiKey; + + /** Authentication host. */ + @JsonProperty("host") + private String host; + + /** 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. */ + @JsonProperty("copilotUser") + private CopilotUserResponse copilotUser; + + public String getApiKey() { return apiKey; } + public void setApiKey(String apiKey) { this.apiKey = apiKey; } + + public String getHost() { return host; } + public void setHost(String host) { this.host = host; } + + public CopilotUserResponse getCopilotUser() { return copilotUser; } + public void setCopilotUser(CopilotUserResponse copilotUser) { this.copilotUser = copilotUser; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AuthInfo.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AuthInfo.java new file mode 100644 index 0000000000..5d82a47f85 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AuthInfo.java @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * Authentication credentials accepted only at native protocol ingress. Runtime outputs use credential-free `AuthIdentity` metadata. + * + * @since 1.0.0 + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", visible = true) +@JsonSubTypes({ + @JsonSubTypes.Type(value = HMACAuthInfo.class, name = "hmac"), + @JsonSubTypes.Type(value = EnvAuthInfo.class, name = "env"), + @JsonSubTypes.Type(value = TokenAuthInfo.class, name = "token"), + @JsonSubTypes.Type(value = TokenProviderAuthInfo.class, name = "token-provider"), + @JsonSubTypes.Type(value = CopilotApiTokenAuthInfo.class, name = "copilot-api-token"), + @JsonSubTypes.Type(value = UserAuthInfo.class, name = "user"), + @JsonSubTypes.Type(value = GhCliAuthInfo.class, name = "gh-cli"), + @JsonSubTypes.Type(value = ApiKeyAuthInfo.class, name = "api-key") +}) +@JsonIgnoreProperties(ignoreUnknown = true) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public abstract class AuthInfo { + + /** + * Returns the discriminator value for this variant. + * + * @return the type discriminator + */ + public abstract String getType(); +} 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 94% 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 index 1fb4b43ba4..5f81c8cf84 100644 --- 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 @@ -28,6 +28,8 @@ public enum AuthInfoType { API_KEY("api-key"), /** The {@code token} variant. */ TOKEN("token"), + /** The {@code token-provider} variant. */ + TOKEN_PROVIDER("token-provider"), /** The {@code copilot-api-token} variant. */ COPILOT_API_TOKEN("copilot-api-token"); diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AuthValidationError.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AuthValidationError.java new file mode 100644 index 0000000000..99f78006fd --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AuthValidationError.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; + +/** + * Validation error from an authentication attempt. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record AuthValidationError( + /** Authentication validation error message */ + @JsonProperty("message") String message, + /** Optional message returned by GitHub */ + @JsonProperty("githubMessage") String gitHubMessage +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AutoTier.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AutoTier.java new file mode 100644 index 0000000000..a4433e1ea9 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AutoTier.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; + +/** + * Routing preference used when the session model is `auto`. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum AutoTier { + /** The {@code efficiency} variant. */ + EFFICIENCY("efficiency"), + /** The {@code balance} variant. */ + BALANCE("balance"), + /** The {@code intelligence} variant. */ + INTELLIGENCE("intelligence"); + + private final String value; + AutoTier(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static AutoTier fromValue(String value) { + for (AutoTier v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown AutoTier value: " + value); + } +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/BuiltInModelCatalogEntry.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/BuiltInModelCatalogEntry.java similarity index 78% rename from java/src/generated/java/com/github/copilot/generated/rpc/BuiltInModelCatalogEntry.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/BuiltInModelCatalogEntry.java index 679278356e..a3f751b418 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/BuiltInModelCatalogEntry.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/BuiltInModelCatalogEntry.java @@ -21,7 +21,7 @@ @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. */ + /** Well-known runtime model ID suitable for provider or provider-model metadata. 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/sdk/src/generated/java/com/github/copilot/generated/rpc/BuiltinToolDescriptor.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/BuiltinToolDescriptor.java new file mode 100644 index 0000000000..057498fb98 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/BuiltinToolDescriptor.java @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * Rust-owned metadata and input schema for a built-in tool. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record BuiltinToolDescriptor( + /** Stable name used to invoke the built-in tool. */ + @JsonProperty("name") String name, + /** Optional human-readable title for the tool. */ + @JsonProperty("title") String title, + /** Model-facing description of the tool's behavior. */ + @JsonProperty("description") String description, + /** JSON Schema for the tool input, or null when the tool uses a custom format. */ + @JsonProperty("inputSchema") BuiltinToolInputSchema inputSchema, + /** Optional supplemental usage instructions for the tool. */ + @JsonProperty("instructions") String instructions, + /** Optional tool category discriminator. */ + @JsonProperty("type") String type, + /** Optional custom input format used instead of a JSON Schema. */ + @JsonProperty("format") BuiltinToolFormat format, + /** Policy describing which tool metadata may be recorded without obfuscation. */ + @JsonProperty("safeForTelemetry") Object safeForTelemetry, + /** Whether the tool executes commands in a terminal. */ + @JsonProperty("isTerminal") Boolean isTerminal, + /** Whether the tool provides a specialized intention summary. */ + @JsonProperty("hasSummariseIntention") Boolean hasSummariseIntention +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/BuiltinToolFormat.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/BuiltinToolFormat.java new file mode 100644 index 0000000000..7af5ef2d45 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/BuiltinToolFormat.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; + +/** + * Custom grammar input format accepted by a built-in tool. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record BuiltinToolFormat( + /** Custom input-format discriminator. */ + @JsonProperty("type") BuiltinToolFormatType type, + /** Grammar syntax used by the format definition. */ + @JsonProperty("syntax") String syntax, + /** Grammar definition accepted by the tool. */ + @JsonProperty("definition") String definition +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/BuiltinToolFormatType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/BuiltinToolFormatType.java new file mode 100644 index 0000000000..6239493671 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/BuiltinToolFormatType.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 javax.annotation.processing.Generated; + +/** + * Custom input-format kind. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum BuiltinToolFormatType { + /** The {@code grammar} variant. */ + GRAMMAR("grammar"); + + private final String value; + BuiltinToolFormatType(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static BuiltinToolFormatType fromValue(String value) { + for (BuiltinToolFormatType v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown BuiltinToolFormatType value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/BuiltinToolInputSchema.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/BuiltinToolInputSchema.java new file mode 100644 index 0000000000..764450b37e --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/BuiltinToolInputSchema.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; + +/** + * JSON Schema object accepted by a built-in tool. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record BuiltinToolInputSchema( + /** Root type of the tool input schema. */ + @JsonProperty("type") BuiltinToolInputSchemaType type +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/BuiltinToolInputSchemaType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/BuiltinToolInputSchemaType.java new file mode 100644 index 0000000000..729f3329b5 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/BuiltinToolInputSchemaType.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 javax.annotation.processing.Generated; + +/** + * Root JSON Schema type for a built-in tool input. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum BuiltinToolInputSchemaType { + /** The {@code object} variant. */ + OBJECT("object"); + + private final String value; + BuiltinToolInputSchemaType(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static BuiltinToolInputSchemaType fromValue(String value) { + for (BuiltinToolInputSchemaType v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown BuiltinToolInputSchemaType value: " + value); + } +} 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 81% 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 index 27fd29128d..e77117b2cb 100644 --- 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 @@ -21,6 +21,8 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record CapiSessionOptions( + /** Routing preference used when the session model is `auto`. The runtime persists the preference across cold resume. When omitted, the default routing behavior is used. Resuming an already-resident session cannot change its preference. */ + @JsonProperty("autoTier") AutoTier autoTier, /** 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. */ @JsonProperty("enableWebSocketResponses") Boolean enableWebSocketResponses ) { diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CardDigest.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CardDigest.java new file mode 100644 index 0000000000..f14f9abdfc --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CardDigest.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; + +/** + * Semantic digest of a strictly parsed and schema-validated JSON MCP card. Both URL-backed and embedded cards are canonicalised with RFC 8785 JSON Canonicalization Scheme, encoded as UTF-8, and hashed with SHA-256. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record CardDigest( + /** Digest algorithm and canonical representation */ + @JsonProperty("algorithm") CardDigestAlgorithm algorithm, + /** SHA-256 digest of the RFC 8785 canonical UTF-8 bytes, encoded as exactly 64 lowercase hexadecimal characters. */ + @JsonProperty("value") String value +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CardDigestAlgorithm.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CardDigestAlgorithm.java new file mode 100644 index 0000000000..83a691705b --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CardDigestAlgorithm.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 javax.annotation.processing.Generated; + +/** + * Canonical digest algorithm for a validated MCP card + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum CardDigestAlgorithm { + /** The {@code sha256-rfc8785} variant. */ + SHA256_RFC8785("sha256-rfc8785"); + + private final String value; + CardDigestAlgorithm(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static CardDigestAlgorithm fromValue(String value) { + for (CardDigestAlgorithm v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown CardDigestAlgorithm value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAuthenticationRequiredError.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAuthenticationRequiredError.java new file mode 100644 index 0000000000..3433f0e977 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAuthenticationRequiredError.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; + +/** + * An optional catalog authentication exchange did not establish the caller's identity. Anonymous search remains supported; this refusal is reserved for an operation that cannot continue after the attempted exchange. It is distinct from `policy-rejected` and from a network failure, and the reason identifies the recovery action. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class CatalogAuthenticationRequiredError extends CatalogSearchResult { + + @JsonProperty("kind") + private final String kind = "authentication-required"; + + @Override + public String getKind() { return kind; } + + /** Why authentication failed. Only an expired credential justifies attempting a silent refresh; an absent or rejected credential requires sign-in. */ + @JsonProperty("reason") + private CatalogAuthenticationRequiredReason reason; + + /** Human-readable explanation, safe to surface. Never contains a credential or token, nor a query, URL, handle, or secret. */ + @JsonProperty("message") + private String message; + + public CatalogAuthenticationRequiredReason getReason() { return reason; } + public void setReason(CatalogAuthenticationRequiredReason reason) { this.reason = reason; } + + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAuthenticationRequiredReason.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAuthenticationRequiredReason.java new file mode 100644 index 0000000000..1c87593273 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogAuthenticationRequiredReason.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; + +/** + * Why the catalog authority did not accept the caller's identity + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum CatalogAuthenticationRequiredReason { + /** The {@code no-credential} variant. */ + NO_CREDENTIAL("no-credential"), + /** The {@code credential-expired} variant. */ + CREDENTIAL_EXPIRED("credential-expired"), + /** The {@code credential-rejected} variant. */ + CREDENTIAL_REJECTED("credential-rejected"); + + private final String value; + CatalogAuthenticationRequiredReason(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static CatalogAuthenticationRequiredReason fromValue(String value) { + for (CatalogAuthenticationRequiredReason v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown CatalogAuthenticationRequiredReason value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogCandidateKind.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogCandidateKind.java new file mode 100644 index 0000000000..e7316cf872 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogCandidateKind.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; + +/** + * What kind of resource a catalog candidate describes + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum CatalogCandidateKind { + /** The {@code mcp-server} variant. */ + MCP_SERVER("mcp-server"), + /** The {@code ai-skill} variant. */ + AI_SKILL("ai-skill"); + + private final String value; + CatalogCandidateKind(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static CatalogCandidateKind fromValue(String value) { + for (CatalogCandidateKind v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown CatalogCandidateKind value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogCapability.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogCapability.java new file mode 100644 index 0000000000..bb1662ba27 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogCapability.java @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * A wire feature a caller can require of the catalog surface, negotiated per request. A grant means the runtime understands the feature's contract, not that the deployment has enabled the operation; typed unavailable results report availability separately. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum CatalogCapability { + /** The {@code mcp-server-card} variant. */ + MCP_SERVER_CARD("mcp-server-card"), + /** The {@code legacy-mcp-server-card} variant. */ + LEGACY_MCP_SERVER_CARD("legacy-mcp-server-card"), + /** The {@code ai-skill-discovery} variant. */ + AI_SKILL_DISCOVERY("ai-skill-discovery"), + /** The {@code mcp-install-planning} variant. */ + MCP_INSTALL_PLANNING("mcp-install-planning"), + /** The {@code multiple-transport-choice} variant. */ + MULTIPLE_TRANSPORT_CHOICE("multiple-transport-choice"); + + private final String value; + CatalogCapability(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static CatalogCapability fromValue(String value) { + for (CatalogCapability v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown CatalogCapability value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogClientContract.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogClientContract.java new file mode 100644 index 0000000000..62cf8db22c --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogClientContract.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; + +/** + * The protocol version and capability set a caller requires, supplied on every catalog request so negotiation cannot be skipped by omission. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record CatalogClientContract( + /** SDK protocol version the caller was generated against. A caller below the runtime's minimum supported version is refused rather than served a partial result. */ + @JsonProperty("protocolVersion") Long protocolVersion, + /** Wire features the caller requires the runtime to understand. Identifiers are bounded but extensible so a newer caller can negotiate with an older runtime. Requiring an unknown feature yields a typed refusal listing what is understood, never a partial grant. A grant does not promise that a deployment has enabled the operation; typed unavailable results report that separately. */ + @JsonProperty("requiredCapabilities") List requiredCapabilities +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogContractViolationError.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogContractViolationError.java new file mode 100644 index 0000000000..5951d75a5e --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogContractViolationError.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; + +/** + * An upstream catalog response broke the wire contract. Most importantly, every result must carry exactly one of a URL or embedded data: a result carrying both, or neither, is refused here rather than being guessed at. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class CatalogContractViolationError extends CatalogSearchResult { + + @JsonProperty("kind") + private final String kind = "contract-violation"; + + @Override + public String getKind() { return kind; } + + /** Which rule the response broke. */ + @JsonProperty("reason") + private CatalogContractViolationReason reason; + + /** Human-readable explanation, safe to surface. Never echoes response content, nor a query, URL, handle, or secret. */ + @JsonProperty("message") + private String message; + + public CatalogContractViolationReason getReason() { return reason; } + public void setReason(CatalogContractViolationReason reason) { this.reason = reason; } + + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogContractViolationReason.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogContractViolationReason.java new file mode 100644 index 0000000000..f289d67666 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogContractViolationReason.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; + +/** + * Which wire-contract rule an upstream response broke + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum CatalogContractViolationReason { + /** The {@code both-url-and-data} variant. */ + BOTH_URL_AND_DATA("both-url-and-data"), + /** The {@code neither-url-nor-data} variant. */ + NEITHER_URL_NOR_DATA("neither-url-nor-data"), + /** The {@code duplicate-identity} variant. */ + DUPLICATE_IDENTITY("duplicate-identity"), + /** The {@code unknown-media-type} variant. */ + UNKNOWN_MEDIA_TYPE("unknown-media-type"); + + private final String value; + CatalogContractViolationReason(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static CatalogContractViolationReason fromValue(String value) { + for (CatalogContractViolationReason v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown CatalogContractViolationReason value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogHandleRejectedError.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogHandleRejectedError.java new file mode 100644 index 0000000000..7469d6194d --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogHandleRejectedError.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; + +/** + * A presented handle was not accepted. Handles are runtime-instance scoped, TTL-bound, and single-use, so each way of failing is reported distinctly. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class CatalogHandleRejectedError extends McpPlanInstallResult { + + @JsonProperty("kind") + private final String kind = "handle-rejected"; + + @Override + public String getKind() { return kind; } + + /** Which kind of handle was presented. */ + @JsonProperty("handleType") + private CatalogHandleType handleType; + + /** Why the handle was rejected. */ + @JsonProperty("reason") + private CatalogHandleRejectionReason reason; + + /** Human-readable explanation, safe to surface. Never contains the handle itself, nor a query, URL, or secret. */ + @JsonProperty("message") + private String message; + + public CatalogHandleType getHandleType() { return handleType; } + public void setHandleType(CatalogHandleType handleType) { this.handleType = handleType; } + + public CatalogHandleRejectionReason getReason() { return reason; } + public void setReason(CatalogHandleRejectionReason reason) { this.reason = reason; } + + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogHandleRejectionReason.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogHandleRejectionReason.java new file mode 100644 index 0000000000..6db6b69365 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogHandleRejectionReason.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; + +/** + * Why a presented handle was rejected + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum CatalogHandleRejectionReason { + /** The {@code invalid} variant. */ + INVALID("invalid"), + /** The {@code stale} variant. */ + STALE("stale"), + /** The {@code replayed} variant. */ + REPLAYED("replayed"), + /** The {@code foreign} variant. */ + FOREIGN("foreign"); + + private final String value; + CatalogHandleRejectionReason(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static CatalogHandleRejectionReason fromValue(String value) { + for (CatalogHandleRejectionReason v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown CatalogHandleRejectionReason value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogHandleType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogHandleType.java new file mode 100644 index 0000000000..a14ceef85b --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogHandleType.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; + +/** + * Which kind of opaque handle was presented + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum CatalogHandleType { + /** The {@code candidate} variant. */ + CANDIDATE("candidate"), + /** The {@code plan} variant. */ + PLAN("plan"); + + private final String value; + CatalogHandleType(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static CatalogHandleType fromValue(String value) { + for (CatalogHandleType v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown CatalogHandleType value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogInvalidRequestError.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogInvalidRequestError.java new file mode 100644 index 0000000000..0f9eb7e769 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogInvalidRequestError.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; + +/** + * The request was rejected before any work was done, because a bounded field fell outside its permitted range or a required field was unusable. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class CatalogInvalidRequestError extends CatalogSearchResult { + + @JsonProperty("kind") + private final String kind = "invalid-request"; + + @Override + public String getKind() { return kind; } + + /** Which request field was rejected. */ + @JsonProperty("field") + private CatalogInvalidRequestField field; + + /** Human-readable explanation, safe to surface. Never echoes the offending value, nor a query, URL, handle, or secret. */ + @JsonProperty("message") + private String message; + + public CatalogInvalidRequestField getField() { return field; } + public void setField(CatalogInvalidRequestField field) { this.field = field; } + + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogInvalidRequestField.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogInvalidRequestField.java new file mode 100644 index 0000000000..b930640163 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogInvalidRequestField.java @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * 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 request field was rejected before any work was done + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum CatalogInvalidRequestField { + /** The {@code query} variant. */ + QUERY("query"), + /** The {@code limit} variant. */ + LIMIT("limit"), + /** The {@code kinds} variant. */ + KINDS("kinds"), + /** The {@code contract} variant. */ + CONTRACT("contract"), + /** The {@code source} variant. */ + SOURCE("source"), + /** The {@code card} variant. */ + CARD("card"), + /** The {@code scope} variant. */ + SCOPE("scope"); + + private final String value; + CatalogInvalidRequestField(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static CatalogInvalidRequestField fromValue(String value) { + for (CatalogInvalidRequestField v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown CatalogInvalidRequestField value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogMalformedCardError.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogMalformedCardError.java new file mode 100644 index 0000000000..24cb5cae13 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogMalformedCardError.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; + +/** + * A card could not be parsed or did not satisfy its declared media type's schema. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class CatalogMalformedCardError extends CatalogSearchResult { + + @JsonProperty("kind") + private final String kind = "malformed-card"; + + @Override + public String getKind() { return kind; } + + /** How the card failed validation. */ + @JsonProperty("reason") + private CatalogMalformedCardReason reason; + + /** Media type the card was interpreted as, when it declared one this runtime recognises. */ + @JsonProperty("mediaType") + private CatalogMediaType mediaType; + + /** Human-readable explanation, safe to surface. Never echoes card content, nor a query, URL, handle, or secret. */ + @JsonProperty("message") + private String message; + + public CatalogMalformedCardReason getReason() { return reason; } + public void setReason(CatalogMalformedCardReason reason) { this.reason = reason; } + + public CatalogMediaType getMediaType() { return mediaType; } + public void setMediaType(CatalogMediaType mediaType) { this.mediaType = mediaType; } + + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogMalformedCardReason.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogMalformedCardReason.java new file mode 100644 index 0000000000..17c1a05407 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogMalformedCardReason.java @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * 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 a card failed validation + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum CatalogMalformedCardReason { + /** The {@code invalid-json} variant. */ + INVALID_JSON("invalid-json"), + /** The {@code schema-violation} variant. */ + SCHEMA_VIOLATION("schema-violation"), + /** The {@code unsupported-media-type} variant. */ + UNSUPPORTED_MEDIA_TYPE("unsupported-media-type"), + /** The {@code missing-required-field} variant. */ + MISSING_REQUIRED_FIELD("missing-required-field"), + /** The {@code size-limit-exceeded} variant. */ + SIZE_LIMIT_EXCEEDED("size-limit-exceeded"); + + private final String value; + CatalogMalformedCardReason(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static CatalogMalformedCardReason fromValue(String value) { + for (CatalogMalformedCardReason v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown CatalogMalformedCardReason value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogMediaType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogMediaType.java new file mode 100644 index 0000000000..1528b052ee --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogMediaType.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; + +/** + * Media type a catalog card is interpreted as + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum CatalogMediaType { + /** The {@code application/mcp-server-card+json} variant. */ + APPLICATION_MCP_SERVER_CARD_JSON("application/mcp-server-card+json"), + /** The {@code application/mcp-server+json} variant. */ + APPLICATION_MCP_SERVER_JSON("application/mcp-server+json"), + /** The {@code application/ai-skill} variant. */ + APPLICATION_AI_SKILL("application/ai-skill"); + + private final String value; + CatalogMediaType(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static CatalogMediaType fromValue(String value) { + for (CatalogMediaType v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown CatalogMediaType value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogNegotiatedContract.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogNegotiatedContract.java new file mode 100644 index 0000000000..379525fc28 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogNegotiatedContract.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; + +/** + * The protocol version and capability set the runtime actually honoured for a successful catalog operation. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record CatalogNegotiatedContract( + /** Protocol version of the runtime that served the request. */ + @JsonProperty("runtimeProtocolVersion") Long runtimeProtocolVersion, + /** Wire features the runtime understood for this operation. Always a superset of the caller's required features, because any shortfall is a refusal instead. Operation availability remains a separate typed result. */ + @JsonProperty("grantedCapabilities") List grantedCapabilities +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogNegotiationRefusedError.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogNegotiationRefusedError.java new file mode 100644 index 0000000000..56e20d3b4e --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogNegotiationRefusedError.java @@ -0,0 +1,73 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * The caller's protocol version or required capabilities cannot be honoured. Returned instead of a partial or ambiguous success. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class CatalogNegotiationRefusedError extends CatalogSearchResult { + + @JsonProperty("kind") + private final String kind = "negotiation-refused"; + + @Override + public String getKind() { return kind; } + + /** Whether the version or the capability set was the problem. */ + @JsonProperty("reason") + private CatalogNegotiationRefusedReason reason; + + /** Protocol version of the runtime that refused the request. */ + @JsonProperty("runtimeProtocolVersion") + private Long runtimeProtocolVersion; + + /** Lowest caller protocol version this runtime will serve. */ + @JsonProperty("minimumSupportedProtocolVersion") + private Long minimumSupportedProtocolVersion; + + /** Every wire feature this runtime understands, so the caller can retry within that contract. This list does not imply that every deployment has enabled every operation. */ + @JsonProperty("supportedCapabilities") + private List supportedCapabilities; + + /** The subset of the caller's bounded extensible capability identifiers this runtime cannot honour. */ + @JsonProperty("unsupportedCapabilities") + private List unsupportedCapabilities; + + /** Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. */ + @JsonProperty("message") + private String message; + + public CatalogNegotiationRefusedReason getReason() { return reason; } + public void setReason(CatalogNegotiationRefusedReason reason) { this.reason = reason; } + + public Long getRuntimeProtocolVersion() { return runtimeProtocolVersion; } + public void setRuntimeProtocolVersion(Long runtimeProtocolVersion) { this.runtimeProtocolVersion = runtimeProtocolVersion; } + + public Long getMinimumSupportedProtocolVersion() { return minimumSupportedProtocolVersion; } + public void setMinimumSupportedProtocolVersion(Long minimumSupportedProtocolVersion) { this.minimumSupportedProtocolVersion = minimumSupportedProtocolVersion; } + + public List getSupportedCapabilities() { return supportedCapabilities; } + public void setSupportedCapabilities(List supportedCapabilities) { this.supportedCapabilities = supportedCapabilities; } + + public List getUnsupportedCapabilities() { return unsupportedCapabilities; } + public void setUnsupportedCapabilities(List unsupportedCapabilities) { this.unsupportedCapabilities = unsupportedCapabilities; } + + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogNegotiationRefusedReason.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogNegotiationRefusedReason.java new file mode 100644 index 0000000000..ee86246b4a --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogNegotiationRefusedReason.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; + +/** + * Why capability and protocol-version negotiation refused a caller + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum CatalogNegotiationRefusedReason { + /** The {@code unsupported-protocol-version} variant. */ + UNSUPPORTED_PROTOCOL_VERSION("unsupported-protocol-version"), + /** The {@code unsupported-capability} variant. */ + UNSUPPORTED_CAPABILITY("unsupported-capability"); + + private final String value; + CatalogNegotiationRefusedReason(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static CatalogNegotiationRefusedReason fromValue(String value) { + for (CatalogNegotiationRefusedReason v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown CatalogNegotiationRefusedReason value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogNetworkFailureError.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogNetworkFailureError.java new file mode 100644 index 0000000000..01d92e908a --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogNetworkFailureError.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; + +/** + * The runtime could not reach the catalog authority or retrieve a card. Covers being offline as well as transport-level failure. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class CatalogNetworkFailureError extends CatalogSearchResult { + + @JsonProperty("kind") + private final String kind = "network-failure"; + + @Override + public String getKind() { return kind; } + + /** Categorised failure, low cardinality so it can be aggregated without carrying a URL. */ + @JsonProperty("reason") + private CatalogNetworkFailureReason reason; + + /** HTTP status code, when the failure was a rejected response. */ + @JsonProperty("statusCode") + private Long statusCode; + + /** Bounded cooldown in seconds before another catalog request should be attempted, when the authority supplied a numeric Retry-After value or the runtime applied its documented fallback. */ + @JsonProperty("retryAfterSeconds") + private Long retryAfterSeconds; + + /** Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. */ + @JsonProperty("message") + private String message; + + public CatalogNetworkFailureReason getReason() { return reason; } + public void setReason(CatalogNetworkFailureReason reason) { this.reason = reason; } + + public Long getStatusCode() { return statusCode; } + public void setStatusCode(Long statusCode) { this.statusCode = statusCode; } + + public Long getRetryAfterSeconds() { return retryAfterSeconds; } + public void setRetryAfterSeconds(Long retryAfterSeconds) { this.retryAfterSeconds = retryAfterSeconds; } + + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogNetworkFailureReason.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogNetworkFailureReason.java new file mode 100644 index 0000000000..ac821b062d --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogNetworkFailureReason.java @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * Categorised network failure, low cardinality so it can be aggregated without carrying a URL + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum CatalogNetworkFailureReason { + /** The {@code offline} variant. */ + OFFLINE("offline"), + /** The {@code dns} variant. */ + DNS("dns"), + /** The {@code timeout} variant. */ + TIMEOUT("timeout"), + /** The {@code tls} variant. */ + TLS("tls"), + /** The {@code connection-refused} variant. */ + CONNECTION_REFUSED("connection-refused"), + /** The {@code proxy-authentication-required} variant. */ + PROXY_AUTHENTICATION_REQUIRED("proxy-authentication-required"), + /** The {@code rate-limited} variant. */ + RATE_LIMITED("rate-limited"), + /** The {@code service-unavailable} variant. */ + SERVICE_UNAVAILABLE("service-unavailable"), + /** The {@code http-status} variant. */ + HTTP_STATUS("http-status"), + /** The {@code response-too-large} variant. */ + RESPONSE_TOO_LARGE("response-too-large"), + /** The {@code redirect-rejected} variant. */ + REDIRECT_REJECTED("redirect-rejected"); + + private final String value; + CatalogNetworkFailureReason(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static CatalogNetworkFailureReason fromValue(String value) { + for (CatalogNetworkFailureReason v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown CatalogNetworkFailureReason value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogNotInstallableError.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogNotInstallableError.java new file mode 100644 index 0000000000..2ba3c84ec4 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogNotInstallableError.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; + +/** + * The candidate is discoverable but cannot be installed. `application/ai-skill` resolves here, because it stays searchable while remaining typed non-installable. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class CatalogNotInstallableError extends McpPlanInstallResult { + + @JsonProperty("kind") + private final String kind = "not-installable"; + + @Override + public String getKind() { return kind; } + + /** Why the candidate cannot be installed. */ + @JsonProperty("reason") + private CatalogNotInstallableReason reason; + + /** Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. */ + @JsonProperty("message") + private String message; + + public CatalogNotInstallableReason getReason() { return reason; } + public void setReason(CatalogNotInstallableReason reason) { this.reason = reason; } + + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogNotInstallableReason.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogNotInstallableReason.java new file mode 100644 index 0000000000..75abb619e2 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogNotInstallableReason.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; + +/** + * Why a discoverable candidate cannot be installed + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum CatalogNotInstallableReason { + /** The {@code kind-not-installable} variant. */ + KIND_NOT_INSTALLABLE("kind-not-installable"), + /** The {@code ai-skill-not-installable} variant. */ + AI_SKILL_NOT_INSTALLABLE("ai-skill-not-installable"), + /** The {@code policy-forbids} variant. */ + POLICY_FORBIDS("policy-forbids"); + + private final String value; + CatalogNotInstallableReason(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static CatalogNotInstallableReason fromValue(String value) { + for (CatalogNotInstallableReason v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown CatalogNotInstallableReason value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogPolicyRejectedError.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogPolicyRejectedError.java new file mode 100644 index 0000000000..56542ba46e --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogPolicyRejectedError.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; + +/** + * Registry or enterprise policy refused the operation. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class CatalogPolicyRejectedError extends CatalogSearchResult { + + @JsonProperty("kind") + private final String kind = "policy-rejected"; + + @Override + public String getKind() { return kind; } + + /** Which authority produced the decision. */ + @JsonProperty("source") + private McpPlanPolicySource source; + + /** Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. */ + @JsonProperty("message") + private String message; + + public McpPlanPolicySource getSource() { return source; } + public void setSource(McpPlanPolicySource source) { this.source = source; } + + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogSearchParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogSearchParams.java new file mode 100644 index 0000000000..68b90a19c9 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogSearchParams.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; + +/** + * A bounded catalog search. Both the query length and the result count are capped by the schema so a caller cannot request an unbounded scan. + * + * @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 CatalogSearchParams( + /** Protocol version and capabilities the caller requires. */ + @JsonProperty("contract") CatalogClientContract contract, + /** Free-text search query. Persisted as tool input for session continuity, but omitted from telemetry. */ + @JsonProperty("query") String query, + /** Maximum number of candidates to return. Defaults to 10 when omitted. */ + @JsonProperty("limit") Long limit, + /** Restrict results to these candidate kinds. When omitted, every kind the runtime supports is searched. */ + @JsonProperty("kinds") List kinds +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogSearchResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogSearchResult.java new file mode 100644 index 0000000000..4bfbf32701 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogSearchResult.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.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import javax.annotation.processing.Generated; + +/** + * Outcome of a catalog.search call: either bounded inert candidates, or one typed refusal. Never a partial success. + * + * @since 1.0.0 + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "kind", visible = true) +@JsonSubTypes({ + @JsonSubTypes.Type(value = CatalogSearchSucceeded.class, name = "succeeded"), + @JsonSubTypes.Type(value = CatalogNegotiationRefusedError.class, name = "negotiation-refused"), + @JsonSubTypes.Type(value = CatalogUnsupportedKindError.class, name = "unsupported-kind"), + @JsonSubTypes.Type(value = CatalogInvalidRequestError.class, name = "invalid-request"), + @JsonSubTypes.Type(value = CatalogAuthenticationRequiredError.class, name = "authentication-required"), + @JsonSubTypes.Type(value = CatalogPolicyRejectedError.class, name = "policy-rejected"), + @JsonSubTypes.Type(value = CatalogNetworkFailureError.class, name = "network-failure"), + @JsonSubTypes.Type(value = CatalogUnsafeRetrievalError.class, name = "unsafe-retrieval"), + @JsonSubTypes.Type(value = CatalogMalformedCardError.class, name = "malformed-card"), + @JsonSubTypes.Type(value = CatalogContractViolationError.class, name = "contract-violation"), + @JsonSubTypes.Type(value = CatalogUnavailableError.class, name = "unavailable") +}) +@JsonIgnoreProperties(ignoreUnknown = true) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public abstract class CatalogSearchResult { + + /** + * 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/CatalogSearchSucceeded.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogSearchSucceeded.java new file mode 100644 index 0000000000..8ecd11788a --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogSearchSucceeded.java @@ -0,0 +1,59 @@ +/*--------------------------------------------------------------------------------------------- + * 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 completed catalog search: inert candidate summaries, each carrying a single-use handle. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class CatalogSearchSucceeded extends CatalogSearchResult { + + @JsonProperty("kind") + private final String kind = "succeeded"; + + @Override + public String getKind() { return kind; } + + /** Pseudonymous identifier for this search, issued by the runtime or by the catalog authority it queried and never by the caller, so it cannot be forged or replayed to attribute an install to a search that never happened. Always present on a success, so a result set can be tied to the installs it leads to. It identifies a search rather than a person: it is derived from no user, account, device, or query data, and must never be joined with user identity to re-identify anyone. */ + @JsonProperty("searchId") + private String searchId; + + /** Matching candidates, never more than the requested limit. All text is inert untrusted data. */ + @JsonProperty("candidates") + private List candidates; + + /** Whether further matches existed beyond the requested limit. */ + @JsonProperty("truncated") + private Boolean truncated; + + /** Protocol version and capabilities the runtime honoured. */ + @JsonProperty("negotiated") + private CatalogNegotiatedContract negotiated; + + public String getSearchId() { return searchId; } + public void setSearchId(String searchId) { this.searchId = searchId; } + + public List getCandidates() { return candidates; } + public void setCandidates(List candidates) { this.candidates = candidates; } + + public Boolean getTruncated() { return truncated; } + public void setTruncated(Boolean truncated) { this.truncated = truncated; } + + public CatalogNegotiatedContract getNegotiated() { return negotiated; } + public void setNegotiated(CatalogNegotiatedContract negotiated) { this.negotiated = negotiated; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogUnavailableError.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogUnavailableError.java new file mode 100644 index 0000000000..7f21c57085 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogUnavailableError.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; + +/** + * The operation is not available on this runtime. Distinct from a network failure: nothing was attempted. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class CatalogUnavailableError extends CatalogSearchResult { + + @JsonProperty("kind") + private final String kind = "unavailable"; + + @Override + public String getKind() { return kind; } + + /** Why the operation is unavailable. */ + @JsonProperty("reason") + private CatalogUnavailableReason reason; + + /** Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. */ + @JsonProperty("message") + private String message; + + public CatalogUnavailableReason getReason() { return reason; } + public void setReason(CatalogUnavailableReason reason) { this.reason = reason; } + + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogUnavailableReason.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogUnavailableReason.java new file mode 100644 index 0000000000..c7bf4dcd76 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogUnavailableReason.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; + +/** + * Why a catalog operation is not available on this runtime + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum CatalogUnavailableReason { + /** The {@code search-unavailable} variant. */ + SEARCH_UNAVAILABLE("search-unavailable"), + /** The {@code planning-unavailable} variant. */ + PLANNING_UNAVAILABLE("planning-unavailable"), + /** The {@code authority-not-configured} variant. */ + AUTHORITY_NOT_CONFIGURED("authority-not-configured"), + /** The {@code disabled-by-policy} variant. */ + DISABLED_BY_POLICY("disabled-by-policy"); + + private final String value; + CatalogUnavailableReason(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static CatalogUnavailableReason fromValue(String value) { + for (CatalogUnavailableReason v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown CatalogUnavailableReason value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogUnavailableTransportError.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogUnavailableTransportError.java new file mode 100644 index 0000000000..3c40687eac --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogUnavailableTransportError.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; + +/** + * No transport this runtime can use is available for the requested server. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class CatalogUnavailableTransportError extends McpPlanInstallResult { + + @JsonProperty("kind") + private final String kind = "unavailable-transport"; + + @Override + public String getKind() { return kind; } + + /** Why no transport could be offered. */ + @JsonProperty("reason") + private CatalogUnavailableTransportReason reason; + + /** Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. */ + @JsonProperty("message") + private String message; + + public CatalogUnavailableTransportReason getReason() { return reason; } + public void setReason(CatalogUnavailableTransportReason reason) { this.reason = reason; } + + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogUnavailableTransportReason.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogUnavailableTransportReason.java new file mode 100644 index 0000000000..d532967313 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogUnavailableTransportReason.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; + +/** + * Why no usable transport could be offered + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum CatalogUnavailableTransportReason { + /** The {@code no-eligible-transport} variant. */ + NO_ELIGIBLE_TRANSPORT("no-eligible-transport"), + /** The {@code transport-not-supported} variant. */ + TRANSPORT_NOT_SUPPORTED("transport-not-supported"), + /** The {@code remote-enumeration-unavailable} variant. */ + REMOTE_ENUMERATION_UNAVAILABLE("remote-enumeration-unavailable"); + + private final String value; + CatalogUnavailableTransportReason(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static CatalogUnavailableTransportReason fromValue(String value) { + for (CatalogUnavailableTransportReason v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown CatalogUnavailableTransportReason value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogUnsafeRetrievalError.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogUnsafeRetrievalError.java new file mode 100644 index 0000000000..513ce5b80f --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogUnsafeRetrievalError.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; + +/** + * Retrieval was refused by the runtime's hardened fetch boundary before any request left the process, or before a redirect was followed. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class CatalogUnsafeRetrievalError extends CatalogSearchResult { + + @JsonProperty("kind") + private final String kind = "unsafe-retrieval"; + + @Override + public String getKind() { return kind; } + + /** Which control refused the retrieval, low cardinality so it can be aggregated without carrying a URL. */ + @JsonProperty("reason") + private CatalogUnsafeRetrievalReason reason; + + /** Human-readable explanation, safe to surface. Never contains the refused URL, nor a query, handle, or secret. */ + @JsonProperty("message") + private String message; + + public CatalogUnsafeRetrievalReason getReason() { return reason; } + public void setReason(CatalogUnsafeRetrievalReason reason) { this.reason = reason; } + + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogUnsafeRetrievalReason.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogUnsafeRetrievalReason.java new file mode 100644 index 0000000000..e05931dac1 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogUnsafeRetrievalReason.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 javax.annotation.processing.Generated; + +/** + * Which hardened-fetch control refused a retrieval + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum CatalogUnsafeRetrievalReason { + /** The {@code blocked-scheme} variant. */ + BLOCKED_SCHEME("blocked-scheme"), + /** The {@code credentials-in-url} variant. */ + CREDENTIALS_IN_URL("credentials-in-url"), + /** The {@code blocked-address} variant. */ + BLOCKED_ADDRESS("blocked-address"), + /** The {@code redirect-to-blocked-address} variant. */ + REDIRECT_TO_BLOCKED_ADDRESS("redirect-to-blocked-address"), + /** The {@code proxy-rejected} variant. */ + PROXY_REJECTED("proxy-rejected"), + /** The {@code host-not-permitted} variant. */ + HOST_NOT_PERMITTED("host-not-permitted"); + + private final String value; + CatalogUnsafeRetrievalReason(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static CatalogUnsafeRetrievalReason fromValue(String value) { + for (CatalogUnsafeRetrievalReason v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown CatalogUnsafeRetrievalReason value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogUnsupportedKindError.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogUnsupportedKindError.java new file mode 100644 index 0000000000..06cc7bc5a8 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CatalogUnsupportedKindError.java @@ -0,0 +1,52 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * The request asked for a candidate kind this runtime does not serve. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class CatalogUnsupportedKindError extends CatalogSearchResult { + + @JsonProperty("kind") + private final String kind = "unsupported-kind"; + + @Override + public String getKind() { return kind; } + + /** The kinds from the request that are not supported. */ + @JsonProperty("requestedKinds") + private List requestedKinds; + + /** Every candidate kind this runtime can serve. */ + @JsonProperty("supportedKinds") + private List supportedKinds; + + /** Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. */ + @JsonProperty("message") + private String message; + + public List getRequestedKinds() { return requestedKinds; } + public void setRequestedKinds(List requestedKinds) { this.requestedKinds = requestedKinds; } + + public List getSupportedKinds() { return supportedKinds; } + public void setSupportedKinds(List supportedKinds) { this.supportedKinds = supportedKinds; } + + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CommandsInvocationEffectOutcome.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CommandsInvocationEffectOutcome.java new file mode 100644 index 0000000000..79f3be8baf --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CommandsInvocationEffectOutcome.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; + +/** + * Whether a pending slash-command invocation effect was applied or cancelled by the host. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum CommandsInvocationEffectOutcome { + /** The {@code applied} variant. */ + APPLIED("applied"), + /** The {@code cancelled} variant. */ + CANCELLED("cancelled"); + + private final String value; + CommandsInvocationEffectOutcome(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static CommandsInvocationEffectOutcome fromValue(String value) { + for (CommandsInvocationEffectOutcome v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown CommandsInvocationEffectOutcome value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CommandsInvocationOrigin.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CommandsInvocationOrigin.java new file mode 100644 index 0000000000..14d5906d34 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CommandsInvocationOrigin.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 CommandsInvocationOrigin { + /** The {@code settings} variant. */ + SETTINGS("settings"); + + private final String value; + CommandsInvocationOrigin(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static CommandsInvocationOrigin fromValue(String value) { + for (CommandsInvocationOrigin v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown CommandsInvocationOrigin value: " + value); + } +} 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/sdk/src/generated/java/com/github/copilot/generated/rpc/ConnectClientInfo.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ConnectClientInfo.java new file mode 100644 index 0000000000..e5b6b6f24d --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ConnectClientInfo.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; + +/** + * Identity of the integrating host, declared once on the `server.connect` handshake so telemetry from this connection is attributed to a single, consistent surface. All fields are optional; omit them to keep the default attribution. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record ConnectClientInfo( + /** Name of the host editor, e.g. `"vscode"`. */ + @JsonProperty("editorName") String editorName, + /** Version of the host editor, e.g. `"1.124.2"`. Ignored unless it looks like a version string. */ + @JsonProperty("editorVersion") String editorVersion, + /** Name of the Copilot extension within the host, e.g. `"copilot-chat"`. */ + @JsonProperty("extensionName") String extensionName, + /** Version of the Copilot extension within the host, e.g. `"0.54.0"`. Ignored unless it looks like a version string. */ + @JsonProperty("extensionVersion") String extensionVersion +) { +} 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 83% 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 19172cb1b4..05f2534970 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 @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Parameters for the `server.connect` handshake: an optional connection token and optional connection-level opt-ins (e.g. GitHub telemetry forwarding). + * Connection-level opt-ins for the `server.connect` handshake. Transport authentication is consumed by the native protocol boundary before dispatch. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 @@ -24,9 +24,11 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) 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. 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 + @JsonProperty("enableGitHubTelemetryForwarding") Boolean enableGitHubTelemetryForwarding, + /** Identity of the integrating host. Optional; omit it to keep the default attribution. */ + @JsonProperty("clientInfo") ConnectClientInfo clientInfo, + /** Connection token; required when the server was started with COPILOT_CONNECTION_TOKEN */ + @JsonProperty("token") String token ) { } 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/src/generated/java/com/github/copilot/generated/rpc/ContentExclusionPathCheck.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ContentExclusionPathCheck.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ContentExclusionPathCheck.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ContentExclusionPathCheck.java 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/sdk/src/generated/java/com/github/copilot/generated/rpc/CopilotApiTokenAuthInfo.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CopilotApiTokenAuthInfo.java new file mode 100644 index 0000000000..2e6952f538 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CopilotApiTokenAuthInfo.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; + +/** + * Authentication-info variant for direct Copilot API token auth sourced from environment variables, with public GitHub host. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class CopilotApiTokenAuthInfo extends AuthInfo { + + @JsonProperty("type") + private final String type = "copilot-api-token"; + + @Override + public String getType() { return type; } + + /** Authentication host (always the public GitHub host). */ + @JsonProperty("host") + private String host; + + /** 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. */ + @JsonProperty("copilotUser") + private CopilotUserResponse copilotUser; + + public String getHost() { return host; } + public void setHost(String host) { this.host = host; } + + public CopilotUserResponse getCopilotUser() { return copilotUser; } + public void setCopilotUser(CopilotUserResponse copilotUser) { this.copilotUser = copilotUser; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CopilotUserResponse.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CopilotUserResponse.java new file mode 100644 index 0000000000..cd6d5c83bd --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CopilotUserResponse.java @@ -0,0 +1,79 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * 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. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record CopilotUserResponse( + /** GitHub login of the authenticated user. */ + @JsonProperty("login") String login, + /** Copilot access SKU identifier (e.g. `free_limited_copilot`, `copilot_for_business_seat_quota`) used to gate model and feature access. */ + @JsonProperty("access_type_sku") String accessTypeSku, + /** Opaque analytics tracking identifier for the user, forwarded from the Copilot API. */ + @JsonProperty("analytics_tracking_id") String analyticsTrackingId, + /** Date the Copilot seat was assigned to the user, if applicable. */ + @JsonProperty("assigned_date") Object assignedDate, + /** Whether the user is eligible to sign up for the free/limited Copilot tier. */ + @JsonProperty("can_signup_for_limited") Boolean canSignupForLimited, + /** Whether Copilot chat is enabled for the user. */ + @JsonProperty("chat_enabled") Boolean chatEnabled, + /** Copilot plan name for the user (e.g. `individual`, `business`, `enterprise`). */ + @JsonProperty("copilot_plan") String copilotPlan, + /** Whether `.copilotignore` content-exclusion support is enabled for the user. */ + @JsonProperty("copilotignore_enabled") Boolean copilotignoreEnabled, + /** Endpoint URLs from the raw Copilot `/copilot_internal/v2/token` user-response passthrough. */ + @JsonProperty("endpoints") CopilotUserResponseEndpoints endpoints, + /** Logins of the organizations the user belongs to. */ + @JsonProperty("organization_login_list") List organizationLoginList, + /** Organizations the user belongs to, each with an optional login and display name. */ + @JsonProperty("organization_list") Object organizationList, + /** Whether the Codex agent is enabled for the user. */ + @JsonProperty("codex_agent_enabled") Boolean codexAgentEnabled, + /** Whether MCP (Model Context Protocol) support is enabled for the user. */ + @JsonProperty("is_mcp_enabled") Object isMcpEnabled, + /** Date the user's usage quota next resets, as a raw string from the Copilot API; see `quota_reset_date_utc` for the UTC-normalized value. */ + @JsonProperty("quota_reset_date") String quotaResetDate, + /** Quota snapshot map from the raw Copilot user-response passthrough, with chat, completions, premium-interactions, and other entries. */ + @JsonProperty("quota_snapshots") CopilotUserResponseQuotaSnapshots quotaSnapshots, + /** Whether the user's telemetry is subject to restricted-data handling. */ + @JsonProperty("restricted_telemetry") Boolean restrictedTelemetry, + /** Whether the user is a GitHub/Microsoft staff member. */ + @JsonProperty("is_staff") Boolean isStaff, + /** Raw passthrough of the Copilot API `te` flag for the user (an opaque server-side eligibility signal surfaced in telemetry); not otherwise interpreted by the runtime. */ + @JsonProperty("te") Boolean te, + /** Whether the account is on usage-based (token/AI-credit) billing rather than a fixed premium-request quota. */ + @JsonProperty("token_based_billing") Boolean tokenBasedBilling, + /** Whether the user is able to upgrade their Copilot plan. */ + @JsonProperty("can_upgrade_plan") Boolean canUpgradePlan, + /** UTC-normalized form of `quota_reset_date` (the date the user's usage quota next resets). */ + @JsonProperty("quota_reset_date_utc") String quotaResetDateUtc, + /** Per-category quota allotments for free/limited-tier users, keyed by quota category. */ + @JsonProperty("limited_user_quotas") Map limitedUserQuotas, + /** Date the free/limited-tier user's quotas next reset, as a raw string from the Copilot API. */ + @JsonProperty("limited_user_reset_date") String limitedUserResetDate, + /** Per-category monthly quota allotments, keyed by quota category. */ + @JsonProperty("monthly_quotas") Map monthlyQuotas, + /** Whether cloud session storage is enabled for the user. */ + @JsonProperty("cloud_session_storage_enabled") Boolean cloudSessionStorageEnabled, + /** Whether CLI remote control is enabled for the user. */ + @JsonProperty("cli_remote_control_enabled") Boolean cliRemoteControlEnabled +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CopilotUserResponseEndpoints.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CopilotUserResponseEndpoints.java new file mode 100644 index 0000000000..3e4606559f --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CopilotUserResponseEndpoints.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 javax.annotation.processing.Generated; + +/** + * Endpoint URLs from the raw Copilot `/copilot_internal/v2/token` user-response passthrough. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record CopilotUserResponseEndpoints( + /** Copilot API endpoint URL. */ + @JsonProperty("api") String api, + /** Origin-tracker endpoint URL. */ + @JsonProperty("origin-tracker") String originTracker, + /** Copilot proxy endpoint URL. */ + @JsonProperty("proxy") String proxy, + /** Copilot telemetry endpoint URL. */ + @JsonProperty("telemetry") String telemetry, + /** Experimental-service endpoint URL. */ + @JsonProperty("exp") String exp +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CopilotUserResponseQuotaSnapshots.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CopilotUserResponseQuotaSnapshots.java new file mode 100644 index 0000000000..10c983bb40 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CopilotUserResponseQuotaSnapshots.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; + +/** + * Quota snapshot map from the raw Copilot user-response passthrough, with chat, completions, premium-interactions, and other entries. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record CopilotUserResponseQuotaSnapshots( + /** Chat quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. */ + @JsonProperty("chat") CopilotUserResponseQuotaSnapshotsChat chat, + /** Completions quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. */ + @JsonProperty("completions") CopilotUserResponseQuotaSnapshotsCompletions completions, + /** Premium-interactions quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. */ + @JsonProperty("premium_interactions") CopilotUserResponseQuotaSnapshotsPremiumInteractions premiumInteractions +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CopilotUserResponseQuotaSnapshotsChat.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CopilotUserResponseQuotaSnapshotsChat.java new file mode 100644 index 0000000000..94a516286b --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CopilotUserResponseQuotaSnapshotsChat.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 com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Chat quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record CopilotUserResponseQuotaSnapshotsChat( + /** Number of requests/units included in the entitlement for this period; `-1` denotes an unlimited entitlement. */ + @JsonProperty("entitlement") Double entitlement, + /** Count of additional pay-per-request usage consumed this period beyond the entitlement. */ + @JsonProperty("overage_count") Double overageCount, + /** Whether usage may continue at pay-per-request rates once the entitlement is exhausted. */ + @JsonProperty("overage_permitted") Boolean overagePermitted, + /** Percentage of the entitlement remaining at the snapshot timestamp. */ + @JsonProperty("percent_remaining") Double percentRemaining, + /** Identifier of the quota bucket this snapshot describes. */ + @JsonProperty("quota_id") String quotaId, + /** Amount of quota remaining at the snapshot timestamp. */ + @JsonProperty("quota_remaining") Double quotaRemaining, + /** Remaining entitlement/quota amount at the snapshot timestamp. */ + @JsonProperty("remaining") Double remaining, + /** Whether the entitlement for this category is unlimited. */ + @JsonProperty("unlimited") Boolean unlimited, + /** UTC timestamp when this snapshot was captured. */ + @JsonProperty("timestamp_utc") String timestampUtc, + /** Whether the user currently has quota available; when `false` and not unlimited, further requests are blocked until the quota resets. */ + @JsonProperty("has_quota") Boolean hasQuota, + /** Unix epoch time, in seconds, when this quota next resets. */ + @JsonProperty("quota_reset_at") Double quotaResetAt, + /** Whether this category uses usage-based (token/AI-credit) billing rather than a fixed premium-request count. */ + @JsonProperty("token_based_billing") Boolean tokenBasedBilling +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CopilotUserResponseQuotaSnapshotsCompletions.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CopilotUserResponseQuotaSnapshotsCompletions.java new file mode 100644 index 0000000000..826b034ba0 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CopilotUserResponseQuotaSnapshotsCompletions.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 com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Completions quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record CopilotUserResponseQuotaSnapshotsCompletions( + /** Number of requests/units included in the entitlement for this period; `-1` denotes an unlimited entitlement. */ + @JsonProperty("entitlement") Double entitlement, + /** Count of additional pay-per-request usage consumed this period beyond the entitlement. */ + @JsonProperty("overage_count") Double overageCount, + /** Whether usage may continue at pay-per-request rates once the entitlement is exhausted. */ + @JsonProperty("overage_permitted") Boolean overagePermitted, + /** Percentage of the entitlement remaining at the snapshot timestamp. */ + @JsonProperty("percent_remaining") Double percentRemaining, + /** Identifier of the quota bucket this snapshot describes. */ + @JsonProperty("quota_id") String quotaId, + /** Amount of quota remaining at the snapshot timestamp. */ + @JsonProperty("quota_remaining") Double quotaRemaining, + /** Remaining entitlement/quota amount at the snapshot timestamp. */ + @JsonProperty("remaining") Double remaining, + /** Whether the entitlement for this category is unlimited. */ + @JsonProperty("unlimited") Boolean unlimited, + /** UTC timestamp when this snapshot was captured. */ + @JsonProperty("timestamp_utc") String timestampUtc, + /** Whether the user currently has quota available; when `false` and not unlimited, further requests are blocked until the quota resets. */ + @JsonProperty("has_quota") Boolean hasQuota, + /** Unix epoch time, in seconds, when this quota next resets. */ + @JsonProperty("quota_reset_at") Double quotaResetAt, + /** Whether this category uses usage-based (token/AI-credit) billing rather than a fixed premium-request count. */ + @JsonProperty("token_based_billing") Boolean tokenBasedBilling +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CopilotUserResponseQuotaSnapshotsPremiumInteractions.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CopilotUserResponseQuotaSnapshotsPremiumInteractions.java new file mode 100644 index 0000000000..ca4a08b4f0 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CopilotUserResponseQuotaSnapshotsPremiumInteractions.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 com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Premium-interactions quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record CopilotUserResponseQuotaSnapshotsPremiumInteractions( + /** Number of requests/units included in the entitlement for this period; `-1` denotes an unlimited entitlement. */ + @JsonProperty("entitlement") Double entitlement, + /** Count of additional pay-per-request usage consumed this period beyond the entitlement. */ + @JsonProperty("overage_count") Double overageCount, + /** Whether usage may continue at pay-per-request rates once the entitlement is exhausted. */ + @JsonProperty("overage_permitted") Boolean overagePermitted, + /** Percentage of the entitlement remaining at the snapshot timestamp. */ + @JsonProperty("percent_remaining") Double percentRemaining, + /** Identifier of the quota bucket this snapshot describes. */ + @JsonProperty("quota_id") String quotaId, + /** Amount of quota remaining at the snapshot timestamp. */ + @JsonProperty("quota_remaining") Double quotaRemaining, + /** Remaining entitlement/quota amount at the snapshot timestamp. */ + @JsonProperty("remaining") Double remaining, + /** Whether the entitlement for this category is unlimited. */ + @JsonProperty("unlimited") Boolean unlimited, + /** UTC timestamp when this snapshot was captured. */ + @JsonProperty("timestamp_utc") String timestampUtc, + /** Whether the user currently has quota available; when `false` and not unlimited, further requests are blocked until the quota resets. */ + @JsonProperty("has_quota") Boolean hasQuota, + /** Unix epoch time, in seconds, when this quota next resets. */ + @JsonProperty("quota_reset_at") Double quotaResetAt, + /** Whether this category uses usage-based (token/AI-credit) billing rather than a fixed premium-request count. */ + @JsonProperty("token_based_billing") Boolean tokenBasedBilling +) { +} 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/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/src/generated/java/com/github/copilot/generated/rpc/DiscoveredExtension.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DiscoveredExtension.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/DiscoveredExtension.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/DiscoveredExtension.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/DiscoveredExtensionMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DiscoveredExtensionMode.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/DiscoveredExtensionMode.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/DiscoveredExtensionMode.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/DiscoveredExtensionPlugin.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DiscoveredExtensionPlugin.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/DiscoveredExtensionPlugin.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/DiscoveredExtensionPlugin.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/DiscoveredExtensionSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DiscoveredExtensionSource.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/DiscoveredExtensionSource.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/DiscoveredExtensionSource.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DiscoveredHook.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DiscoveredHook.java new file mode 100644 index 0000000000..fce707243d --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DiscoveredHook.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; + +/** + * One server-discovered hook action from user, repository, plugin, or managed-policy configuration. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record DiscoveredHook( + /** Deterministic identifier for this server-discovered action row. It remains stable while the project, origin, source, event, action content, and duplicate ordinal are unchanged. This is row identity, not the key persisted in disabledHooks. */ + @JsonProperty("id") String id, + /** Hook event that invokes this action. */ + @JsonProperty("hookType") HookType hookType, + /** Configuration tier that contributed this hook action. */ + @JsonProperty("origin") HookOrigin origin, + /** Human-readable source label, such as a hook file path, settings source, or plugin name. */ + @JsonProperty("source") String source, + /** Input project path for which this server-side action was resolved. Set on every row returned for project-scoped discovery, including repeated user and policy actions. */ + @JsonProperty("projectPath") String projectPath, + /** Whether this action is enabled under the server-side discovery settings. Concrete sessions may differ because they can add session-specific directories, plugins, or trust. False when its disable key is present in the user's disabled-hooks setting or disable-all settings suppress the action. */ + @JsonProperty("enabled") Boolean enabled, + /** Durable content hash used by hook enablement. Identical actions may intentionally share this key. Omitted when changing the user's disabled-hooks setting cannot change the action's current server-discovered state, including managed-policy hooks, session-start prompt actions, actions suppressed by disable-all settings, and projectless plugin actions that require project-directory expansion. */ + @JsonProperty("disableKey") String disableKey +) { +} 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/sdk/src/generated/java/com/github/copilot/generated/rpc/EnvAuthInfo.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/EnvAuthInfo.java new file mode 100644 index 0000000000..ce96797903 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/EnvAuthInfo.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; + +/** + * Authentication-info input variant for a token sourced from an environment variable, with host, optional login, token, and env var name. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class EnvAuthInfo extends AuthInfo { + + @JsonProperty("type") + private final String type = "env"; + + @Override + public String getType() { return type; } + + /** Authentication host (e.g. https://github.com or a GHES host). */ + @JsonProperty("host") + private String host; + + /** User login associated with the token. Undefined for server-to-server tokens (those starting with `ghs_`). */ + @JsonProperty("login") + private String login; + + /** The token value itself. Treat as a secret. */ + @JsonProperty("token") + private String token; + + /** Name of the environment variable the token was sourced from. */ + @JsonProperty("envVar") + private String envVar; + + /** 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. */ + @JsonProperty("copilotUser") + private CopilotUserResponse copilotUser; + + public String getHost() { return host; } + public void setHost(String host) { this.host = host; } + + public String getLogin() { return login; } + public void setLogin(String login) { this.login = login; } + + public String getToken() { return token; } + public void setToken(String token) { this.token = token; } + + public String getEnvVar() { return envVar; } + public void setEnvVar(String envVar) { this.envVar = envVar; } + + public CopilotUserResponse getCopilotUser() { return copilotUser; } + public void setCopilotUser(CopilotUserResponse copilotUser) { this.copilotUser = copilotUser; } +} 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 100% 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 diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/EventsReadDirection.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/EventsReadDirection.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/EventsReadDirection.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/EventsReadDirection.java 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 0000000000..e7590c7f9e --- /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 0000000000..7b520f9067 --- /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 0000000000..8a43ad4af3 --- /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/src/generated/java/com/github/copilot/generated/rpc/ExtensionsDisableParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExtensionsDisableParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ExtensionsDisableParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExtensionsDisableParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ExtensionsDiscoverResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExtensionsDiscoverResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ExtensionsDiscoverResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExtensionsDiscoverResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ExtensionsEnableParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExtensionsEnableParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ExtensionsEnableParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExtensionsEnableParams.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExternalToolTextResultForLlmBinaryResultsForLlm.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExternalToolTextResultForLlmBinaryResultsForLlm.java new file mode 100644 index 0000000000..9aec5d03a1 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExternalToolTextResultForLlmBinaryResultsForLlm.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 java.util.Map; +import javax.annotation.processing.Generated; + +/** + * Binary result returned by a tool for the model + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record ExternalToolTextResultForLlmBinaryResultsForLlm( + /** Binary result type discriminator. Use "image" for images and "resource" for other binary data. */ + @JsonProperty("type") ExternalToolTextResultForLlmBinaryResultsForLlmType type, + /** Base64-encoded binary data */ + @JsonProperty("data") String data, + /** MIME type of the binary data */ + @JsonProperty("mimeType") String mimeType, + /** Human-readable description of the binary data */ + @JsonProperty("description") String description, + /** Optional metadata from the producing tool. */ + @JsonProperty("metadata") Map metadata +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExternalToolTextResultForLlmBinaryResultsForLlmType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExternalToolTextResultForLlmBinaryResultsForLlmType.java new file mode 100644 index 0000000000..d73e769b87 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExternalToolTextResultForLlmBinaryResultsForLlmType.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; + +/** + * Binary result type discriminator. Use "image" for images and "resource" for other binary data. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum ExternalToolTextResultForLlmBinaryResultsForLlmType { + /** The {@code image} variant. */ + IMAGE("image"), + /** The {@code resource} variant. */ + RESOURCE("resource"); + + private final String value; + ExternalToolTextResultForLlmBinaryResultsForLlmType(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static ExternalToolTextResultForLlmBinaryResultsForLlmType fromValue(String value) { + for (ExternalToolTextResultForLlmBinaryResultsForLlmType v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown ExternalToolTextResultForLlmBinaryResultsForLlmType value: " + value); + } +} 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 675e715e85..9910d4f7f5 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/src/generated/java/com/github/copilot/generated/rpc/FactoryAgentSummary.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryAgentSummary.java similarity index 65% rename from java/src/generated/java/com/github/copilot/generated/rpc/FactoryAgentSummary.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryAgentSummary.java index af20d8e810..d1bd2329b2 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/FactoryAgentSummary.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryAgentSummary.java @@ -21,18 +21,33 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record FactoryAgentSummary( + /** Stable direct-agent identifier. */ @JsonProperty("agentId") String agentId, + /** Tool-call identifier that launched the agent. */ @JsonProperty("toolCallId") String toolCallId, + /** Owning factory run identifier. */ @JsonProperty("runId") String runId, + /** Phase identifier active when the agent was launched, or null. */ @JsonProperty("phaseId") String phaseId, + /** Friendly, non-unique name intended for display */ @JsonProperty("label") String label, + /** Friendly, non-unique name intended for display */ + @JsonProperty("displayName") String displayName, + /** Registered agent type. */ @JsonProperty("agentType") String agentType, + /** Current durable or live agent status. */ @JsonProperty("status") String status, + /** Model requested when the agent was launched. */ @JsonProperty("requestedModel") String requestedModel, + /** Concrete model resolved for the agent. */ @JsonProperty("resolvedModel") String resolvedModel, + /** Epoch milliseconds when the agent started. */ @JsonProperty("startedAt") Long startedAt, + /** Epoch milliseconds when the agent completed. */ @JsonProperty("completedAt") Long completedAt, + /** Accumulated active agent time in milliseconds. */ @JsonProperty("activeMs") Long activeMs, + /** Prompt-safe live activity text. */ @JsonProperty("activity") String activity ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/FactoryCurrentPhase.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryCurrentPhase.java similarity index 88% rename from java/src/generated/java/com/github/copilot/generated/rpc/FactoryCurrentPhase.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryCurrentPhase.java index 6a8de8e82c..e0a30f51a6 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/FactoryCurrentPhase.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryCurrentPhase.java @@ -21,7 +21,9 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record FactoryCurrentPhase( + /** Current phase identifier. */ @JsonProperty("id") String id, + /** Zero-based declared phase ordinal, or null for an undeclared phase. */ @JsonProperty("ordinal") Long ordinal ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/FactoryDeclaredLimits.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryDeclaredLimits.java similarity index 82% rename from java/src/generated/java/com/github/copilot/generated/rpc/FactoryDeclaredLimits.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryDeclaredLimits.java index 21f74646fb..7fa99b005b 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/FactoryDeclaredLimits.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryDeclaredLimits.java @@ -21,9 +21,13 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record FactoryDeclaredLimits( + /** Maximum concurrently active subagents. */ @JsonProperty("maxConcurrentSubagents") Long maxConcurrentSubagents, + /** Maximum total subagents spawned by the run. */ @JsonProperty("maxTotalSubagents") Long maxTotalSubagents, + /** Maximum accumulated active execution time in seconds. */ @JsonProperty("timeoutSeconds") Double timeoutSeconds, + /** Maximum AI credits consumed by subagents and descendants. */ @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 100% 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 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/src/generated/java/com/github/copilot/generated/rpc/FactoryPhaseObservation.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryPhaseObservation.java similarity index 62% rename from java/src/generated/java/com/github/copilot/generated/rpc/FactoryPhaseObservation.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryPhaseObservation.java index aa04ef5ba8..aa96e0cb90 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/FactoryPhaseObservation.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryPhaseObservation.java @@ -21,18 +21,31 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record FactoryPhaseObservation( + /** Phase identifier. */ @JsonProperty("id") String id, + /** Zero-based declared phase ordinal, or null for an undeclared phase. */ @JsonProperty("ordinal") Long ordinal, + /** Human-readable phase title. */ @JsonProperty("title") String title, + /** Optional human-readable phase detail. */ @JsonProperty("detail") String detail, + /** Derived lifecycle state of the phase. */ @JsonProperty("status") FactoryPhaseStatus status, + /** Most recent run attempt that entered this phase, or `0` if the phase has never been entered. */ @JsonProperty("lastEnteredRunAttempt") Long lastEnteredRunAttempt, + /** Number of times execution entered this phase. */ @JsonProperty("entryCount") Long entryCount, + /** Epoch milliseconds when this phase first started; for a skipped phase, the synthetic skip timestamp (equal to `completedAt`). */ @JsonProperty("startedAt") Long startedAt, + /** Epoch milliseconds when this phase completed; for a skipped phase, the synthetic skip timestamp (equal to `startedAt`). */ @JsonProperty("completedAt") Long completedAt, + /** Completed active time accumulated by this phase in milliseconds. */ @JsonProperty("accumulatedActiveMs") Long accumulatedActiveMs, + /** Current live active time for this phase in milliseconds. */ @JsonProperty("currentActiveMs") Long currentActiveMs, + /** Total direct agents associated with this phase. */ @JsonProperty("totalAgentCount") Long totalAgentCount, + /** Direct agents in this phase that are currently live. */ @JsonProperty("liveAgentCount") Long liveAgentCount ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/FactoryPhaseStatus.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryPhaseStatus.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/FactoryPhaseStatus.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryPhaseStatus.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/FactoryProgressLine.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryProgressLine.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/FactoryProgressLine.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryProgressLine.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/FactoryProgressPage.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryProgressPage.java similarity index 79% rename from java/src/generated/java/com/github/copilot/generated/rpc/FactoryProgressPage.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryProgressPage.java index 56732d8f47..76278a1585 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/FactoryProgressPage.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryProgressPage.java @@ -22,10 +22,15 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record FactoryProgressPage( + /** Progress records in sequence order. */ @JsonProperty("records") List records, + /** Oldest sequence number in this page, or null when empty. */ @JsonProperty("oldestSeq") Long oldestSeq, + /** Newest sequence number in this page, or null when empty. */ @JsonProperty("newestSeq") Long newestSeq, + /** Whether progress records older than this page exist. */ @JsonProperty("hasMoreOlder") Boolean hasMoreOlder, + /** Whether progress records newer than this page exist. */ @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/FactoryRunConsumed.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunConsumed.java similarity index 85% rename from java/src/generated/java/com/github/copilot/generated/rpc/FactoryRunConsumed.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunConsumed.java index 62cec5f730..a492db26f6 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/FactoryRunConsumed.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunConsumed.java @@ -21,8 +21,11 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record FactoryRunConsumed( + /** Accumulated active execution time in milliseconds. */ @JsonProperty("activeMs") Long activeMs, + /** Total subagents spawned by the run. */ @JsonProperty("subagents") Long subagents, + /** AI usage consumed by the run in nano-AIU. */ @JsonProperty("nanoAiu") Long nanoAiu ) { } 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/FactoryRunLimits.java similarity index 100% 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/FactoryRunLimits.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/FactoryRunResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunResult.java similarity index 84% rename from java/src/generated/java/com/github/copilot/generated/rpc/FactoryRunResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunResult.java index bb28f40887..71ee3c49e6 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/FactoryRunResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunResult.java @@ -23,13 +23,15 @@ public record FactoryRunResult( /** Factory run identifier. */ @JsonProperty("runId") String runId, + /** One-based execution attempt represented by this envelope. Absent before the first attempt starts or when returned by an older runtime. */ + @JsonProperty("attempt") Long attempt, /** 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. */ + /** Machine-readable failure details for a halted or errored run. */ @JsonProperty("failure") Object failure, /** Reason for a halted or cancelled run. */ @JsonProperty("reason") String reason, 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/src/generated/java/com/github/copilot/generated/rpc/FactoryRunSummary.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunSummary.java similarity index 62% rename from java/src/generated/java/com/github/copilot/generated/rpc/FactoryRunSummary.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunSummary.java index fb90885ee4..f482acfa16 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/FactoryRunSummary.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunSummary.java @@ -21,24 +21,43 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record FactoryRunSummary( + /** Factory run identifier. */ @JsonProperty("runId") String runId, + /** Registered factory name. */ @JsonProperty("factoryName") String factoryName, + /** Human-readable factory description. */ @JsonProperty("description") String description, + /** Current factory run status. */ @JsonProperty("status") FactoryRunStatus status, + /** Monotonic durable run revision. */ @JsonProperty("revision") Long revision, + /** Epoch milliseconds when the run was created. */ @JsonProperty("createdAt") Long createdAt, + /** Epoch milliseconds when execution first started, or null before start. */ @JsonProperty("startedAt") Long startedAt, + /** Epoch milliseconds when the durable run was last updated. */ @JsonProperty("updatedAt") Long updatedAt, + /** Epoch milliseconds when the run completed, or null while nonterminal. */ @JsonProperty("completedAt") Long completedAt, + /** Current phase identity, or null before any phase is entered. */ @JsonProperty("currentPhase") FactoryCurrentPhase currentPhase, + /** Number of phases declared by the factory. */ @JsonProperty("declaredPhaseCount") Long declaredPhaseCount, + /** Number of direct factory agents currently live. */ @JsonProperty("liveAgentCount") Long liveAgentCount, + /** Total direct factory agents spawned across all attempts. */ @JsonProperty("totalSpawnedAgentCount") Long totalSpawnedAgentCount, + /** Durable resource consumption. */ @JsonProperty("consumed") FactoryRunConsumed consumed, + /** Resource ceilings declared by the factory. */ @JsonProperty("declaredLimits") FactoryDeclaredLimits declaredLimits, + /** Approved effective resource ceilings, or null until approved. */ @JsonProperty("approved") FactoryDeclaredLimits approved, + /** Epoch milliseconds when this live-overlay snapshot was observed. */ @JsonProperty("observedAt") Long observedAt, + /** Epoch milliseconds when the current active segment started, or null while inactive. */ @JsonProperty("activeSegmentStartedAt") Long activeSegmentStartedAt, + /** Terminal run outcome, or null while nonterminal. */ @JsonProperty("terminal") FactoryRunTerminal terminal ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/FactoryRunTerminal.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunTerminal.java similarity index 84% rename from java/src/generated/java/com/github/copilot/generated/rpc/FactoryRunTerminal.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunTerminal.java index 231c1b8a11..bf9bfa7db4 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/FactoryRunTerminal.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunTerminal.java @@ -21,9 +21,13 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record FactoryRunTerminal( + /** Human-readable terminal reason. */ @JsonProperty("reason") String reason, + /** Machine-readable terminal failure. */ @JsonProperty("failure") Object failure, + /** Human-readable terminal error. */ @JsonProperty("error") String error, + /** Prompt-safe preview of the completed result. */ @JsonProperty("resultPreview") String resultPreview ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryToolRunOptions.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryToolRunOptions.java new file mode 100644 index 0000000000..cc3e7c8da1 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryToolRunOptions.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; + +/** + * Options for an internal tool-originated factory invocation. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record FactoryToolRunOptions( + /** Per-invocation resource ceiling overrides. */ + @JsonProperty("limits") FactoryRunLimits limits, + /** Run identifier whose journal and progress should seed this resumed run. */ + @JsonProperty("resumeFromRunId") String resumeFromRunId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GhCliAuthInfo.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GhCliAuthInfo.java new file mode 100644 index 0000000000..5bfc896f63 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GhCliAuthInfo.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; + +/** + * Authentication-info input variant for GitHub CLI credentials, carrying host, login, and the `gh auth token` value. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class GhCliAuthInfo extends AuthInfo { + + @JsonProperty("type") + private final String type = "gh-cli"; + + @Override + public String getType() { return type; } + + /** Authentication host. */ + @JsonProperty("host") + private String host; + + /** User login as reported by `gh auth status`. */ + @JsonProperty("login") + private String login; + + /** The token returned by `gh auth token`. Treat as a secret. */ + @JsonProperty("token") + private String token; + + /** 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. */ + @JsonProperty("copilotUser") + private CopilotUserResponse copilotUser; + + public String getHost() { return host; } + public void setHost(String host) { this.host = host; } + + public String getLogin() { return login; } + public void setLogin(String login) { this.login = login; } + + public String getToken() { return token; } + public void setToken(String token) { this.token = token; } + + public CopilotUserResponse getCopilotUser() { return copilotUser; } + public void setCopilotUser(CopilotUserResponse copilotUser) { this.copilotUser = copilotUser; } +} 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 87% 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 index 7d7a1eaf72..b6bef1bffd 100644 --- 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 @@ -40,6 +40,10 @@ public record GitHubTelemetryClientInfo( /** Whether the user is a GitHub/Microsoft staff member. */ @JsonProperty("is_staff") Boolean isStaff, /** Stable machine identifier for the device. */ - @JsonProperty("dev_device_id") String devDeviceId + @JsonProperty("dev_device_id") String devDeviceId, + /** Distinct CPU model names for the host, comma-separated. */ + @JsonProperty("cpu_model") String cpuModel, + /** Number of logical CPU cores on the host. */ + @JsonProperty("cpu_count") Long cpuCount ) { } 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/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTokenAcquireReason.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTokenAcquireReason.java new file mode 100644 index 0000000000..9f18889a03 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTokenAcquireReason.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; + +/** + * Why the runtime is requesting a GitHub credential. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum GitHubTokenAcquireReason { + /** The {@code initial} variant. */ + INITIAL("initial"), + /** The {@code refresh} variant. */ + REFRESH("refresh"); + + private final String value; + GitHubTokenAcquireReason(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static GitHubTokenAcquireReason fromValue(String value) { + for (GitHubTokenAcquireReason v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown GitHubTokenAcquireReason value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTokenAcquireRequest.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTokenAcquireRequest.java new file mode 100644 index 0000000000..59ba9d8ed5 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTokenAcquireRequest.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; + +/** + * Asks the SDK client to acquire a GitHub access token from an opaque callback registration. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record GitHubTokenAcquireRequest( + /** Opaque identifier generated by the SDK for this callback registration. */ + @JsonProperty("registrationId") String registrationId, + /** Effective GitHub host for which the callback must return a token. */ + @JsonProperty("host") String host, + /** Session receiving the token. Absent only before a cloud session has been assigned its id. */ + @JsonProperty("sessionId") String sessionId, + /** Why the runtime is requesting a GitHub credential. */ + @JsonProperty("reason") GitHubTokenAcquireReason reason +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTokenAcquireResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTokenAcquireResult.java new file mode 100644 index 0000000000..a20724c337 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTokenAcquireResult.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; + +/** + * SDK host response to a GitHub credential request. + * + * @since 1.0.0 + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "kind", visible = true) +@JsonSubTypes({ + @JsonSubTypes.Type(value = GitHubTokenAcquireResultToken.class, name = "token"), + @JsonSubTypes.Type(value = GitHubTokenAcquireResultCancelled.class, name = "cancelled") +}) +@JsonIgnoreProperties(ignoreUnknown = true) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public abstract class GitHubTokenAcquireResult { + + /** + * 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/GitHubTokenAcquireResultCancelled.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTokenAcquireResultCancelled.java new file mode 100644 index 0000000000..40ab8d5f74 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTokenAcquireResultCancelled.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 javax.annotation.processing.Generated; + +/** + * Variant {@code cancelled} of {@link GitHubTokenAcquireResult}. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class GitHubTokenAcquireResultCancelled extends GitHubTokenAcquireResult { + + @JsonProperty("kind") + private final String kind = "cancelled"; + + @Override + public String getKind() { return kind; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTokenAcquireResultToken.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTokenAcquireResultToken.java new file mode 100644 index 0000000000..cc250a54fd --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTokenAcquireResultToken.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; + +/** + * Variant {@code token} of {@link GitHubTokenAcquireResult}. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class GitHubTokenAcquireResultToken extends GitHubTokenAcquireResult { + + @JsonProperty("kind") + private final String kind = "token"; + + @Override + public String getKind() { return kind; } + + /** GitHub access token acquired by the SDK host. */ + @JsonProperty("accessToken") + private String accessToken; + + /** OAuth token type. Defaults to bearer when omitted. */ + @JsonProperty("tokenType") + private String tokenType; + + /** Remaining token lifetime in seconds when callback execution completes. It must exceed the one-hour preflight refresh threshold. */ + @JsonProperty("expiresIn") + private Long expiresIn; + + public String getAccessToken() { return accessToken; } + public void setAccessToken(String accessToken) { this.accessToken = accessToken; } + + public String getTokenType() { return tokenType; } + public void setTokenType(String tokenType) { this.tokenType = tokenType; } + + public Long getExpiresIn() { return expiresIn; } + public void setExpiresIn(Long expiresIn) { this.expiresIn = expiresIn; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HMACAuthInfo.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HMACAuthInfo.java new file mode 100644 index 0000000000..06cebd66c5 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HMACAuthInfo.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; + +/** + * Authentication-info input variant for GitHub-internal HMAC auth, carrying the public GitHub host and HMAC secret. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class HMACAuthInfo extends AuthInfo { + + @JsonProperty("type") + private final String type = "hmac"; + + @Override + public String getType() { return type; } + + /** Authentication host. HMAC auth always targets the public GitHub host. */ + @JsonProperty("host") + private String host; + + /** HMAC secret used to sign requests. */ + @JsonProperty("hmac") + private String hmac; + + /** 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. */ + @JsonProperty("copilotUser") + private CopilotUserResponse copilotUser; + + public String getHost() { return host; } + public void setHost(String host) { this.host = host; } + + public String getHmac() { return hmac; } + public void setHmac(String hmac) { this.hmac = hmac; } + + public CopilotUserResponse getCopilotUser() { return copilotUser; } + public void setCopilotUser(CopilotUserResponse copilotUser) { this.copilotUser = copilotUser; } +} 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/src/generated/java/com/github/copilot/generated/rpc/HistoryFileRestoreSkipReason.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryFileRestoreSkipReason.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/HistoryFileRestoreSkipReason.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryFileRestoreSkipReason.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindChangeType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindChangeType.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindChangeType.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindChangeType.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindFilePreview.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindFilePreview.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindFilePreview.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindFilePreview.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindMode.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindMode.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindMode.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindOutcome.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindOutcome.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindOutcome.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindOutcome.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindPoint.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindPoint.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindPoint.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindPoint.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindUnavailableReason.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindUnavailableReason.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindUnavailableReason.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindUnavailableReason.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/HistorySkippedFileRestore.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistorySkippedFileRestore.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/HistorySkippedFileRestore.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistorySkippedFileRestore.java 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/sdk/src/generated/java/com/github/copilot/generated/rpc/HookOrigin.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HookOrigin.java new file mode 100644 index 0000000000..bf3bfd14cd --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HookOrigin.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; + +/** + * Configuration tier that contributed a discovered hook action. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum HookOrigin { + /** The {@code user} variant. */ + USER("user"), + /** The {@code repository} variant. */ + REPOSITORY("repository"), + /** The {@code plugin} variant. */ + PLUGIN("plugin"), + /** The {@code policy} variant. */ + POLICY("policy"); + + private final String value; + HookOrigin(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static HookOrigin fromValue(String value) { + for (HookOrigin v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown HookOrigin value: " + value); + } +} 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 95% 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 index 8d7cd913c3..1a185958de 100644 --- 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 @@ -10,7 +10,7 @@ import javax.annotation.processing.Generated; /** - * Hook event name dispatched through the SDK callback transport. + * Hook event name. Discovery emits the file-configurable subset; SDK callbacks additionally support callback-only events. * * @since 1.0.0 */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HooksDiscoverParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HooksDiscoverParams.java new file mode 100644 index 0000000000..a0e7610394 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HooksDiscoverParams.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; + +/** + * Optional project paths and host-exclusion behavior for server-scoped hook discovery. + * + * @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 HooksDiscoverParams( + /** Optional project directory paths whose trusted repository and project-expanded plugin hooks should be discovered. When omitted or empty, user, managed-policy, and globally enabled installed or explicit plugin hooks are returned without project expansion. */ + @JsonProperty("projectPaths") List projectPaths, + /** When true, omit host-owned user and plugin hook rows and their diagnostics. Managed-policy hooks and trusted repository hooks remain visible, and host disabledHooks still contribute to each remaining row's effective enabled state. This filters sources rather than simulating a host with no settings. */ + @JsonProperty("excludeHostHooks") Boolean excludeHostHooks +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HooksDiscoverResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HooksDiscoverResult.java new file mode 100644 index 0000000000..f30d4837ae --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HooksDiscoverResult.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; + +/** + * Server-discovered hook actions and partial-load diagnostics from user, repository, plugin, and managed-policy sources. Concrete sessions may include additional session-specific hook sources. + * + * @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 HooksDiscoverResult( + /** All discovered hook actions. Byte-identical actions remain separate rows even when they share a disable key. */ + @JsonProperty("hooks") List hooks, + /** Non-fatal source-loading warnings. Discovery remains complete for the affected source, although the source had a recoverable issue. Repository-settings warnings are prefixed with their project path when attribution is available. */ + @JsonProperty("warnings") List warnings, + /** Errors for hook sources or actions that could not be loaded, making the result partially incomplete. Other valid actions are still returned. Project-resolution and repository-settings errors are prefixed with their project path. */ + @JsonProperty("errors") List errors +) { +} 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 78% 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 3da690f47b..e372049403 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 @@ -36,6 +36,8 @@ public record InstalledPlugin( /** Source for direct repo installs (when marketplace is empty) */ @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 + @JsonProperty("source_sha") String sourceSha, + /** Absolute path of the marketplace directory a live plugin was resolved from. Present only on live, never-persisted records — those synthesized at session start for a directory/local marketplace, whose cache_path points at the real plugin directory on disk rather than a copy under the installed-plugins cache. Its presence is what marks a record as live, and no record carrying it is ever written to the persisted installedPlugins key. */ + @JsonProperty("installed_from") String installedFrom ) { } 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 73% 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 index 2f4895690f..2c81e95f2b 100644 --- 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 @@ -30,6 +30,8 @@ public record InstalledPluginInfo( /** Installed version (when reported by the plugin manifest) */ @JsonProperty("version") String version, /** Whether the plugin is currently enabled for new sessions */ - @JsonProperty("enabled") Boolean enabled + @JsonProperty("enabled") Boolean enabled, + /** Absolute path of the marketplace directory a live plugin was resolved from. Present only on live, never-persisted records — a plugin belonging to a directory/local marketplace, which is loaded from its real directory on every pass instead of a copy under the installed-plugins cache. Its presence is what marks a listed plugin as live: such a plugin is always present on disk, so `enabled` is its only meaningful state and it is never "not installed". */ + @JsonProperty("installedFrom") String installedFrom ) { } 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 100% 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 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 98% 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 index b846fcf37c..a75733cb3a 100644 --- 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 @@ -31,6 +31,7 @@ public record LlmInferenceHttpRequestStartRequest( @JsonProperty("method") String method, /** Absolute request URL. */ @JsonProperty("url") String url, + /** HTTP request headers, preserving multiple values per name. */ @JsonProperty("headers") Map> headers, /** 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. */ @JsonProperty("transport") LlmInferenceHttpRequestStartTransport transport, 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 95% 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 index 69c26221b8..dddc6a315a 100644 --- 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 @@ -32,6 +32,7 @@ public record LlmInferenceHttpResponseStartParams( @JsonProperty("status") Long status, /** Optional HTTP status reason phrase. */ @JsonProperty("statusText") String statusText, + /** HTTP response headers, preserving multiple values per name. */ @JsonProperty("headers") Map> headers ) { } 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 0000000000..2018f62ce0 --- /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/sdk/src/generated/java/com/github/copilot/generated/rpc/McpElicitationFormMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpElicitationFormMode.java new file mode 100644 index 0000000000..8bb655e3e8 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpElicitationFormMode.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 javax.annotation.processing.Generated; + +/** + * Structured MCP elicitation mode. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum McpElicitationFormMode { + /** The {@code form} variant. */ + FORM("form"); + + private final String value; + McpElicitationFormMode(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static McpElicitationFormMode fromValue(String value) { + for (McpElicitationFormMode v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown McpElicitationFormMode value: " + value); + } +} 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/sdk/src/generated/java/com/github/copilot/generated/rpc/McpFailedServer.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpFailedServer.java new file mode 100644 index 0000000000..36009e4915 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpFailedServer.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; + +/** + * MCP server whose connection attempt failed. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record McpFailedServer( + /** The config key of the server that failed to connect. */ + @JsonProperty("name") String name, + /** The captured connection failure detail. */ + @JsonProperty("error") String error +) { +} 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 100% 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 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 100% 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 diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpInstallPlan.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpInstallPlan.java new file mode 100644 index 0000000000..7274ce2b7f --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpInstallPlan.java @@ -0,0 +1,48 @@ +/*--------------------------------------------------------------------------------------------- + * 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 normalised, inert description of what installing an MCP server would involve. Carries no raw card, no install specification, and no secret value. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record McpInstallPlan( + /** Opaque, runtime-instance scoped, TTL-bound, single-use handle for this plan. Rejected when stale, replayed, or presented to a different runtime instance. Never logged. */ + @JsonProperty("planHandle") String planHandle, + /** ISO 8601 timestamp after which the plan handle is stale and will be rejected. Abandoning a plan needs no call: an unused handle simply expires, so cancellation before commit is side-effect free. */ + @JsonProperty("planHandleExpiresAt") String planHandleExpiresAt, + /** Normalised identity of the server the plan would install. */ + @JsonProperty("identity") McpPlanResourceIdentity identity, + /** Origin and semantic digest of the exact validated JSON MCP card content bound to this plan. */ + @JsonProperty("provenance") McpPlanProvenance provenance, + /** Every eligible transport, so a host can present an explicit choice. A completed plan always has at least one; when none is eligible, planning returns `CatalogUnavailableTransportError` instead. */ + @JsonProperty("transportChoices") List transportChoices, + /** Identifier of the choice the runtime would pick by default. Omitted when there is no eligible transport, or when the runtime expresses no preference. */ + @JsonProperty("recommendedTransportChoiceId") String recommendedTransportChoiceId, + /** Configuration scope and key the plan would write to. */ + @JsonProperty("target") McpPlanTarget target, + /** Outcome of evaluating the server against registry and enterprise policy. */ + @JsonProperty("policy") McpPlanPolicyResult policy, + /** The configuration changes installing would make, described rather than serialised, so the mutable configuration payload stays behind the runtime boundary. */ + @JsonProperty("configurationChanges") List configurationChanges, + /** Whether applying this plan would require an MCP reload to take effect. Planning itself never reloads. */ + @JsonProperty("reloadRequired") Boolean reloadRequired, + /** Whether the plan cannot be applied without further input, because a required value has no default or a secret must be supplied. */ + @JsonProperty("requiresInteractiveConfiguration") Boolean requiresInteractiveConfiguration +) { +} 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/sdk/src/generated/java/com/github/copilot/generated/rpc/McpOauthProbeNeedsAuthReason.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpOauthProbeNeedsAuthReason.java new file mode 100644 index 0000000000..967a01aabc --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpOauthProbeNeedsAuthReason.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; + +/** + * Why a passive MCP OAuth probe determined authentication is needed. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum McpOauthProbeNeedsAuthReason { + /** The {@code initial} variant. */ + INITIAL("initial"), + /** The {@code refresh} variant. */ + REFRESH("refresh"), + /** The {@code upscope} variant. */ + UPSCOPE("upscope"); + + private final String value; + McpOauthProbeNeedsAuthReason(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static McpOauthProbeNeedsAuthReason fromValue(String value) { + for (McpOauthProbeNeedsAuthReason v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown McpOauthProbeNeedsAuthReason value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpOauthProbeResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpOauthProbeResult.java new file mode 100644 index 0000000000..1a94e17142 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpOauthProbeResult.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.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import javax.annotation.processing.Generated; + +/** + * Passive MCP OAuth probe result. `authenticated` means the server accepted the probe request while an OAuth-origin access token was attached; it does not prove the server required or independently validated that token. The probe does not make a second unauthenticated request. Failed is an expected probe-domain outcome; JSON-RPC errors are reserved for API-call failures. + * + * @since 1.0.0 + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "status", visible = true) +@JsonSubTypes({ + @JsonSubTypes.Type(value = McpOauthProbeResultNoAuthRequired.class, name = "no-auth-required"), + @JsonSubTypes.Type(value = McpOauthProbeResultAuthenticated.class, name = "authenticated"), + @JsonSubTypes.Type(value = McpOauthProbeResultNeedsAuth.class, name = "needs-auth"), + @JsonSubTypes.Type(value = McpOauthProbeResultFailed.class, name = "failed") +}) +@JsonIgnoreProperties(ignoreUnknown = true) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public abstract class McpOauthProbeResult { + + /** + * Returns the discriminator value for this variant. + * + * @return the status discriminator + */ + public abstract String getStatus(); +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpOauthProbeResultAuthenticated.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpOauthProbeResultAuthenticated.java new file mode 100644 index 0000000000..8af0d6ca4e --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpOauthProbeResultAuthenticated.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.generated.McpOauthHttpResponse; +import javax.annotation.processing.Generated; + +/** + * Variant {@code authenticated} of {@link McpOauthProbeResult}. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class McpOauthProbeResultAuthenticated extends McpOauthProbeResult { + + @JsonProperty("status") + private final String status = "authenticated"; + + @Override + public String getStatus() { return status; } + + /** HTTP response returned by the server. */ + @JsonProperty("httpResponse") + private McpOauthHttpResponse httpResponse; + + public McpOauthHttpResponse getHttpResponse() { return httpResponse; } + public void setHttpResponse(McpOauthHttpResponse httpResponse) { this.httpResponse = httpResponse; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpOauthProbeResultFailed.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpOauthProbeResultFailed.java new file mode 100644 index 0000000000..a613265878 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpOauthProbeResultFailed.java @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * 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.generated.McpOauthHttpResponse; +import javax.annotation.processing.Generated; + +/** + * Variant {@code failed} of {@link McpOauthProbeResult}. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class McpOauthProbeResultFailed extends McpOauthProbeResult { + + @JsonProperty("status") + private final String status = "failed"; + + @Override + public String getStatus() { return status; } + + /** Human-readable probe failure detail. */ + @JsonProperty("error") + private String error; + + /** HTTP response returned by the server, when the probe reached the server and captured the complete response. */ + @JsonProperty("httpResponse") + private McpOauthHttpResponse httpResponse; + + public String getError() { return error; } + public void setError(String error) { this.error = error; } + + public McpOauthHttpResponse getHttpResponse() { return httpResponse; } + public void setHttpResponse(McpOauthHttpResponse httpResponse) { this.httpResponse = httpResponse; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpOauthProbeResultNeedsAuth.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpOauthProbeResultNeedsAuth.java new file mode 100644 index 0000000000..4cc54dfbe2 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpOauthProbeResultNeedsAuth.java @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------------------------- + * 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.generated.McpOauthHttpResponse; +import com.github.copilot.generated.McpOauthWWWAuthenticateParams; +import javax.annotation.processing.Generated; + +/** + * Variant {@code needs-auth} of {@link McpOauthProbeResult}. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class McpOauthProbeResultNeedsAuth extends McpOauthProbeResult { + + @JsonProperty("status") + private final String status = "needs-auth"; + + @Override + public String getStatus() { return status; } + + /** HTTP 401 or 403 response returned by the server. */ + @JsonProperty("httpResponse") + private McpOauthHttpResponse httpResponse; + + /** Why authentication is needed. */ + @JsonProperty("reason") + private McpOauthProbeNeedsAuthReason reason; + + /** Parsed WWW-Authenticate challenge parameters, when present and parseable. */ + @JsonProperty("wwwAuthenticateParams") + private McpOauthWWWAuthenticateParams wwwAuthenticateParams; + + public McpOauthHttpResponse getHttpResponse() { return httpResponse; } + public void setHttpResponse(McpOauthHttpResponse httpResponse) { this.httpResponse = httpResponse; } + + public McpOauthProbeNeedsAuthReason getReason() { return reason; } + public void setReason(McpOauthProbeNeedsAuthReason reason) { this.reason = reason; } + + public McpOauthWWWAuthenticateParams getWwwAuthenticateParams() { return wwwAuthenticateParams; } + public void setWwwAuthenticateParams(McpOauthWWWAuthenticateParams wwwAuthenticateParams) { this.wwwAuthenticateParams = wwwAuthenticateParams; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpOauthProbeResultNoAuthRequired.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpOauthProbeResultNoAuthRequired.java new file mode 100644 index 0000000000..71ddbfc2ac --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpOauthProbeResultNoAuthRequired.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.generated.McpOauthHttpResponse; +import javax.annotation.processing.Generated; + +/** + * Variant {@code no-auth-required} of {@link McpOauthProbeResult}. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class McpOauthProbeResultNoAuthRequired extends McpOauthProbeResult { + + @JsonProperty("status") + private final String status = "no-auth-required"; + + @Override + public String getStatus() { return status; } + + /** HTTP response returned by the server. */ + @JsonProperty("httpResponse") + private McpOauthHttpResponse httpResponse; + + public McpOauthHttpResponse getHttpResponse() { return httpResponse; } + public void setHttpResponse(McpOauthHttpResponse httpResponse) { this.httpResponse = httpResponse; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanConfigurationChange.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanConfigurationChange.java new file mode 100644 index 0000000000..d419772111 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanConfigurationChange.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 java.util.List; +import javax.annotation.processing.Generated; + +/** + * One change applying the plan would make, described rather than serialised so the configuration payload stays behind the runtime boundary. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record McpPlanConfigurationChange( + /** Whether the change would create a new entry or modify an existing one. */ + @JsonProperty("operation") McpPlanConfigurationOperation operation, + /** Scope the change would be written to. */ + @JsonProperty("scope") McpPlanScope scope, + /** Configuration key the change applies to. */ + @JsonProperty("configKey") String configKey, + /** Names of the configuration fields the change would set, without their values. */ + @JsonProperty("changedFields") List changedFields, + /** Secret placeholders the written configuration would reference. The constrained placeholder type cannot carry a literal secret value. */ + @JsonProperty("secretReferences") List secretReferences +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanConfigurationOperation.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanConfigurationOperation.java new file mode 100644 index 0000000000..a661db48ab --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanConfigurationOperation.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; + +/** + * Whether a planned configuration change would create or modify an entry + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum McpPlanConfigurationOperation { + /** The {@code add} variant. */ + ADD("add"), + /** The {@code update} variant. */ + UPDATE("update"); + + private final String value; + McpPlanConfigurationOperation(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static McpPlanConfigurationOperation fromValue(String value) { + for (McpPlanConfigurationOperation v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown McpPlanConfigurationOperation value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanInstallParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanInstallParams.java new file mode 100644 index 0000000000..7b182fe2d6 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanInstallParams.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; + +/** + * A side-effect-free request for an MCP install plan. Computing a plan never writes configuration, stores a secret, or reloads MCP servers. + * + * @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 McpPlanInstallParams( + /** Protocol version and capabilities the caller requires. */ + @JsonProperty("contract") CatalogClientContract contract, + /** What to plan: either a candidate handle from a previous search, or a card supplied directly. */ + @JsonProperty("source") Object source, + /** Configuration scope the plan targets. Defaults to user scope when omitted. */ + @JsonProperty("scope") McpPlanScope scope +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanInstallPlanned.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanInstallPlanned.java new file mode 100644 index 0000000000..571353a384 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanInstallPlanned.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; + +/** + * A computed MCP install plan. Nothing has been applied: the plan describes what installing would change, and the plan handle is what a later apply operation would consume. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class McpPlanInstallPlanned extends McpPlanInstallResult { + + @JsonProperty("kind") + private final String kind = "planned"; + + @Override + public String getKind() { return kind; } + + /** The normalised plan. */ + @JsonProperty("plan") + private McpInstallPlan plan; + + /** Protocol version and capabilities the runtime honoured. */ + @JsonProperty("negotiated") + private CatalogNegotiatedContract negotiated; + + public McpInstallPlan getPlan() { return plan; } + public void setPlan(McpInstallPlan plan) { this.plan = plan; } + + public CatalogNegotiatedContract getNegotiated() { return negotiated; } + public void setNegotiated(CatalogNegotiatedContract negotiated) { this.negotiated = negotiated; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanInstallResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanInstallResult.java new file mode 100644 index 0000000000..d257703477 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanInstallResult.java @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * Outcome of an mcp.planInstall call: either a normalised plan, or one typed refusal. Nothing is written in either case. + * + * @since 1.0.0 + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "kind", visible = true) +@JsonSubTypes({ + @JsonSubTypes.Type(value = McpPlanInstallPlanned.class, name = "planned"), + @JsonSubTypes.Type(value = CatalogNegotiationRefusedError.class, name = "negotiation-refused"), + @JsonSubTypes.Type(value = CatalogHandleRejectedError.class, name = "handle-rejected"), + @JsonSubTypes.Type(value = CatalogInvalidRequestError.class, name = "invalid-request"), + @JsonSubTypes.Type(value = CatalogAuthenticationRequiredError.class, name = "authentication-required"), + @JsonSubTypes.Type(value = CatalogPolicyRejectedError.class, name = "policy-rejected"), + @JsonSubTypes.Type(value = CatalogNetworkFailureError.class, name = "network-failure"), + @JsonSubTypes.Type(value = CatalogUnsafeRetrievalError.class, name = "unsafe-retrieval"), + @JsonSubTypes.Type(value = CatalogMalformedCardError.class, name = "malformed-card"), + @JsonSubTypes.Type(value = CatalogContractViolationError.class, name = "contract-violation"), + @JsonSubTypes.Type(value = CatalogUnavailableTransportError.class, name = "unavailable-transport"), + @JsonSubTypes.Type(value = CatalogNotInstallableError.class, name = "not-installable"), + @JsonSubTypes.Type(value = CatalogUnavailableError.class, name = "unavailable") +}) +@JsonIgnoreProperties(ignoreUnknown = true) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public abstract class McpPlanInstallResult { + + /** + * 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/McpPlanPolicyDecision.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanPolicyDecision.java new file mode 100644 index 0000000000..be32c283a0 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanPolicyDecision.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; + +/** + * What policy decided for a planned server + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum McpPlanPolicyDecision { + /** The {@code allowed} variant. */ + ALLOWED("allowed"), + /** The {@code blocked} variant. */ + BLOCKED("blocked"), + /** The {@code requires-approval} variant. */ + REQUIRES_APPROVAL("requires-approval"); + + private final String value; + McpPlanPolicyDecision(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static McpPlanPolicyDecision fromValue(String value) { + for (McpPlanPolicyDecision v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown McpPlanPolicyDecision value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanPolicyResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanPolicyResult.java new file mode 100644 index 0000000000..ee8d2ff0c1 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanPolicyResult.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; + +/** + * Outcome of evaluating the planned server against registry and enterprise policy. Evaluation is read-only. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record McpPlanPolicyResult( + /** What policy decided for this server. */ + @JsonProperty("decision") McpPlanPolicyDecision decision, + /** Which authority produced the decision. */ + @JsonProperty("source") McpPlanPolicySource source, + /** Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. */ + @JsonProperty("reason") String reason +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanPolicySource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanPolicySource.java new file mode 100644 index 0000000000..f6c0498f3b --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanPolicySource.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; + +/** + * Which authority produced a policy decision + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum McpPlanPolicySource { + /** The {@code none} variant. */ + NONE("none"), + /** The {@code enterprise-allowlist} variant. */ + ENTERPRISE_ALLOWLIST("enterprise-allowlist"), + /** The {@code registry-policy} variant. */ + REGISTRY_POLICY("registry-policy"), + /** The {@code local-trust} variant. */ + LOCAL_TRUST("local-trust"); + + private final String value; + McpPlanPolicySource(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static McpPlanPolicySource fromValue(String value) { + for (McpPlanPolicySource v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown McpPlanPolicySource value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanProvenance.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanProvenance.java new file mode 100644 index 0000000000..5b7d6531d3 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanProvenance.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; + +/** + * Provenance of the exact validated JSON MCP card content bound privately to a completed plan and its opaque handle. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record McpPlanProvenance( + /** Authority associated with the validated card, without path, query, or credentials. Inert untrusted data. */ + @JsonProperty("authority") String authority, + /** ISO 8601 timestamp at which the runtime completed strict parsing and schema validation of the card content. */ + @JsonProperty("validatedAt") String validatedAt, + /** Semantic digest of the exact validated JSON content bound to the plan handle. */ + @JsonProperty("cardDigest") CardDigest cardDigest, + /** JSON MCP media type the validated card was interpreted as. */ + @JsonProperty("mediaType") McpServerCardMediaType mediaType +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanResourceIdentity.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanResourceIdentity.java new file mode 100644 index 0000000000..abe525c1dc --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanResourceIdentity.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; + +/** + * Normalised identity of the MCP server a plan targets, independent of how the card spelled it. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record McpPlanResourceIdentity( + /** Canonical, normalised name of the server, for example `io.github.owner/server`. */ + @JsonProperty("canonicalName") String canonicalName, + /** Local configuration key the server would be recorded under. */ + @JsonProperty("serverName") String serverName, + /** Version advertised by the card, when it declares one. */ + @JsonProperty("version") String version, + /** Registry identifier of the server, when it came from a registry. */ + @JsonProperty("registryId") String registryId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanScope.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanScope.java new file mode 100644 index 0000000000..41f5d72016 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanScope.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 javax.annotation.processing.Generated; + +/** + * Configuration scope an MCP install plan targets + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum McpPlanScope { + /** The {@code user} variant. */ + USER("user"); + + private final String value; + McpPlanScope(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static McpPlanScope fromValue(String value) { + for (McpPlanScope v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown McpPlanScope value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanTarget.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanTarget.java new file mode 100644 index 0000000000..95362a4cca --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpPlanTarget.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; + +/** + * Where a plan would be written. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record McpPlanTarget( + /** Configuration scope the plan targets. */ + @JsonProperty("scope") McpPlanScope scope, + /** Configuration key the server would be recorded under within that scope. */ + @JsonProperty("configKey") String configKey +) { +} 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 100% 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 diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpServerCardMediaType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpServerCardMediaType.java new file mode 100644 index 0000000000..4258f7d4d4 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpServerCardMediaType.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; + +/** + * JSON MCP card media type accepted for install planning + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum McpServerCardMediaType { + /** The {@code application/mcp-server-card+json} variant. */ + APPLICATION_MCP_SERVER_CARD_JSON("application/mcp-server-card+json"), + /** The {@code application/mcp-server+json} variant. */ + APPLICATION_MCP_SERVER_JSON("application/mcp-server+json"); + + private final String value; + McpServerCardMediaType(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static McpServerCardMediaType fromValue(String value) { + for (McpServerCardMediaType v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown McpServerCardMediaType value: " + value); + } +} 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 100% 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 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/sdk/src/generated/java/com/github/copilot/generated/rpc/McpTaskMetadata.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpTaskMetadata.java new file mode 100644 index 0000000000..1b7fccb29f --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpTaskMetadata.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; + +/** + * Metadata controlling an MCP task's lifetime. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record McpTaskMetadata( + /** Task time-to-live. */ + @JsonProperty("ttl") Long ttl +) { +} 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/src/generated/java/com/github/copilot/generated/rpc/MemoryConfiguration.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/MemoryConfiguration.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/MemoryConfiguration.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/MemoryConfiguration.java 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 55% 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 f002df540d..a652e8f4f6 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,9 +34,19 @@ 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, + /** Context-window tiers this model offers, when the provider advertises them independently of tiered token pricing. Copilot models carry their tiers in `billing.tokenPrices`; a provider that has no pricing to publish (an agent host reached over AHP, for example) declares them here instead, so the model picker can still offer the tier toggle. */ + @JsonProperty("supportedContextTiers") List supportedContextTiers, /** Model capability category for grouping in the model picker */ @JsonProperty("modelPickerCategory") ModelPickerCategory modelPickerCategory, /** Relative cost tier for token-based billing users */ - @JsonProperty("modelPickerPriceCategory") ModelPickerPriceCategory modelPickerPriceCategory + @JsonProperty("modelPickerPriceCategory") ModelPickerPriceCategory modelPickerPriceCategory, + /** Warning text the service requires hosts to surface for this model. Present only when the service published at least one warning. */ + @JsonProperty("warningText") ModelWarningText warningText, + /** Informational notices the service published for this model, such as an upcoming change or a recommended alternative. Present only when the service published at least one notice. Hosts should surface these without implying anything is wrong with the model. */ + @JsonProperty("infoMessages") List infoMessages, + /** Warnings the service published for this model, such as a deprecated client version. Present only when the service published at least one warning. The model remains usable; hosts should surface these as advisory rather than blocking. */ + @JsonProperty("warningMessages") List warningMessages ) { } 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 100% 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 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 75% 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 087ca1c15a..d44087dcff 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 @@ -28,6 +28,8 @@ public record ModelBillingPromo( /** 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 when present. */ - @JsonProperty("message") String message + @JsonProperty("message") String message, + /** Whether the service asked hosts to give this promotion a prominent surface, such as a dedicated banner, in addition to listing it with the model. `true` requests that surface and `false` asks for the model list only. Absent means the service expressed no preference — for example a response that predates the field — so hosts should apply their own default rather than read it as `false`. */ + @JsonProperty("showBanner") Boolean showBanner ) { } 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 93% 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 index 56e06fe22f..e6eae8c0e5 100644 --- 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 @@ -31,6 +31,8 @@ public record ModelBillingTokenPrices( @JsonProperty("cacheReadPrice") Double cacheReadPrice, /** AI Credits cost per billing batch of cache-write (cache creation) tokens. */ @JsonProperty("cacheWritePrice") Double cacheWritePrice, + /** AI Credits cost per billing batch of 1-hour cache-write (cache creation) tokens. */ + @JsonProperty("cacheWrite1hPrice") Double cacheWrite1hPrice, /** Number of tokens per standard billing batch */ @JsonProperty("batchSize") Long batchSize, /** Use maxPromptTokens instead. Prompt token budget for the default tier. The total context window is this value plus the model's max_output_tokens. */ 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 92% 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 index bb67515793..7d108d3708 100644 --- 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 @@ -31,6 +31,8 @@ public record ModelBillingTokenPricesLongContext( @JsonProperty("cacheReadPrice") Double cacheReadPrice, /** AI Credits cost per billing batch of cache-write (cache creation) tokens. */ @JsonProperty("cacheWritePrice") Double cacheWritePrice, + /** AI Credits cost per billing batch of 1-hour cache-write (cache creation) tokens. */ + @JsonProperty("cacheWrite1hPrice") Double cacheWrite1hPrice, /** 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. */ @JsonProperty("contextMax") Long contextMax, /** Prompt token budget for the long context tier. The total context window is this value plus the model's max_output_tokens. */ 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/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelChangeSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelChangeSource.java new file mode 100644 index 0000000000..6e5d4ea8da --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelChangeSource.java @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * Origin of an effective session model change. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum ModelChangeSource { + /** The {@code model_command} variant. */ + MODEL_COMMAND("model_command"), + /** The {@code settings_command} variant. */ + SETTINGS_COMMAND("settings_command"), + /** The {@code config_command} variant. */ + CONFIG_COMMAND("config_command"), + /** The {@code model_picker} variant. */ + MODEL_PICKER("model_picker"), + /** The {@code managed_settings} variant. */ + MANAGED_SETTINGS("managed_settings"), + /** The {@code repo_settings} variant. */ + REPO_SETTINGS("repo_settings"), + /** The {@code startup} variant. */ + STARTUP("startup"), + /** The {@code agent} variant. */ + AGENT("agent"), + /** The {@code plan_mode} variant. */ + PLAN_MODE("plan_mode"), + /** The {@code automatic} variant. */ + AUTOMATIC("automatic"), + /** The {@code sdk} variant. */ + SDK("sdk"); + + private final String value; + ModelChangeSource(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static ModelChangeSource fromValue(String value) { + for (ModelChangeSource v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown ModelChangeSource value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelMessage.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelMessage.java new file mode 100644 index 0000000000..35e8a17386 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelMessage.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 service-published message about a model, carrying a stable machine-readable code alongside human-readable text. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record ModelMessage( + /** Stable machine-readable identifier for the message, such as `client_version_deprecated`. Hosts can key custom presentation off this; unrecognized codes should fall back to displaying `message`. */ + @JsonProperty("code") String code, + /** Human-readable message text intended for display to the user. */ + @JsonProperty("message") String message +) { +} 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/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelPickerPersistenceRequest.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelPickerPersistenceRequest.java new file mode 100644 index 0000000000..48612cdfa5 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelPickerPersistenceRequest.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; + +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record ModelPickerPersistenceRequest( + /** Filesystem and environment context used to resolve settings persistence. */ + @JsonProperty("settingsContext") ModelPickerSettingsContext settingsContext, + /** Whether reasoning effort was explicitly selected and should be persisted. */ + @JsonProperty("reasoningEffortExplicit") Boolean reasoningEffortExplicit, + /** Whether context tier was explicitly selected and should be persisted. */ + @JsonProperty("contextTierExplicit") Boolean contextTierExplicit +) { +} 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/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelPickerSettingsContext.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelPickerSettingsContext.java new file mode 100644 index 0000000000..04eec742eb --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelPickerSettingsContext.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; + +/** + * Filesystem and environment context used to resolve model-picker settings. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record ModelPickerSettingsContext( + /** Optional Copilot configuration directory containing persisted settings. */ + @JsonProperty("configDir") String configDir, + /** User home directory used when resolving persisted settings. */ + @JsonProperty("homeDirectory") String homeDirectory, + /** Environment variables consulted while resolving model-picker settings. */ + @JsonProperty("environment") Map environment +) { +} 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/ModelSwitchConfirmation.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelSwitchConfirmation.java new file mode 100644 index 0000000000..4a75be2b10 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelSwitchConfirmation.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; + +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record ModelSwitchConfirmation( + /** Display name of the model that requires compaction confirmation. */ + @JsonProperty("targetModelDisplayName") String targetModelDisplayName, + /** Current conversation token count before switching models. */ + @JsonProperty("currentTokens") Double currentTokens, + /** Target model token limit used by the compaction preflight. */ + @JsonProperty("targetLimit") Double targetLimit +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelWarningText.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelWarningText.java new file mode 100644 index 0000000000..817be420c5 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelWarningText.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; + +/** + * Service-published warning text that hosts should display when presenting a model. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record ModelWarningText( + /** Data-retention warning for the model. The text may contain Markdown links and should be rendered as Markdown when supported. */ + @JsonProperty("dataRetention") String dataRetention +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ModelsGetBuiltInCatalogResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelsGetBuiltInCatalogResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ModelsGetBuiltInCatalogResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelsGetBuiltInCatalogResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ModelsListParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelsListParams.java similarity index 76% rename from java/src/generated/java/com/github/copilot/generated/rpc/ModelsListParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelsListParams.java index 3366ff61c4..d5bcad93ac 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/ModelsListParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelsListParams.java @@ -24,7 +24,9 @@ @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. */ + /** Opaque account identifier returned by `account.getAllUsers`. When omitted, the current account is used. */ + @JsonProperty("selectionId") String selectionId, + /** GitHub token accepted for compatibility with existing SDK clients. When provided, resolves this token instead of using the current account. */ @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/sdk/src/generated/java/com/github/copilot/generated/rpc/NamedProviderConfig.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/NamedProviderConfig.java new file mode 100644 index 0000000000..dc57bca4f3 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/NamedProviderConfig.java @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * External SDK input for a named custom model provider. Ingested by the native protocol boundary before host dispatch. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record NamedProviderConfig( + /** Unique provider name used to qualify model selection IDs. */ + @JsonProperty("name") String name, + /** Provider protocol family. */ + @JsonProperty("type") ProviderConfigType type, + /** Wire API used to communicate with the provider. */ + @JsonProperty("wireApi") ProviderConfigWireApi wireApi, + /** Transport used to communicate with the provider. */ + @JsonProperty("transport") ProviderConfigTransport transport, + /** Base URL for provider API requests. */ + @JsonProperty("baseUrl") String baseUrl, + /** Static API key used to authenticate provider requests. */ + @JsonProperty("apiKey") String apiKey, + /** Static bearer token used to authenticate provider requests. */ + @JsonProperty("bearerToken") String bearerToken, + /** Azure authentication configuration for the provider. */ + @JsonProperty("azure") ProviderConfigAzure azure, + /** Additional HTTP headers included with provider requests. */ + @JsonProperty("headers") Map headers, + /** Whether the host supplies bearer tokens dynamically. */ + @JsonProperty("hasBearerTokenProvider") Boolean hasBearerTokenProvider +) { +} 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 92% 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 index 2864bbd99b..3efd398a0c 100644 --- 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 @@ -22,7 +22,9 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record OptionsUpdateAdditionalContentExclusionPolicy( + /** Content-exclusion rules to apply. */ @JsonProperty("rules") List rules, + /** Opaque policy update timestamp supplied by the host. */ @JsonProperty("last_updated_at") Object lastUpdatedAt, /** Allowed values for the `OptionsUpdateAdditionalContentExclusionPolicyScope` enumeration. */ @JsonProperty("scope") OptionsUpdateAdditionalContentExclusionPolicyScope scope 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 89% 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 index 135a7c7f85..047dc9fa01 100644 --- 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 @@ -22,8 +22,11 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record OptionsUpdateAdditionalContentExclusionPolicyRule( + /** Path patterns covered by this rule. */ @JsonProperty("paths") List paths, + /** Conditions of which at least one must match. */ @JsonProperty("ifAnyMatch") List ifAnyMatch, + /** Conditions none of which may match. */ @JsonProperty("ifNoneMatch") List ifNoneMatch, /** Source descriptor for a `session.options.update` content-exclusion rule, with source name and type. */ @JsonProperty("source") OptionsUpdateAdditionalContentExclusionPolicyRuleSource source 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 93% 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 index a363722801..43ec75b1a1 100644 --- 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 @@ -21,7 +21,9 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record OptionsUpdateAdditionalContentExclusionPolicyRuleSource( + /** Name of the policy source. */ @JsonProperty("name") String name, + /** Type of the policy source. */ @JsonProperty("type") String type ) { } 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 0000000000..5200f4cd51 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionDecisionContext.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 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, + /** Whether the responding client could ask a user interactively, was running headlessly, or had no response path. Omit when the client cannot determine this authoritatively. */ + @JsonProperty("responseCapability") PermissionResponseCapability responseCapability +) { +} 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 0000000000..d46c460a29 --- /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 0000000000..56f5793c5e --- /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 assisted_approval} variant. */ + ASSISTED_APPROVAL("assisted_approval"), + /** 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 0000000000..c6d2db8c5f --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionDecisionSurface.java @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * 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 acp} variant. */ + ACP("acp"), + /** 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/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionMode.java new file mode 100644 index 0000000000..1f4ea9b6bc --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionMode.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; + +/** + * Current or requested permission mode. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum PermissionMode { + /** The {@code manual} variant. */ + MANUAL("manual"), + /** The {@code assisted} variant. */ + ASSISTED("assisted"), + /** The {@code allow-all} variant. */ + ALLOW_ALL("allow-all"); + + private final String value; + PermissionMode(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static PermissionMode fromValue(String value) { + for (PermissionMode v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown PermissionMode value: " + value); + } +} 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/PermissionModeSource.java similarity index 68% 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/PermissionModeSource.java index a7ff9ae9aa..534760c746 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/PermissionsSetAllowAllSource.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionModeSource.java @@ -10,30 +10,32 @@ import javax.annotation.processing.Generated; /** - * Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers. + * Optional source for permission-mode telemetry. Defaults to `rpc` when omitted for SDK callers. * * @since 1.0.0 */ @javax.annotation.processing.Generated("copilot-sdk-codegen") -public enum PermissionsSetAllowAllSource { +public enum PermissionModeSource { /** The {@code cli_flag} variant. */ CLI_FLAG("cli_flag"), /** The {@code slash_command} variant. */ SLASH_COMMAND("slash_command"), /** The {@code autopilot_confirmation} variant. */ AUTOPILOT_CONFIRMATION("autopilot_confirmation"), + /** The {@code user_setting} variant. */ + USER_SETTING("user_setting"), /** The {@code rpc} variant. */ RPC("rpc"); private final String value; - PermissionsSetAllowAllSource(String value) { this.value = value; } + PermissionModeSource(String value) { this.value = value; } @com.fasterxml.jackson.annotation.JsonValue public String getValue() { return value; } @com.fasterxml.jackson.annotation.JsonCreator - public static PermissionsSetAllowAllSource fromValue(String value) { - for (PermissionsSetAllowAllSource v : values()) { + public static PermissionModeSource fromValue(String value) { + for (PermissionModeSource v : values()) { if (v.value.equals(value)) return v; } - throw new IllegalArgumentException("Unknown PermissionsSetAllowAllSource value: " + value); + throw new IllegalArgumentException("Unknown PermissionModeSource value: " + value); } } 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 80% 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 index 29aef6c66f..56dbd73dd8 100644 --- 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 @@ -24,7 +24,7 @@ public record PermissionPathsConfig( /** If true, the runtime allows access to all paths without prompting. Equivalent to constructing an UnrestrictedPathManager. */ @JsonProperty("unrestricted") Boolean unrestricted, - /** 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). */ + /** Additional directories to allow tool access to (in addition to the session's working directory). Conventional `.github/skills/` and `.github/agents/` definitions under them also join the session catalogs when their subsystem gates are enabled, so supplying a directory is a trust decision for configuration stored there. When `unrestricted` is true, these are still pre-populated on the UnrestrictedPathManager so they remain visible via getDirectories() (e.g. for @-mention completion). */ @JsonProperty("additionalDirectories") List additionalDirectories, /** Whether to include the system temp directory in the allowed list (defaults to true). Ignored when `unrestricted` is true. */ @JsonProperty("includeTempDirectory") Boolean includeTempDirectory, diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionResponseCapability.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionResponseCapability.java new file mode 100644 index 0000000000..c52bfd3234 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionResponseCapability.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; + +/** + * Response capability available to the client when it settled a permission request. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum PermissionResponseCapability { + /** The {@code interactive} variant. */ + INTERACTIVE("interactive"), + /** The {@code headless} variant. */ + HEADLESS("headless"), + /** The {@code none} variant. */ + NONE("none"); + + private final String value; + PermissionResponseCapability(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static PermissionResponseCapability fromValue(String value) { + for (PermissionResponseCapability v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown PermissionResponseCapability value: " + value); + } +} 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/PermissionsConfigureAdditionalContentExclusionPolicy.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionsConfigureAdditionalContentExclusionPolicy.java similarity index 92% 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 index 249c7598d9..e451bfe490 100644 --- 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 @@ -22,7 +22,9 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record PermissionsConfigureAdditionalContentExclusionPolicy( + /** Content-exclusion rules to apply. */ @JsonProperty("rules") List rules, + /** Opaque policy update timestamp supplied by the host. */ @JsonProperty("last_updated_at") Object lastUpdatedAt, /** Allowed values for the `PermissionsConfigureAdditionalContentExclusionPolicyScope` enumeration. */ @JsonProperty("scope") PermissionsConfigureAdditionalContentExclusionPolicyScope scope 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 90% 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 index b1afc50fcd..4515f55d7c 100644 --- 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 @@ -22,8 +22,11 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record PermissionsConfigureAdditionalContentExclusionPolicyRule( + /** Path patterns covered by this rule. */ @JsonProperty("paths") List paths, + /** Conditions of which at least one must match. */ @JsonProperty("ifAnyMatch") List ifAnyMatch, + /** Conditions none of which may match. */ @JsonProperty("ifNoneMatch") List ifNoneMatch, /** Source descriptor for a `session.permissions.configure` content-exclusion rule, with source name and type. */ @JsonProperty("source") PermissionsConfigureAdditionalContentExclusionPolicyRuleSource source 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 93% 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 index f592ae7991..e4eae24027 100644 --- 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 @@ -21,7 +21,9 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record PermissionsConfigureAdditionalContentExclusionPolicyRuleSource( + /** Name of the policy source. */ @JsonProperty("name") String name, + /** Type of the policy source. */ @JsonProperty("type") String type ) { } 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/PermissionsSetApproveAllSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionsSetApproveAllSource.java similarity index 95% 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 index b86b09dfa3..0bf52d53df 100644 --- 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 @@ -22,6 +22,8 @@ public enum PermissionsSetApproveAllSource { SLASH_COMMAND("slash_command"), /** The {@code autopilot_confirmation} variant. */ AUTOPILOT_CONFIRMATION("autopilot_confirmation"), + /** The {@code user_setting} variant. */ + USER_SETTING("user_setting"), /** The {@code rpc} variant. */ RPC("rpc"); 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 76% 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 index bee0a48546..56e0e3dfcc 100644 --- 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 @@ -28,6 +28,8 @@ public record PlanSqlTodosRow( /** Todo description. */ @JsonProperty("description") String description, /** Todo status. */ - @JsonProperty("status") String status + @JsonProperty("status") String status, + /** Todo creation time, as stored by the session SQL schema's `datetime('now')` default: `YYYY-MM-DD HH:MM:SS` in UTC. Lets clients attribute todos to the work item that created them (e.g. scoping a goal's progress to the todos it produced) rather than to the whole session. */ + @JsonProperty("createdAt") String createdAt ) { } 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/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsBuiltinSetParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsBuiltinSetParams.java new file mode 100644 index 0000000000..f0d77fe641 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsBuiltinSetParams.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; + +/** + * Trusted built-in plugin directories to use for this runtime process. + * + * @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 PluginsBuiltinSetParams( + /** Complete replacement set of trusted built-in plugin directories. Every entry must be an absolute local filesystem path no longer than 4096 characters. */ + @JsonProperty("paths") List paths +) { +} 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/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesRefreshParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesRefreshParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesRefreshParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesRefreshParams.java 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/PermissionsAllowAllMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProtocolExternalToolDefer.java similarity index 61% 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/ProtocolExternalToolDefer.java index db24a2bad1..58f1ec24d5 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/PermissionsAllowAllMode.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProtocolExternalToolDefer.java @@ -10,28 +10,26 @@ import javax.annotation.processing.Generated; /** - * Current or requested allow-all mode. + * Controls whether the runtime may defer loading an external tool definition. * * @since 1.0.0 */ @javax.annotation.processing.Generated("copilot-sdk-codegen") -public enum PermissionsAllowAllMode { - /** The {@code off} variant. */ - OFF("off"), - /** The {@code on} variant. */ - ON("on"), +public enum ProtocolExternalToolDefer { /** The {@code auto} variant. */ - AUTO("auto"); + AUTO("auto"), + /** The {@code never} variant. */ + NEVER("never"); private final String value; - PermissionsAllowAllMode(String value) { this.value = value; } + ProtocolExternalToolDefer(String value) { this.value = value; } @com.fasterxml.jackson.annotation.JsonValue public String getValue() { return value; } @com.fasterxml.jackson.annotation.JsonCreator - public static PermissionsAllowAllMode fromValue(String value) { - for (PermissionsAllowAllMode v : values()) { + public static ProtocolExternalToolDefer fromValue(String value) { + for (ProtocolExternalToolDefer v : values()) { if (v.value.equals(value)) return v; } - throw new IllegalArgumentException("Unknown PermissionsAllowAllMode value: " + value); + throw new IllegalArgumentException("Unknown ProtocolExternalToolDefer value: " + value); } } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProtocolExternalToolDefinition.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProtocolExternalToolDefinition.java new file mode 100644 index 0000000000..5e7095700c --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProtocolExternalToolDefinition.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.Map; +import javax.annotation.processing.Generated; + +/** + * Serializable definition of a caller-implemented tool whose execution is handled over the SDK connection. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record ProtocolExternalToolDefinition( + /** Unique model-visible tool name. */ + @JsonProperty("name") String name, + /** Model-visible explanation of what the tool does. */ + @JsonProperty("description") String description, + /** Optional human-readable display title. */ + @JsonProperty("title") String title, + /** JSON Schema describing the tool's input arguments. */ + @JsonProperty("parameters") Map parameters, + /** Whether this definition replaces a built-in tool with the same name. */ + @JsonProperty("overridesBuiltInTool") Boolean overridesBuiltInTool, + /** Whether execution bypasses the normal tool permission prompt. */ + @JsonProperty("skipPermission") Boolean skipPermission, + /** Tool-loading deferral policy. */ + @JsonProperty("defer") ProtocolExternalToolDefer defer, + /** Whether the tool executes commands in a terminal. */ + @JsonProperty("isTerminal") Boolean isTerminal, + /** Optional caller-defined metadata associated with the tool. */ + @JsonProperty("metadata") Map metadata +) { +} 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 92% 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 index ee21c07d1b..25cc365ab0 100644 --- 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 @@ -38,6 +38,10 @@ public record ProviderConfig( @JsonProperty("azure") ProviderConfigAzure azure, /** Well-known model ID used for capability lookup. When set, agent behavior config and token limits are inferred from this model. */ @JsonProperty("modelId") String modelId, + /** Overrides for model capabilities when they cannot be inferred from modelId. */ + @JsonProperty("modelCapabilities") ModelCapabilitiesOverride modelCapabilities, + /** Provider name used for model and telemetry attribution. */ + @JsonProperty("providerName") String providerName, /** The model identifier sent to the provider API for inference (the "wire" model), as opposed to modelId which is the well-known base. */ @JsonProperty("wireModel") String wireModel, /** Maximum prompt/input tokens for the model. */ 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 87% 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 index c9f2630ed3..8e92701bad 100644 --- 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 @@ -23,7 +23,7 @@ public record ProviderModelConfig( /** 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`. */ @JsonProperty("id") String id, - /** Name of the NamedProviderConfig that serves this model. */ + /** Name of the configured provider that serves this model. */ @JsonProperty("provider") String provider, /** The model name sent to the provider API for inference. Defaults to `id`. */ @JsonProperty("wireModel") String wireModel, @@ -32,11 +32,11 @@ public record ProviderModelConfig( /** Display name for model pickers. Defaults to the provider-qualified selection id (`provider/id`). */ @JsonProperty("name") String name, /** Maximum prompt/input tokens for the model. */ - @JsonProperty("maxPromptTokens") Double maxPromptTokens, + @JsonProperty("maxPromptTokens") Long maxPromptTokens, /** Maximum context window tokens for the model. */ - @JsonProperty("maxContextWindowTokens") Double maxContextWindowTokens, + @JsonProperty("maxContextWindowTokens") Long maxContextWindowTokens, /** Maximum output tokens for the model. */ - @JsonProperty("maxOutputTokens") Double maxOutputTokens, + @JsonProperty("maxOutputTokens") Long maxOutputTokens, /** Optional capability overrides (vision, tool_calls, reasoning, etc.). */ @JsonProperty("capabilities") ModelCapabilitiesOverride capabilities ) { 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 92% 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 index a3e3cad7eb..1181e3ec6c 100644 --- 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 @@ -26,7 +26,7 @@ public record ProviderTokenGetTokenParams( /** Target session identifier */ @JsonProperty("sessionId") String 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`. */ + /** 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 the configured provider name. */ @JsonProperty("providerName") String providerName ) { } 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/src/generated/java/com/github/copilot/generated/rpc/QueueInsertMessage.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/QueueInsertMessage.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/QueueInsertMessage.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/QueueInsertMessage.java 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 90% 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 f3b2f99188..f815f565f5 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 @@ -23,6 +23,8 @@ public record QueuePendingItems( /** Stable opaque id for the canonical queued item. Batch rows share one id. */ @JsonProperty("id") String id, + /** Stable identity of the queued user message. Present for message rows and absent for slash commands and model changes. */ + @JsonProperty("messageId") String messageId, /** 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 */ 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/sdk/src/generated/java/com/github/copilot/generated/rpc/RemoteSessionHostStatus.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RemoteSessionHostStatus.java new file mode 100644 index 0000000000..50b6355d93 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RemoteSessionHostStatus.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; + +/** + * What a remote host says one of its sessions is doing right now. Deliberately coarse: this is what a host can report for EVERY session in a catalogue listing, without a client subscribing to each one. AHP's `SessionSummary.status` is the source today; `input-needed` covers both a permission prompt and an `ask_user` question, since the summary does not say which. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum RemoteSessionHostStatus { + /** The {@code idle} variant. */ + IDLE("idle"), + /** The {@code working} variant. */ + WORKING("working"), + /** The {@code input-needed} variant. */ + INPUT_NEEDED("input-needed"), + /** The {@code error} variant. */ + ERROR("error"); + + private final String value; + RemoteSessionHostStatus(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static RemoteSessionHostStatus fromValue(String value) { + for (RemoteSessionHostStatus v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown RemoteSessionHostStatus value: " + value); + } +} 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 79% 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 index 46f1077d9e..3ff7338d4a 100644 --- 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 @@ -49,6 +49,10 @@ public record RemoteSessionMetadataValue( /** Deadline (ISO 8601) at which a CLI remote session becomes stale without further heartbeats. */ @JsonProperty("staleAt") String staleAt, /** Server-side task state returned by GitHub. */ - @JsonProperty("state") String state + @JsonProperty("state") String state, + /** Live status as the owning host reports it in its session listing, so a row for a session running elsewhere can show that it is running. Absent for hosts that publish no such status (the cloud task managers), which read as idle. */ + @JsonProperty("hostStatus") RemoteSessionHostStatus hostStatus, + /** Host-supplied human description of what the session is doing right now ("running tests", "waiting for approval"). Optional in the protocol and absent on hosts that do not publish it, so never rely on it -- it enriches `hostStatus`, it does not replace it. */ + @JsonProperty("hostActivity") String hostActivity ) { } 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/src/generated/java/com/github/copilot/generated/rpc/RemoteSessionRepository.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RemoteSessionRepository.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/RemoteSessionRepository.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/RemoteSessionRepository.java 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 79% 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 index 92e4c401f8..11ac51827a 100644 --- 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 @@ -23,6 +23,10 @@ public record RunOptions( /** Per-invocation resource ceiling overrides. */ @JsonProperty("limits") FactoryRunLimits limits, + /** Whether to notify the originating session when the factory completes. */ + @JsonProperty("notifyOnComplete") Boolean notifyOnComplete, + /** Whether to emit factory phase names to the session transcript. */ + @JsonProperty("logPhaseNames") Boolean logPhaseNames, /** Run identifier whose journal and progress should seed this resumed run. */ @JsonProperty("resumeFromRunId") String resumeFromRunId ) { 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 54% 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 be4f2abe20..beda6b20a2 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,11 +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, - /** 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 exporting CARGO_HOME/GOPATH/etc. Default: true (enabled by default; set to false to opt out). */ - @JsonProperty("allowDevToolCaches") Boolean allowDevToolCaches + /** Credential-injection capability flags. */ + @JsonProperty("auth") SandboxConfigAuth auth, + /** Whether to auto-grant read access to tool directories discovered on PATH and in toolchain environment variables (GOROOT, JAVA_HOME, VIRTUAL_ENV, and similar), and to common developer-tool caches, config, and toolchains. Writable grants cover scratch caches, the Unix GitHub CLI cache, and Cargo's registry, git store, and lock/tracker files. A relocated CARGO_HOME gets the same narrow split: registry and git are read-write; bin is read-only; the home root, config.toml, and credentials.toml stay ungranted. Set to false to disable every grant listed above; user-installed toolchains and caches then need explicit userPolicy.filesystem readonlyPaths and readwritePaths entries. The working directory (see addCurrentWorkingDirectory), temporary storage, session log paths, and system locations follow their own rules and stay granted. 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 0000000000..cb4efc78e7 --- /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. For the same capability independent of sandboxing, and matched to the credential's GitHub host, see `shell.credentials`; the two are additive. + * + * @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/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigSource.java new file mode 100644 index 0000000000..73760b08a2 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigSource.java @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * Origin of the sandbox choice supplied by an internal client. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum SandboxConfigSource { + /** The {@code never_configured} variant. */ + NEVER_CONFIGURED("never_configured"), + /** The {@code user_enabled} variant. */ + USER_ENABLED("user_enabled"), + /** The {@code user_disabled} variant. */ + USER_DISABLED("user_disabled"), + /** The {@code session_flag} variant. */ + SESSION_FLAG("session_flag"), + /** The {@code session_disabled} variant. */ + SESSION_DISABLED("session_disabled"), + /** The {@code unsupported_host} variant. */ + UNSUPPORTED_HOST("unsupported_host"), + /** The {@code repository_policy} variant. */ + REPOSITORY_POLICY("repository_policy"); + + private final String value; + SandboxConfigSource(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static SandboxConfigSource fromValue(String value) { + for (SandboxConfigSource v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown SandboxConfigSource value: " + value); + } +} 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 63% 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 1e56acb53f..9c5e3e475f 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 @@ -25,7 +25,7 @@ public record SandboxConfigUserPolicyNetwork( @JsonProperty("allowOutbound") Boolean allowOutbound, /** Whether traffic to local/loopback addresses is allowed. */ @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. */ + /** HTTP proxy for sandboxed process traffic. Linux restricts egress to the proxy endpoint, requires that endpoint to be reachable over IPv4 (the [::] dual-stack wildcard is accepted and routed through the IPv4 gateway), and does not support proxy credentials. macOS relies on applications honoring proxy environment variables. Windows also configures a per-AppContainer WinHTTP proxy, but enforcement depends on the application's networking stack. Configure supported credentials in the separate `username` and `password` fields. A credential-free http:// loopback URL uses the localhost proxy form, while an https:// or authenticated loopback URL uses the URL form. */ @JsonProperty("proxy") SandboxConfigUserPolicyNetworkProxy proxy ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyNetworkProxy.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyNetworkProxy.java similarity index 78% rename from java/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyNetworkProxy.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyNetworkProxy.java index 74ff86919e..2946269ec1 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyNetworkProxy.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyNetworkProxy.java @@ -21,7 +21,7 @@ @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. */ + /** Proxy URL (e.g. http://proxy.example.com:8080). The port is optional and defaults to the scheme's standard port when omitted; an explicit port must be between 1 and 65535. 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 proxy 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, 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 100% 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 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 91% 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 a6ecb05547..0adbf18092 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 @@ -28,7 +28,7 @@ public final class ServerAccountApi { } /** - * Optional GitHub token used to look up quota for a specific user instead of the global auth context. + * Optional opaque account selection or compatibility GitHub token used to look up quota. *

* Invokes the method with no params, applying the runtime defaults. * @@ -41,7 +41,7 @@ public CompletableFuture getQuota() { } /** - * Optional GitHub token used to look up quota for a specific user instead of the global auth context. + * Optional opaque account selection or compatibility GitHub token used to look up quota. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 @@ -74,7 +74,7 @@ public CompletableFuture> getAllUsers() { } /** - * Credentials to store after successful authentication + * Credentials to validate and store. Omit login to resolve the authenticated user from the token. * * @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/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/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerCatalogApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerCatalogApi.java new file mode 100644 index 0000000000..9b320cbb39 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerCatalogApi.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.github.copilot.CopilotExperimental; +import java.util.concurrent.CompletableFuture; +import javax.annotation.processing.Generated; + +/** + * API methods for the {@code catalog} namespace. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class ServerCatalogApi { + + private final RpcCaller caller; + + /** @param caller the RPC transport function */ + ServerCatalogApi(RpcCaller caller) { + this.caller = caller; + } + + /** + * A bounded catalog search. Both the query length and the result count are capped by the schema so a caller cannot request an unbounded scan. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture search(CatalogSearchParams params) { + return caller.invoke("catalog.search", params, CatalogSearchResult.class); + } + +} 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/ServerExtensionsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerExtensionsApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ServerExtensionsApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerExtensionsApi.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerHooksApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerHooksApi.java new file mode 100644 index 0000000000..b6f69bf7dc --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerHooksApi.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.github.copilot.CopilotExperimental; +import java.util.concurrent.CompletableFuture; +import javax.annotation.processing.Generated; + +/** + * API methods for the {@code hooks} namespace. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class ServerHooksApi { + + private final RpcCaller caller; + + /** @param caller the RPC transport function */ + ServerHooksApi(RpcCaller caller) { + this.caller = caller; + } + + /** + * Optional project paths and host-exclusion behavior for server-scoped hook discovery. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture discover(HooksDiscoverParams params) { + return caller.invoke("hooks.discover", params, HooksDiscoverResult.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/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerManagedSettingsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerManagedSettingsApi.java new file mode 100644 index 0000000000..e85b7b987a --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerManagedSettingsApi.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.github.copilot.CopilotExperimental; +import java.util.concurrent.CompletableFuture; +import javax.annotation.processing.Generated; + +/** + * API methods for the {@code managedSettings} namespace. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class ServerManagedSettingsApi { + + private final RpcCaller caller; + + /** @param caller the RPC transport function */ + ServerManagedSettingsApi(RpcCaller caller) { + this.caller = caller; + } + + /** + * 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 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 74% 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 index b29c27fa4c..88dd7621fd 100644 --- 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 @@ -41,4 +41,15 @@ public CompletableFuture discover(McpDiscoverParams params) { return caller.invoke("mcp.discover", params, McpDiscoverResult.class); } + /** + * A side-effect-free request for an MCP install plan. Computing a plan never writes configuration, stores a secret, or reloads MCP servers. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture planInstall(McpPlanInstallParams params) { + return caller.invoke("mcp.planInstall", params, McpPlanInstallResult.class); + } + } 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/src/generated/java/com/github/copilot/generated/rpc/ServerModelsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerModelsApi.java similarity index 90% 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/ServerModelsApi.java index 0b14979707..e4fd3cbc53 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/ServerModelsApi.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerModelsApi.java @@ -27,7 +27,7 @@ public final class ServerModelsApi { } /** - * Optional GitHub token used to list models for a specific user instead of the global auth context. + * Optional opaque account selection or compatibility GitHub token used to list models. *

* Invokes the method with no params, applying the runtime defaults. * @@ -40,7 +40,7 @@ public CompletableFuture list() { } /** - * Optional GitHub token used to list models for a specific user instead of the global auth context. + * Optional opaque account selection or compatibility GitHub token used to list models. * * @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/ServerPluginsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerPluginsApi.java similarity index 95% 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 index 20dc6ab15a..a7a28f5d1c 100644 --- 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 @@ -21,12 +21,15 @@ public final class ServerPluginsApi { private final RpcCaller caller; + /** API methods for the {@code plugins.builtin} sub-namespace. */ + public final ServerPluginsBuiltinApi builtin; /** API methods for the {@code plugins.marketplaces} sub-namespace. */ public final ServerPluginsMarketplacesApi marketplaces; /** @param caller the RPC transport function */ ServerPluginsApi(RpcCaller caller) { this.caller = caller; + this.builtin = new ServerPluginsBuiltinApi(caller); this.marketplaces = new ServerPluginsMarketplacesApi(caller); } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerPluginsBuiltinApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerPluginsBuiltinApi.java new file mode 100644 index 0000000000..0610fcb28e --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerPluginsBuiltinApi.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.github.copilot.CopilotExperimental; +import java.util.concurrent.CompletableFuture; +import javax.annotation.processing.Generated; + +/** + * API methods for the {@code plugins.builtin} namespace. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class ServerPluginsBuiltinApi { + + private final RpcCaller caller; + + /** @param caller the RPC transport function */ + ServerPluginsBuiltinApi(RpcCaller caller) { + this.caller = caller; + } + + /** + * Trusted built-in plugin directories to use for this runtime process. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture set(PluginsBuiltinSetParams params) { + return caller.invoke("plugins.builtin.set", params, Void.class); + } + +} 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 100% 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 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 81% 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 e66f32737f..caa7f92d07 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 @@ -25,6 +25,8 @@ public final class ServerRpc { private final RpcCaller caller; + /** API methods for the {@code hooks} namespace. */ + public final ServerHooksApi hooks; /** API methods for the {@code models} namespace. */ public final ServerModelsApi models; /** API methods for the {@code tools} namespace. */ @@ -37,6 +39,8 @@ public final class ServerRpc { public final ServerMcpApi mcp; /** API methods for the {@code extensions} namespace. */ public final ServerExtensionsApi extensions; + /** API methods for the {@code catalog} namespace. */ + public final ServerCatalogApi catalog; /** API methods for the {@code plugins} namespace. */ public final ServerPluginsApi plugins; /** API methods for the {@code skills} namespace. */ @@ -49,6 +53,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. */ @@ -67,18 +73,21 @@ public final class ServerRpc { */ public ServerRpc(RpcCaller caller) { this.caller = caller; + this.hooks = new ServerHooksApi(caller); this.models = new ServerModelsApi(caller); this.tools = new ServerToolsApi(caller); this.account = new ServerAccountApi(caller); this.secrets = new ServerSecretsApi(caller); this.mcp = new ServerMcpApi(caller); this.extensions = new ServerExtensionsApi(caller); + this.catalog = new ServerCatalogApi(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); @@ -98,7 +107,7 @@ public CompletableFuture ping(PingParams params) { } /** - * Parameters for the `server.connect` handshake: an optional connection token and optional connection-level opt-ins (e.g. GitHub telemetry forwarding). + * Connection-level opt-ins for the `server.connect` handshake. Transport authentication is consumed by the native protocol boundary before dispatch. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 @@ -108,4 +117,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 96% 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 52481a7d73..870f7ac3cf 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 @@ -94,6 +94,17 @@ public CompletableFuture getMetadata(SessionsGetMetad return caller.invoke("sessions.getMetadata", params, SessionsGetMetadataResult.class); } + /** + * Pagination options for reading an inactive or active local session's persisted event journal. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture readPersistedEvents(SessionsReadPersistedEventsParams params) { + return caller.invoke("sessions.readPersistedEvents", params, SessionsReadPersistedEventsResult.class); + } + /** * Limit for non-empty local session IDs. * 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 3498245262..b1d409d9d9 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 74% 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 index 688288a118..c29770bf14 100644 --- 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 @@ -37,4 +37,15 @@ public CompletableFuture setDisabledSkills(SkillsConfigSetDisabledSkillsPa return caller.invoke("skills.config.setDisabledSkills", params, Void.class); } + /** + * Adds or removes a single skill from the global disabled list, leaving every other entry untouched. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture setSkillDisabled(SkillsConfigSetSkillDisabledParams params) { + return caller.invoke("skills.config.setSkillDisabled", params, Void.class); + } + } 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 100% 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 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/src/generated/java/com/github/copilot/generated/rpc/SessionAgentListParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAgentListParams.java similarity index 100% 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/SessionAgentListParams.java 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 100% 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 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/src/generated/java/com/github/copilot/generated/rpc/SessionAgentSetPromptParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAgentSetPromptParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionAgentSetPromptParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAgentSetPromptParams.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAuthStatus.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAuthStatus.java new file mode 100644 index 0000000000..6f9d876dd8 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAuthStatus.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; + +/** + * Authentication status and account metadata for the session. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionAuthStatus( + /** Whether the session has resolved authentication */ + @JsonProperty("isAuthenticated") Boolean isAuthenticated, + /** Authentication type */ + @JsonProperty("authType") AuthInfoType authType, + /** Authentication host URL */ + @JsonProperty("host") String host, + /** Authenticated login/username, if available */ + @JsonProperty("login") String login, + /** Human-readable authentication status description */ + @JsonProperty("statusMessage") String statusMessage, + /** Copilot plan tier (e.g., individual_pro, business) */ + @JsonProperty("copilotPlan") String copilotPlan +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCancelAllBackgroundAgentsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCancelAllBackgroundAgentsParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionCancelAllBackgroundAgentsParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCancelAllBackgroundAgentsParams.java 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 94% 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 index 88a320e0be..e7cfc4be2f 100644 --- 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 @@ -26,12 +26,15 @@ public final class SessionCanvasApi { /** API methods for the {@code canvas.action} sub-namespace. */ public final SessionCanvasActionApi action; + /** API methods for the {@code canvas.provider} sub-namespace. */ + public final SessionCanvasProviderApi provider; /** @param caller the RPC transport function */ SessionCanvasApi(RpcCaller caller, String sessionId) { this.caller = caller; this.sessionId = sessionId; this.action = new SessionCanvasActionApi(caller, sessionId); + this.provider = new SessionCanvasProviderApi(caller, sessionId); } /** 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/SessionGitHubAuthApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasProviderApi.java similarity index 61% 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/SessionCanvasProviderApi.java index 93fb871501..95fc7ca660 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthApi.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasProviderApi.java @@ -12,12 +12,12 @@ import javax.annotation.processing.Generated; /** - * API methods for the {@code gitHubAuth} namespace. + * API methods for the {@code canvas.provider} namespace. * * @since 1.0.0 */ @javax.annotation.processing.Generated("copilot-sdk-codegen") -public final class SessionGitHubAuthApi { +public final class SessionCanvasProviderApi { private static final com.fasterxml.jackson.databind.ObjectMapper MAPPER = RpcMapper.INSTANCE; @@ -25,24 +25,29 @@ public final class SessionGitHubAuthApi { private final String sessionId; /** @param caller the RPC transport function */ - SessionGitHubAuthApi(RpcCaller caller, String sessionId) { + SessionCanvasProviderApi(RpcCaller caller, String sessionId) { this.caller = caller; this.sessionId = sessionId; } /** - * Identifies the target session. + * Internal canvas provider registration 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 getStatus() { - return caller.invoke("session.gitHubAuth.getStatus", java.util.Map.of("sessionId", this.sessionId), SessionGitHubAuthGetStatusResult.class); + public CompletableFuture register(SessionCanvasProviderRegisterParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.canvas.provider.register", _p, Void.class); } /** - * New auth credentials to install on the session. Omit to leave credentials unchanged. + * Internal canvas provider unregistration parameters. *

* Note: the {@code sessionId} field in the params record is overridden * by the session-scoped wrapper; any value provided is ignored. @@ -51,10 +56,10 @@ public CompletableFuture getStatus() { * @since 1.0.0 */ @CopilotExperimental - public CompletableFuture setCredentials(SessionGitHubAuthSetCredentialsParams params) { + public CompletableFuture unregister(SessionCanvasProviderUnregisterParams params) { com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); _p.put("sessionId", this.sessionId); - return caller.invoke("session.gitHubAuth.setCredentials", _p, SessionGitHubAuthSetCredentialsResult.class); + return caller.invoke("session.canvas.provider.unregister", _p, Void.class); } } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasProviderRegisterParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasProviderRegisterParams.java new file mode 100644 index 0000000000..3047576670 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasProviderRegisterParams.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 canvas provider registration parameters. + * + * @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 SessionCanvasProviderRegisterParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Connection identifier for callback routing */ + @JsonProperty("connectionId") String connectionId, + /** Provider metadata supplied by the host */ + @JsonProperty("info") Object info, + /** Canvas contributions supplied by the provider */ + @JsonProperty("canvases") List canvases +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasProviderUnregisterParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasProviderUnregisterParams.java new file mode 100644 index 0000000000..f7b637ac65 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasProviderUnregisterParams.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 canvas provider unregistration parameters. + * + * @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 SessionCanvasProviderUnregisterParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Connection identifier to unregister */ + @JsonProperty("connectionId") String connectionId +) { +} 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 87% 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 facb3fcfc5..405819efce 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 @@ -75,6 +75,22 @@ public CompletableFuture invoke(SessionCommandsInv return caller.invoke("session.commands.invoke", _p, SlashCommandInvocationResult.class); } + /** + * The pending slash-command invocation effect to finalize, plus whether the host applied or cancelled 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 finalizeInvocationEffect(SessionCommandsFinalizeInvocationEffectParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.commands.finalizeInvocationEffect", _p, SessionCommandsFinalizeInvocationEffectResult.class); + } + /** * Pending command request ID and an optional error if the client handler failed. *

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 86% 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 index d7725bc9cf..073038d2fc 100644 --- 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 @@ -27,6 +27,8 @@ public record SessionCommandsEnqueueParams( /** Target session identifier */ @JsonProperty("sessionId") String sessionId, /** 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. */ - @JsonProperty("command") String command + @JsonProperty("command") String command, + /** Optional user-facing text for the queue row. The command string is shown when omitted. */ + @JsonProperty("displayText") String displayText ) { } 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/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsFinalizeInvocationEffectParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsFinalizeInvocationEffectParams.java new file mode 100644 index 0000000000..2a1b75f5b8 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsFinalizeInvocationEffectParams.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.Map; +import javax.annotation.processing.Generated; + +/** + * The pending slash-command invocation effect to finalize, plus whether the host applied or cancelled 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 SessionCommandsFinalizeInvocationEffectParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** The slash-command result object that produced the pending effect, echoed back unchanged. */ + @JsonProperty("effect") Map effect, + /** Whether the host applied or cancelled the pending invocation effect. */ + @JsonProperty("outcome") CommandsInvocationEffectOutcome outcome +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsFinalizeInvocationEffectResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsFinalizeInvocationEffectResult.java new file mode 100644 index 0000000000..523a68564e --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsFinalizeInvocationEffectResult.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; + +/** + * Whether finalizing the invocation effect succeeded, and the failure reason when it did not. + * + * @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 SessionCommandsFinalizeInvocationEffectResult( + /** Whether the pending invocation effect was finalized successfully. */ + @JsonProperty("success") Boolean success, + /** Failure reason when the invocation effect could not be finalized. */ + @JsonProperty("error") String error +) { +} 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 88% 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 index 01d9488259..0921ba8ef5 100644 --- 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 @@ -29,6 +29,8 @@ public record SessionCommandsInvokeParams( /** Command name. Leading slashes are stripped and the name is matched case-insensitively. */ @JsonProperty("name") String name, /** Raw input after the command name */ - @JsonProperty("input") String input + @JsonProperty("input") String input, + /** Optional client surface that initiated the invocation */ + @JsonProperty("origin") CommandsInvocationOrigin origin ) { } 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/SessionCommandsListParams.java similarity index 100% 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/SessionCommandsListParams.java 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/SessionContentExclusionApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionContentExclusionApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionContentExclusionApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionContentExclusionApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionContentExclusionCheckPathsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionContentExclusionCheckPathsParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionContentExclusionCheckPathsParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionContentExclusionCheckPathsParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionContentExclusionCheckPathsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionContentExclusionCheckPathsResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionContentExclusionCheckPathsResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionContentExclusionCheckPathsResult.java 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 100% 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 diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogReadResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogReadResult.java similarity index 96% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogReadResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogReadResult.java index 767acc8795..857578d0fb 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogReadResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogReadResult.java @@ -11,6 +11,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.github.copilot.CopilotExperimental; +import com.github.copilot.generated.SessionEvent; import java.util.List; import javax.annotation.processing.Generated; @@ -26,7 +27,7 @@ @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, + @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. */ 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 100% 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 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 78% 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 6d15ac17c3..e9a3e9f08a 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 @@ -66,6 +66,38 @@ public CompletableFuture resume(SessionFactoryResume return caller.invoke("session.factory.resume", _p, SessionFactoryResumeResult.class); } + /** + * Internal parameters for invoking a registered factory from a tool. + *

+ * 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 runFromTool(SessionFactoryRunFromToolParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.factory.runFromTool", _p, SessionFactoryRunFromToolResult.class); + } + + /** + * Internal parameters for resuming a factory run from a tool. + *

+ * 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 resumeFromTool(SessionFactoryResumeFromToolParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.factory.resumeFromTool", _p, SessionFactoryResumeFromToolResult.class); + } + /** * Parameters for retrieving a factory run. *

@@ -83,14 +115,19 @@ public CompletableFuture getRun(SessionFactoryGetRun } /** - * Empty parameters for listing factory runs. + * 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() { - return caller.invoke("session.factory.listRuns", java.util.Map.of("sessionId", this.sessionId), SessionFactoryListRunsResult.class); + 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); } /** 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 86% 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 index 0cb66280c0..c9f9de2dcc 100644 --- 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 @@ -26,13 +26,15 @@ public record SessionFactoryCancelResult( /** Factory run identifier. */ @JsonProperty("runId") String runId, + /** One-based execution attempt represented by this envelope. Absent before the first attempt starts or when returned by an older runtime. */ + @JsonProperty("attempt") Long attempt, /** 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. */ + /** Machine-readable failure details for a halted or errored run. */ @JsonProperty("failure") Object failure, /** Reason for a halted or cancelled run. */ @JsonProperty("reason") String reason, diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunDetailParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunDetailParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunDetailParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunDetailParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunDetailResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunDetailResult.java similarity index 63% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunDetailResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunDetailResult.java index 5da6f4979c..01204cb832 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunDetailResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunDetailResult.java @@ -25,27 +25,49 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record SessionFactoryGetRunDetailResult( + /** Factory run identifier. */ @JsonProperty("runId") String runId, + /** Registered factory name. */ @JsonProperty("factoryName") String factoryName, + /** Human-readable factory description. */ @JsonProperty("description") String description, + /** Current factory run status. */ @JsonProperty("status") FactoryRunStatus status, + /** Monotonic durable run revision. */ @JsonProperty("revision") Long revision, + /** Epoch milliseconds when the run was created. */ @JsonProperty("createdAt") Long createdAt, + /** Epoch milliseconds when execution first started, or null before start. */ @JsonProperty("startedAt") Long startedAt, + /** Epoch milliseconds when the durable run was last updated. */ @JsonProperty("updatedAt") Long updatedAt, + /** Epoch milliseconds when the run completed, or null while nonterminal. */ @JsonProperty("completedAt") Long completedAt, + /** Current phase identity, or null before any phase is entered. */ @JsonProperty("currentPhase") FactoryCurrentPhase currentPhase, + /** Number of phases declared by the factory. */ @JsonProperty("declaredPhaseCount") Long declaredPhaseCount, + /** Number of direct factory agents currently live. */ @JsonProperty("liveAgentCount") Long liveAgentCount, + /** Total direct factory agents spawned across all attempts. */ @JsonProperty("totalSpawnedAgentCount") Long totalSpawnedAgentCount, + /** Durable resource consumption. */ @JsonProperty("consumed") FactoryRunConsumed consumed, + /** Resource ceilings declared by the factory. */ @JsonProperty("declaredLimits") FactoryDeclaredLimits declaredLimits, + /** Approved effective resource ceilings, or null until approved. */ @JsonProperty("approved") FactoryDeclaredLimits approved, + /** Epoch milliseconds when this live-overlay snapshot was observed. */ @JsonProperty("observedAt") Long observedAt, + /** Epoch milliseconds when the current active segment started, or null while inactive. */ @JsonProperty("activeSegmentStartedAt") Long activeSegmentStartedAt, + /** Terminal run outcome, or null while nonterminal. */ @JsonProperty("terminal") FactoryRunTerminal terminal, + /** Lifecycle and timing observations for each factory phase. */ @JsonProperty("phases") List phases, + /** Durable identities and live statuses for direct factory agents. */ @JsonProperty("agents") List agents, + /** Bidirectional page of durable factory progress. */ @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/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunProgressParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunProgressParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunProgressParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunProgressParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunProgressResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunProgressResult.java similarity index 81% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunProgressResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunProgressResult.java index 369fa07c22..2a4cb78cb2 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunProgressResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunProgressResult.java @@ -25,10 +25,15 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record SessionFactoryGetRunProgressResult( + /** Progress records in sequence order. */ @JsonProperty("records") List records, + /** Oldest sequence number in this page, or null when empty. */ @JsonProperty("oldestSeq") Long oldestSeq, + /** Newest sequence number in this page, or null when empty. */ @JsonProperty("newestSeq") Long newestSeq, + /** Whether progress records older than this page exist. */ @JsonProperty("hasMoreOlder") Boolean hasMoreOlder, + /** Whether progress records newer than this page exist. */ @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 86% 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 index 6742faf03e..2d6a5f52a9 100644 --- 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 @@ -26,13 +26,15 @@ public record SessionFactoryGetRunResult( /** Factory run identifier. */ @JsonProperty("runId") String runId, + /** One-based execution attempt represented by this envelope. Absent before the first attempt starts or when returned by an older runtime. */ + @JsonProperty("attempt") Long attempt, /** 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. */ + /** Machine-readable failure details for a halted or errored run. */ @JsonProperty("failure") Object failure, /** Reason for a halted or cancelled run. */ @JsonProperty("reason") String reason, 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 100% 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 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 100% 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 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 0000000000..41de4ae4a6 --- /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 0000000000..d081003e47 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryListRunsResult.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; + +/** + * 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( + /** Factory run summaries in durable creation order. */ + @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 100% 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 diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeFromToolParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeFromToolParams.java new file mode 100644 index 0000000000..3a54179329 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeFromToolParams.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 parameters for resuming a factory run from a tool. + * + * @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 SessionFactoryResumeFromToolParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Factory run identifier. */ + @JsonProperty("runId") String runId, + /** Optional per-invocation resource ceiling overrides. */ + @JsonProperty("limits") FactoryRunLimits limits, + /** Opaque identifier of the originating tool call. */ + @JsonProperty("toolCallId") String toolCallId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeFromToolResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeFromToolResult.java new file mode 100644 index 0000000000..d4566384c5 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeFromToolResult.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 SessionFactoryResumeFromToolResult( + /** 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/SessionFactoryResumeParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeParams.java similarity index 79% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeParams.java index 9c264284fc..edb39e0811 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeParams.java @@ -29,6 +29,10 @@ public record SessionFactoryResumeParams( /** Factory run identifier. */ @JsonProperty("runId") String runId, /** Optional per-invocation resource ceiling overrides. */ - @JsonProperty("limits") FactoryRunLimits limits + @JsonProperty("limits") FactoryRunLimits limits, + /** Whether to notify the originating session when the factory completes. */ + @JsonProperty("notifyOnComplete") Boolean notifyOnComplete, + /** Whether to emit factory phase names to the session transcript. */ + @JsonProperty("logPhaseNames") Boolean logPhaseNames ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeResult.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryRunFromToolParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryRunFromToolParams.java new file mode 100644 index 0000000000..dab8ae120d --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryRunFromToolParams.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; + +/** + * Internal parameters for invoking a registered factory from a tool. + * + * @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 SessionFactoryRunFromToolParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Registered factory name. */ + @JsonProperty("name") String name, + /** Factory input value. */ + @JsonProperty("args") Object args, + /** Tool-originated factory invocation options. */ + @JsonProperty("options") FactoryToolRunOptions options, + /** Opaque identifier of the originating tool call. */ + @JsonProperty("toolCallId") String toolCallId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryRunFromToolResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryRunFromToolResult.java new file mode 100644 index 0000000000..1a9dee5926 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryRunFromToolResult.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 com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Complete current or terminal factory 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 SessionFactoryRunFromToolResult( + /** Factory run identifier. */ + @JsonProperty("runId") String runId, + /** One-based execution attempt represented by this envelope. Absent before the first attempt starts or when returned by an older runtime. */ + @JsonProperty("attempt") Long attempt, + /** 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 a halted or 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/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 86% 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 index 46083f2284..d8ce481895 100644 --- 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 @@ -26,13 +26,15 @@ public record SessionFactoryRunResult( /** Factory run identifier. */ @JsonProperty("runId") String runId, + /** One-based execution attempt represented by this envelope. Absent before the first attempt starts or when returned by an older runtime. */ + @JsonProperty("attempt") Long attempt, /** 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. */ + /** Machine-readable failure details for a halted or errored run. */ @JsonProperty("failure") Object failure, /** Reason for a halted or cancelled run. */ @JsonProperty("reason") String reason, 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 100% 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 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 100% 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 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/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionError.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionError.java similarity index 89% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionError.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionError.java index cbe170a298..e25eaed867 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionError.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionError.java @@ -21,7 +21,9 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record SessionFsSqliteTransactionError( + /** Machine-readable classification of the transaction failure. */ @JsonProperty("errorClass") SessionFsSqliteTransactionErrorClass errorClass, + /** Human-readable transaction failure message. */ @JsonProperty("message") String message ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionErrorClass.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionErrorClass.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionErrorClass.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionErrorClass.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionParams.java similarity index 95% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionParams.java index f834d55957..f7699ff8f0 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionParams.java @@ -27,6 +27,7 @@ public record SessionFsSqliteTransactionParams( /** Target session identifier */ @JsonProperty("sessionId") String sessionId, + /** Ordered SQL statements to execute in one transaction. */ @JsonProperty("statements") List statements ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionResult.java similarity index 90% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionResult.java index f9c799b9b2..1b662f4047 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionResult.java @@ -25,7 +25,9 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record SessionFsSqliteTransactionResult( + /** Per-statement query results in input order. */ @JsonProperty("results") List results, + /** Classified transaction failure, when execution did not succeed. */ @JsonProperty("error") SessionFsSqliteTransactionError error ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionStatement.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionStatement.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionStatement.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionStatement.java 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/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthApi.java new file mode 100644 index 0000000000..729979b5e2 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthApi.java @@ -0,0 +1,164 @@ +/*--------------------------------------------------------------------------------------------- + * 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.List; +import java.util.concurrent.CompletableFuture; +import javax.annotation.processing.Generated; + +/** + * API methods for the {@code gitHubAuth} namespace. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SessionGitHubAuthApi { + + 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 */ + SessionGitHubAuthApi(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 getStatus() { + return caller.invoke("session.gitHubAuth.getStatus", java.util.Map.of("sessionId", this.sessionId), SessionGitHubAuthGetStatusResult.class); + } + + /** + * New auth credentials to install on the session. Omit to leave credentials unchanged. + *

+ * 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 setCredentials(SessionGitHubAuthSetCredentialsParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.gitHubAuth.setCredentials", _p, SessionGitHubAuthSetCredentialsResult.class); + } + + /** + * Identifies the target session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture getCurrentAuthInfo() { + return caller.invoke("session.gitHubAuth.getCurrentAuthInfo", java.util.Map.of("sessionId", this.sessionId), 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> getAllAuthAvailable() { + return caller.invoke("session.gitHubAuth.getAllAuthAvailable", java.util.Map.of("sessionId", this.sessionId), RpcMapper.INSTANCE.getTypeFactory().constructCollectionType(List.class, SessionAuthStatus.class)); + } + + /** + * Identifies the target session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture refreshCopilotUser() { + return caller.invoke("session.gitHubAuth.refreshCopilotUser", java.util.Map.of("sessionId", this.sessionId), Void.class); + } + + /** + * Internal GitHub login 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 login(SessionGitHubAuthLoginParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.gitHubAuth.login", _p, AuthInfo.class); + } + + /** + * Parameters for switching the session's active authentication. + *

+ * 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 switchToAuth(SessionGitHubAuthSwitchToAuthParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.gitHubAuth.switchToAuth", _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 logout() { + return caller.invoke("session.gitHubAuth.logout", java.util.Map.of("sessionId", this.sessionId), Void.class); + } + + /** + * Parameters identifying a GitHub authentication to log out. + *

+ * 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 logoutUser(SessionGitHubAuthLogoutUserParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.gitHubAuth.logoutUser", _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> lastAuthErrors() { + return caller.invoke("session.gitHubAuth.lastAuthErrors", java.util.Map.of("sessionId", this.sessionId), RpcMapper.INSTANCE.getTypeFactory().constructCollectionType(List.class, AuthValidationError.class)); + } + +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthGetAllAuthAvailableParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthGetAllAuthAvailableParams.java new file mode 100644 index 0000000000..446642d743 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthGetAllAuthAvailableParams.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 SessionGitHubAuthGetAllAuthAvailableParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthGetCurrentAuthInfoParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthGetCurrentAuthInfoParams.java new file mode 100644 index 0000000000..88838f951d --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthGetCurrentAuthInfoParams.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 SessionGitHubAuthGetCurrentAuthInfoParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId +) { +} 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/SessionFactoryListRunsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthLastAuthErrorsParams.java similarity index 91% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryListRunsParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthLastAuthErrorsParams.java index e3b90f07f7..d4fc156b28 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryListRunsParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthLastAuthErrorsParams.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Empty parameters for listing factory runs. + * 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 SessionFactoryListRunsParams( +public record SessionGitHubAuthLastAuthErrorsParams( /** Target session identifier */ @JsonProperty("sessionId") String sessionId ) { diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthLoginParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthLoginParams.java new file mode 100644 index 0000000000..e0d777956f --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthLoginParams.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; + +/** + * Internal GitHub login parameters. + * + * @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 SessionGitHubAuthLoginParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** GitHub host URL */ + @JsonProperty("host") String host, + /** GitHub login */ + @JsonProperty("login") String login, + /** GitHub authentication token */ + @JsonProperty("token") String token, + /** Whether to persist the token after login */ + @JsonProperty("persist") Boolean persist +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryListRunsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthLogoutParams.java similarity index 84% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryListRunsResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthLogoutParams.java index 7d1ac45a9e..172778d616 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryListRunsResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthLogoutParams.java @@ -11,11 +11,10 @@ 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; /** - * Factory runs in durable creation order. + * Identifies the target session. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 @@ -24,7 +23,8 @@ @javax.annotation.processing.Generated("copilot-sdk-codegen") @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) -public record SessionFactoryListRunsResult( - @JsonProperty("runs") List runs +public record SessionGitHubAuthLogoutParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthLogoutUserParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthLogoutUserParams.java new file mode 100644 index 0000000000..1aafec7811 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthLogoutUserParams.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 identifying a GitHub authentication to log out. + * + * @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 SessionGitHubAuthLogoutUserParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Authentication information to log out */ + @JsonProperty("authInfo") Object authInfo +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthRefreshCopilotUserParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthRefreshCopilotUserParams.java new file mode 100644 index 0000000000..d0b9d33e56 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthRefreshCopilotUserParams.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 SessionGitHubAuthRefreshCopilotUserParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId +) { +} 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/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthSwitchToAuthParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthSwitchToAuthParams.java new file mode 100644 index 0000000000..7cd6f72b41 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthSwitchToAuthParams.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 switching the session's active authentication. + * + * @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 SessionGitHubAuthSwitchToAuthParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Authentication information to activate */ + @JsonProperty("authInfo") Object authInfo, + /** Optional token paired with the authentication information */ + @JsonProperty("token") String token +) { +} 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/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryApi.java 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/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryClearContextParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryClearContextParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryClearContextParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryClearContextParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryClearContextResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryClearContextResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryClearContextResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryClearContextResult.java 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/SessionHistoryCompactParams.java similarity index 100% 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/SessionHistoryCompactParams.java 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/SessionHistoryListRewindPointsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryListRewindPointsParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryListRewindPointsParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryListRewindPointsParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryListRewindPointsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryListRewindPointsResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryListRewindPointsResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryListRewindPointsResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryPreviewRewindParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryPreviewRewindParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryPreviewRewindParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryPreviewRewindParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryPreviewRewindResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryPreviewRewindResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryPreviewRewindResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryPreviewRewindResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryRewindParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryRewindParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryRewindParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryRewindParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryRewindResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryRewindResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryRewindResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryRewindResult.java 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/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryTruncateResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryTruncateResult.java similarity index 100% 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/SessionHistoryTruncateResult.java 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 78% 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 1109f5f231..db8ea12026 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 @@ -36,6 +36,8 @@ public record SessionInstalledPlugin( /** Source descriptor for direct repo installs (when marketplace is empty) */ @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 + @JsonProperty("source_sha") String sourceSha, + /** Absolute path of the marketplace directory a live plugin was resolved from. Present only on live, never-persisted records — those synthesized at session start for a directory/local marketplace, whose cache_path points at the real plugin directory on disk rather than a copy under the installed-plugins cache. Its presence is what marks a record as live, and no record carrying it is ever written to the persisted installedPlugins key. */ + @JsonProperty("installed_from") String installedFrom ) { } 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/src/generated/java/com/github/copilot/generated/rpc/SessionInterruptMainTurnParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionInterruptMainTurnParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionInterruptMainTurnParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionInterruptMainTurnParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionInterruptMainTurnResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionInterruptMainTurnResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionInterruptMainTurnResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionInterruptMainTurnResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionBaselineData.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionBaselineData.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionBaselineData.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionBaselineData.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionClientType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionClientType.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionClientType.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionClientType.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionDetails.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionDetails.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionDetails.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionDetails.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionPredictParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionPredictParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionPredictParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionPredictParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionResultAvailable.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionResultAvailable.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionResultAvailable.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionResultAvailable.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionResultUnavailable.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionResultUnavailable.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionResultUnavailable.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionResultUnavailable.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionSource.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionSource.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionSource.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionTier.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionTier.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionTier.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionTier.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionTierOption.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionTierOption.java similarity index 96% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionTierOption.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionTierOption.java index f468e53e04..ad2a6f82c1 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionTierOption.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionTierOption.java @@ -21,6 +21,7 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record SessionLimitPredictionTierOption( + /** Semantic usage tier. */ @JsonProperty("tier") SessionLimitPredictionTier tier, /** AI-credit cap for this tier. */ @JsonProperty("cap") Double cap diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionUnavailableReason.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionUnavailableReason.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionUnavailableReason.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionUnavailableReason.java 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/src/generated/java/com/github/copilot/generated/rpc/SessionListFilter.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionListFilter.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionListFilter.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionListFilter.java 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 0000000000..8d52a1eb1e --- /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. `allow-auto-only` blocks full allow-all but permits advisory auto-approval. Any other value is accepted rather than failing the session, but is enforced as `disable`: the key is only present to restrict something, so a mode this runtime cannot interpret fails closed to the most restrictive one it knows. Omit the key entirely to impose no restriction. */ + @JsonProperty("disableBypassPermissionsMode") String 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 0000000000..cecaca8c17 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionManagedSettings.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; + +/** + * 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( + /** Managed permission policy injected by the SDK host. */ + @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 96% 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 1a75b92c1b..ed66b7bc15 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 @@ -113,6 +113,17 @@ public CompletableFuture reload() { return caller.invoke("session.mcp.reload", java.util.Map.of("sessionId", this.sessionId), 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 moveLoadingToBackground() { + return caller.invoke("session.mcp.moveLoadingToBackground", java.util.Map.of("sessionId", this.sessionId), SessionMcpMoveLoadingToBackgroundResult.class); + } + /** * Opaque MCP reload configuration. *

@@ -189,7 +200,7 @@ public CompletableFuture removeGitHub() { } /** - * Opaque auth info used to configure GitHub MCP. + * Credential-free authentication identity used to configure GitHub MCP. *

* 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 94% 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 index 2f709a8dda..e46a0af8f0 100644 --- 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 @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Opaque auth info used to configure GitHub MCP. + * Credential-free authentication identity used to configure GitHub MCP. * * @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/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/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpMoveLoadingToBackgroundParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpMoveLoadingToBackgroundParams.java new file mode 100644 index 0000000000..9c3305c3bd --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpMoveLoadingToBackgroundParams.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 SessionMcpMoveLoadingToBackgroundParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpMoveLoadingToBackgroundResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpMoveLoadingToBackgroundResult.java new file mode 100644 index 0000000000..8890481647 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpMoveLoadingToBackgroundResult.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 in-flight MCP loading to the background. + * + * @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 SessionMcpMoveLoadingToBackgroundResult( + /** Whether an in-flight MCP load was moved to the background, releasing turns that were waiting on it. False when no MCP load was in flight or the waiting turns had already been released. */ + @JsonProperty("movedToBackground") Boolean movedToBackground +) { +} 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 85% 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 1fdb292f84..cf4a3601ef 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 @@ -78,6 +78,22 @@ public CompletableFuture login(SessionMcpOauthLoginP return caller.invoke("session.mcp.oauth.login", _p, SessionMcpOauthLoginResult.class); } + /** + * Remote MCP server name for a passive OAuth status probe. + *

+ * 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 probe(SessionMcpOauthProbeParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.mcp.oauth.probe", _p, McpOauthProbeResult.class); + } + /** * Pending MCP OAuth request id to respond to. *

diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthAuthenticationStateChangedParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthAuthenticationStateChangedParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthAuthenticationStateChangedParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthAuthenticationStateChangedParams.java 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/SessionMcpOauthProbeParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthProbeParams.java new file mode 100644 index 0000000000..1980398e08 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthProbeParams.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; + +/** + * Remote MCP server name for a passive OAuth status probe. + * + * @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 SessionMcpOauthProbeParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Name of the configured remote MCP server to probe. */ + @JsonProperty("serverName") String serverName +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthRespondParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthRespondParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthRespondParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthRespondParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthRespondResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthRespondResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthRespondResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthRespondResult.java 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 90% 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 index ba4fcf3b79..eba9a21c11 100644 --- 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 @@ -28,6 +28,8 @@ public record SessionMcpReloadWithConfigResult( /** Servers filtered out before startup */ @JsonProperty("filteredServers") List filteredServers, /** Non-default servers allowed by policy */ - @JsonProperty("allowedServers") List allowedServers + @JsonProperty("allowedServers") List allowedServers, + /** Servers whose connection attempt failed. */ + @JsonProperty("failedServers") List failedServers ) { } 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 100% 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 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 ef7fba44d9..c27f37afb0 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 91% 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 index 58311ff651..0f4989a920 100644 --- 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 @@ -51,10 +51,10 @@ public CompletableFuture get() { * @since 1.0.0 */ @CopilotExperimental - public CompletableFuture set(SessionModeSetParams params) { + public CompletableFuture set(SessionModeSetParams params) { com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); _p.put("sessionId", this.sessionId); - return caller.invoke("session.mode.set", _p, Void.class); + return caller.invoke("session.mode.set", _p, SessionModeSetResult.class); } } 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/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModeSetParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModeSetParams.java new file mode 100644 index 0000000000..4c01d16d03 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModeSetParams.java @@ -0,0 +1,52 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * Agent interaction mode to apply to 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 SessionModeSetParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** The session mode the agent is operating in */ + @JsonProperty("mode") SessionMode mode, + /** Session whose plan-mode base state should be inherited. */ + @JsonProperty("inheritPlanBaseFromSessionId") String inheritPlanBaseFromSessionId, + /** Whether a dedicated plan model is configured. */ + @JsonProperty("planModelConfigured") Boolean planModelConfigured, + /** Dedicated model to use in plan mode, when configured. */ + @JsonProperty("planModel") String planModel, + /** Reasoning effort to use with the dedicated plan model. */ + @JsonProperty("planReasoningEffort") String planReasoningEffort, + /** Context tier to use with the dedicated plan model. */ + @JsonProperty("planContextTier") String planContextTier, + /** Explicit response to a model-switch compaction preflight. */ + @JsonProperty("compactionDecision") String compactionDecision, + /** Whether leaving plan mode should restore the session's previous model. */ + @JsonProperty("restorePlanModel") Boolean restorePlanModel, + /** Whether the selected plan model should be persisted. */ + @JsonProperty("persistPlanSelection") Boolean persistPlanSelection, + /** Settings context used when persisting the selected plan model. */ + @JsonProperty("pickerSettingsContext") ModelPickerSettingsContext pickerSettingsContext, + /** Action to perform when leaving plan mode. */ + @JsonProperty("planExitAction") String planExitAction +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModeSetResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModeSetResult.java new file mode 100644 index 0000000000..e3af446682 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModeSetResult.java @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * Outcome of a session mode change, including any model switch it triggered and follow-up the host must perform. + * + * @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 SessionModeSetResult( + /** Lifecycle status of the requested mode change. */ + @JsonProperty("status") String status, + /** Whether applying the mode changed the active model. */ + @JsonProperty("modelChanged") Boolean modelChanged, + /** Compaction confirmation required before the mode change can complete. */ + @JsonProperty("confirmation") ModelSwitchConfirmation confirmation, + /** User-facing warning produced while applying the mode change. */ + @JsonProperty("warning") String warning, + /** User-facing outcome message for the model switch triggered by the mode change. */ + @JsonProperty("message") String message, + /** Deprecation warnings associated with the model selected by the mode change. */ + @JsonProperty("deprecationWarnings") List deprecationWarnings, + /** Whether the host must defer implementing the requested mode change. */ + @JsonProperty("deferImplementation") Boolean deferImplementation, + /** Whether the host should arm an interactive continuation after the mode change. */ + @JsonProperty("armInteractiveContinuation") Boolean armInteractiveContinuation +) { +} 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 83% 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 9d20e86272..12777c9a1f 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 @@ -57,6 +57,22 @@ public CompletableFuture switchTo(SessionModelSwitch return caller.invoke("session.model.switchTo", _p, SessionModelSwitchToResult.class); } + /** + * Managed, repository, and CLI model overrides to overlay onto the session at startup. + *

+ * 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 applyStartupOverlay(SessionModelApplyStartupOverlayParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.model.applyStartupOverlay", _p, SessionModelApplyStartupOverlayResult.class); + } + /** * Reasoning effort level to apply to the currently selected model. *

diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelApplyStartupOverlayParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelApplyStartupOverlayParams.java new file mode 100644 index 0000000000..dfb593fd21 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelApplyStartupOverlayParams.java @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * Managed, repository, and CLI model overrides to overlay onto the session at startup. + * + * @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 SessionModelApplyStartupOverlayParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Model required by device-managed policy, when configured. */ + @JsonProperty("deviceManagedModel") String deviceManagedModel, + /** Model required by server-managed policy, when configured. */ + @JsonProperty("serverManagedModel") String serverManagedModel, + /** Startup default model from the enterprise policy helper, when configured. Weakest of the managed sources: it applies only when neither device nor server policy names a model, and an explicit user selection still wins. */ + @JsonProperty("policyHelperModel") String policyHelperModel, + /** Model selected by repository settings, when configured. */ + @JsonProperty("repoModel") String repoModel, + /** Reasoning effort selected by repository settings, when configured. */ + @JsonProperty("repoReasoningEffort") String repoReasoningEffort, + /** Context tier selected by repository settings, when configured. */ + @JsonProperty("repoContextTier") String repoContextTier, + /** Model explicitly selected by the CLI, when provided. */ + @JsonProperty("cliModel") String cliModel, + /** Whether the overlay is being applied while resuming a deferred session. */ + @JsonProperty("deferredResume") Boolean deferredResume +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelApplyStartupOverlayResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelApplyStartupOverlayResult.java new file mode 100644 index 0000000000..f29e249c32 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelApplyStartupOverlayResult.java @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * 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 model identifier active on the session after the switch. + * + * @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 SessionModelApplyStartupOverlayResult( + /** Currently active model identifier after the switch */ + @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, + /** Lifecycle result for the requested switch */ + @JsonProperty("status") String status, + /** Compaction confirmation projection when status is confirmation_required */ + @JsonProperty("confirmation") ModelSwitchConfirmation confirmation, + /** Persistence failure encountered after applying the model switch. */ + @JsonProperty("persistenceError") String persistenceError, + /** User-facing outcome message for the model switch. */ + @JsonProperty("message") String message, + /** User-facing warning produced while applying the model switch. */ + @JsonProperty("warning") String warning, + /** Deprecation warnings associated with the selected model or options. */ + @JsonProperty("deprecationWarnings") List deprecationWarnings +) { +} 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 100% 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 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 92% 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 index 295f6a01f0..be637e9488 100644 --- 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 @@ -21,7 +21,9 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record SessionModelPriceCategory( + /** CAPI model identifier. */ @JsonProperty("id") String id, + /** Cost category assigned to the model. */ @JsonProperty("priceCategory") ModelPickerPriceCategory priceCategory ) { } 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 70% 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 fe49e29761..d7e36cf3c9 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 @@ -38,7 +38,21 @@ public record SessionModelSwitchToParams( @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, + /** Origin to record on the effective `session.model_change` event for trusted in-process calls. Transport SDK calls are always recorded as `sdk`, regardless of this value. */ + @JsonProperty("source") ModelChangeSource source, /** 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 + @JsonProperty("deferIfModelChangeQueued") Boolean deferIfModelChangeQueued, + /** Explicit response to a model-switch compaction preflight. Omit to request a confirmation projection when compaction is necessary. */ + @JsonProperty("compactionDecision") String compactionDecision, + /** When true, evaluate context-window compaction policy before applying the switch. */ + @JsonProperty("runCompactionPreflight") Boolean runCompactionPreflight, + /** Optional repository settings scope to persist after the switch commits. */ + @JsonProperty("repoScope") String repoScope, + /** Settings scope used when persisting the selected model. */ + @JsonProperty("modelChangeScope") String modelChangeScope, + /** Require the target to be currently available and enabled before applying the switch. */ + @JsonProperty("requireAvailable") Boolean requireAvailable, + /** Optional settings context and explicit-override flags used to persist a picker selection. */ + @JsonProperty("pickerPersistence") ModelPickerPersistenceRequest pickerPersistence ) { } 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 62% 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 030324a94b..fb143abc30 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 @@ -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; /** @@ -27,6 +28,18 @@ public record SessionModelSwitchToResult( /** Currently active model identifier after the switch */ @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 + @JsonProperty("deferred") Boolean deferred, + /** Lifecycle result for the requested switch */ + @JsonProperty("status") String status, + /** Compaction confirmation projection when status is confirmation_required */ + @JsonProperty("confirmation") ModelSwitchConfirmation confirmation, + /** Persistence failure encountered after applying the model switch. */ + @JsonProperty("persistenceError") String persistenceError, + /** User-facing outcome message for the model switch. */ + @JsonProperty("message") String message, + /** User-facing warning produced while applying the model switch. */ + @JsonProperty("warning") String warning, + /** Deprecation warnings associated with the selected model or options. */ + @JsonProperty("deprecationWarnings") List deprecationWarnings ) { } 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/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptions.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptions.java similarity index 82% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptions.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptions.java index a8108e8292..5f4753b1ac 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptions.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptions.java @@ -47,6 +47,8 @@ public record SessionOpenOptions( @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. */ @@ -65,7 +67,7 @@ public record SessionOpenOptions( @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`). */ + /** 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). Conventional `.github/skills/` and `.github/agents/` definitions under each directory also join the session's project catalogs when their existing subsystem gates are enabled: added-root skills require both `enableConfigDiscovery` and effective `enableSkills`; added-root agents require `enableConfigDiscovery`. Supplying a directory therefore activates configuration from it and should be treated as a trust decision. 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 during session creation and cold resume and is not persisted, so a cold resume must re-supply the directories. */ @JsonProperty("additionalDirectories") List additionalDirectories, /** Pre-resolved working-directory context for session startup. */ @JsonProperty("workingDirectoryContext") SessionContext workingDirectoryContext, @@ -97,14 +99,24 @@ public record SessionOpenOptions( @JsonProperty("shellProcessFlags") List shellProcessFlags, /** Resolved sandbox configuration. */ @JsonProperty("sandboxConfig") SandboxConfig sandboxConfig, + /** Origin of the sandbox choice. The runtime uses this only for internal telemetry provenance; managed policy is derived independently. */ + @JsonProperty("sandboxConfigSource") SandboxConfigSource sandboxConfigSource, /** 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, + /** Whether skill loading is enabled. When omitted, an SDK skill provider enables skills by default. */ + @JsonProperty("enableSkills") Boolean enableSkills, + /** Whether the requesting SDK session has a skill provider. The provider remains ephemeral and is never persisted in session options or history. When enableSkills is false, it remains bound but dormant and receives no callbacks. Cloud, relay, handoff, and raw sessions.open flows reject it because they cannot safely pre-register the callback handler. */ + @JsonProperty("hasSkillProvider") Boolean hasSkillProvider, + /** Built-in skill names to include in this session. When specified, only these runtime-bundled skills are available. Skills from other sources with the same name remain available. */ + @JsonProperty("includedBuiltinSkills") List includedBuiltinSkills, /** Skill IDs disabled for this session. */ @JsonProperty("disabledSkills") List disabledSkills, /** Installed plugins visible to the session. */ @@ -121,7 +133,7 @@ public record SessionOpenOptions( @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. */ + /** Experimental: enable native model citations for supported Anthropic and OpenAI models, 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, diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicy.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicy.java similarity index 92% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicy.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicy.java index bbc53711a4..3ee2252d16 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicy.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicy.java @@ -22,7 +22,9 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record SessionOpenOptionsAdditionalContentExclusionPolicy( + /** Content-exclusion rules to apply. */ @JsonProperty("rules") List rules, + /** Opaque policy update timestamp supplied by the host. */ @JsonProperty("last_updated_at") Object lastUpdatedAt, /** Allowed values for the `SessionOpenOptionsAdditionalContentExclusionPolicyScope` enumeration. */ @JsonProperty("scope") SessionOpenOptionsAdditionalContentExclusionPolicyScope scope diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicyRule.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicyRule.java similarity index 89% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicyRule.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicyRule.java index 403550ae24..480235747a 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicyRule.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicyRule.java @@ -22,8 +22,11 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record SessionOpenOptionsAdditionalContentExclusionPolicyRule( + /** Path patterns covered by this rule. */ @JsonProperty("paths") List paths, + /** Conditions of which at least one must match. */ @JsonProperty("ifAnyMatch") List ifAnyMatch, + /** Conditions none of which may match. */ @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/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource.java similarity index 93% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource.java index 9cfa5894cf..a119c9da76 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource.java @@ -21,7 +21,9 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource( + /** Name of the policy source. */ @JsonProperty("name") String name, + /** Type of the policy source. */ @JsonProperty("type") String type ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicyScope.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicyScope.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicyScope.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicyScope.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsEnvValueMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsEnvValueMode.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsEnvValueMode.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsEnvValueMode.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsReasoningSummary.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsReasoningSummary.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsReasoningSummary.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsReasoningSummary.java 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 92% 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 080b47866b..27a5c2db06 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 @@ -74,6 +74,8 @@ public record SessionOptionsUpdateParams( @JsonProperty("shellProcessFlags") List shellProcessFlags, /** Resolved sandbox configuration. */ @JsonProperty("sandboxConfig") SandboxConfig sandboxConfig, + /** Origin of the sandbox choice. The runtime uses this only for internal telemetry provenance; managed policy is derived independently. */ + @JsonProperty("sandboxConfigSource") SandboxConfigSource sandboxConfigSource, /** Whether interactive shell sessions are logged. */ @JsonProperty("logInteractiveShells") Boolean logInteractiveShells, /** How env values are passed to MCP servers (`direct` inlines literal values; `indirect` resolves at launch). */ @@ -82,6 +84,8 @@ public record SessionOptionsUpdateParams( @JsonProperty("allowAllMcpServerInstructions") Boolean allowAllMcpServerInstructions, /** Additional directories to search for skills. */ @JsonProperty("skillDirectories") List skillDirectories, + /** Built-in skill names to include in this session. When specified, only these runtime-bundled skills are available. Skills from other sources with the same name remain available. Set to null to remove the allowlist restriction. */ + @JsonProperty("includedBuiltinSkills") List includedBuiltinSkills, /** 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`. */ @@ -136,7 +140,7 @@ public record SessionOptionsUpdateParams( @JsonProperty("enableHostGitOperations") Boolean enableHostGitOperations, /** Whether to enable cross-session store writes and reads. */ @JsonProperty("enableSessionStore") Boolean enableSessionStore, - /** Whether to enable skill directory scanning and loading. Falls back to enableConfigDiscovery when unset. */ + /** Whether skill loading is enabled. Explicit false disables every source, including a bound SDK provider; changing the value invalidates the loaded skill snapshot. When omitted, creation falls back to enableConfigDiscovery unless an SDK skill provider is registered. */ @JsonProperty("enableSkills") Boolean enableSkills, /** 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. */ @JsonProperty("contextTier") OptionsUpdateContextTier contextTier, 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 25d2e36666..8b397f2d4d 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 @@ -103,7 +103,7 @@ public CompletableFuture setApproveAll(Se } /** - * Allow-all mode to apply for the session. + * Permission mode to apply for the session. *

* Note: the {@code sessionId} field in the params record is overridden * by the session-scoped wrapper; any value provided is ignored. @@ -112,10 +112,10 @@ public CompletableFuture setApproveAll(Se * @since 1.0.0 */ @CopilotExperimental - public CompletableFuture setAllowAll(SessionPermissionsSetAllowAllParams params) { + public CompletableFuture setMode(SessionPermissionsSetModeParams params) { com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); _p.put("sessionId", this.sessionId); - return caller.invoke("session.permissions.setAllowAll", _p, SessionPermissionsSetAllowAllResult.class); + return caller.invoke("session.permissions.setMode", _p, SessionPermissionsSetModeResult.class); } /** @@ -125,8 +125,8 @@ public CompletableFuture setAllowAll(Sessio * @since 1.0.0 */ @CopilotExperimental - public CompletableFuture getAllowAll() { - return caller.invoke("session.permissions.getAllowAll", java.util.Map.of("sessionId", this.sessionId), SessionPermissionsGetAllowAllResult.class); + public CompletableFuture getMode() { + return caller.invoke("session.permissions.getMode", java.util.Map.of("sessionId", this.sessionId), SessionPermissionsGetModeResult.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/SessionPermissionsGetModeParams.java similarity index 95% 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/SessionPermissionsGetModeParams.java index 761640a4fb..5d41d3c6a2 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsGetAllowAllParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsGetModeParams.java @@ -23,7 +23,7 @@ @javax.annotation.processing.Generated("copilot-sdk-codegen") @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) -public record SessionPermissionsGetAllowAllParams( +public record SessionPermissionsGetModeParams( /** Target session identifier */ @JsonProperty("sessionId") String sessionId ) { diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsGetModeResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsGetModeResult.java new file mode 100644 index 0000000000..1ccb45375c --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsGetModeResult.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; + +/** + * Current permission mode. + * + * @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 SessionPermissionsGetModeResult( + /** Current permission mode */ + @JsonProperty("mode") PermissionMode mode +) { +} 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 075dcee26a..a4d2ba67e5 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 82% 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 index e5f35a2264..48ee26e0d9 100644 --- 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 @@ -26,7 +26,7 @@ public record SessionPermissionsPathsAddParams( /** Target session identifier */ @JsonProperty("sessionId") String sessionId, - /** Directory to add to the allow-list. The runtime resolves and validates the path before adding. */ + /** Directory to add to the allow-list. The runtime resolves and validates the path before adding, then loads conventional `.github/skills/` and `.github/agents/` definitions under it when their subsystem gates are enabled. Adding the directory is therefore also a trust decision for configuration stored there. */ @JsonProperty("path") String path ) { } 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 100% 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 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/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/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetModeParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetModeParams.java new file mode 100644 index 0000000000..712d460243 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetModeParams.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; + +/** + * Permission mode to apply 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 SessionPermissionsSetModeParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Permission mode to apply */ + @JsonProperty("mode") PermissionMode mode, + /** Optional judge model id for assisted mode. When omitted, the session resolves the provider default: `gpt-5.5` for CAPI sessions and the active session model for BYOK sessions. */ + @JsonProperty("assistedApprovalModel") String assistedApprovalModel, + /** Optional source for permission-mode telemetry. Defaults to `rpc` when omitted for SDK callers. */ + @JsonProperty("source") PermissionModeSource 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/SessionPermissionsSetModeResult.java similarity index 73% 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/SessionPermissionsSetModeResult.java index 9b14d8f6ad..1ee34568ea 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetAllowAllResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetModeResult.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Indicates whether the operation succeeded and reports the post-mutation state. + * Indicates whether the requested permission mode was applied and reports the authoritative post-mutation mode. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 @@ -23,12 +23,10 @@ @javax.annotation.processing.Generated("copilot-sdk-codegen") @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) -public record SessionPermissionsSetAllowAllResult( +public record SessionPermissionsSetModeResult( /** Whether the operation succeeded */ @JsonProperty("success") Boolean success, - /** Authoritative full allow-all state after the mutation */ - @JsonProperty("enabled") Boolean enabled, - /** Authoritative allow-all mode after the mutation */ - @JsonProperty("mode") PermissionsAllowAllMode mode + /** Authoritative permission mode after the mutation */ + @JsonProperty("mode") PermissionMode mode ) { } 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 91% 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 index 1230bbc02f..48dfd6e809 100644 --- 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 @@ -25,7 +25,7 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record SessionPlanReadSqlTodosResult( - /** Rows from the session SQL todos table, ordered by creation time and id. */ + /** Rows from the session SQL todos table, ordered by creation time with insertion order used to break ties when available and id used for WITHOUT ROWID tables. */ @JsonProperty("rows") List rows ) { } 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 89% 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 index 505d083036..4e2ad62e0d 100644 --- 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 @@ -25,7 +25,7 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record SessionPlanReadSqlTodosWithDependenciesResult( - /** Rows from the session SQL todos table, ordered by creation time and id. Empty when no database, no todos table, or the SELECT failed. */ + /** Rows from the session SQL todos table, ordered by creation time with insertion order used to break ties when available and id used for WITHOUT ROWID tables. Empty when no database, no todos table, or the SELECT failed. */ @JsonProperty("rows") List rows, /** 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. */ @JsonProperty("dependencies") List dependencies 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 100% 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 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/src/generated/java/com/github/copilot/generated/rpc/SessionPluginsReloadParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPluginsReloadParams.java similarity index 100% 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/SessionPluginsReloadParams.java 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 100% 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 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/SessionProviderGetEndpointParams.java similarity index 100% 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/SessionProviderGetEndpointParams.java 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/src/generated/java/com/github/copilot/generated/rpc/SessionQueueApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueApi.java similarity index 100% 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/SessionQueueApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueBeginDeferredIdleDrainParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueBeginDeferredIdleDrainParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueBeginDeferredIdleDrainParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueBeginDeferredIdleDrainParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueBeginDeferredIdleDrainResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueBeginDeferredIdleDrainResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueBeginDeferredIdleDrainResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueBeginDeferredIdleDrainResult.java 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/src/generated/java/com/github/copilot/generated/rpc/SessionQueueConsumeSystemNotificationsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueConsumeSystemNotificationsParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueConsumeSystemNotificationsParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueConsumeSystemNotificationsParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueConsumeSystemNotificationsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueConsumeSystemNotificationsResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueConsumeSystemNotificationsResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueConsumeSystemNotificationsResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueDeferSessionIdleParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueDeferSessionIdleParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueDeferSessionIdleParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueDeferSessionIdleParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueDuplicateAtParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueDuplicateAtParams.java similarity index 94% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueDuplicateAtParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueDuplicateAtParams.java index bf16f9d35f..0187e814b8 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueDuplicateAtParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueDuplicateAtParams.java @@ -26,6 +26,7 @@ public record SessionQueueDuplicateAtParams( /** Target session identifier */ @JsonProperty("sessionId") String sessionId, + /** Stable opaque ID of the queued item to duplicate. */ @JsonProperty("id") String id ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueDuplicateAtResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueDuplicateAtResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueDuplicateAtResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueDuplicateAtResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueEnqueueResumePendingParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueEnqueueResumePendingParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueEnqueueResumePendingParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueEnqueueResumePendingParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueEnqueueResumePendingResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueEnqueueResumePendingResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueEnqueueResumePendingResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueEnqueueResumePendingResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueFinishDeferredIdleDrainParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueFinishDeferredIdleDrainParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueFinishDeferredIdleDrainParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueFinishDeferredIdleDrainParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueFinishDeferredIdleDrainResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueFinishDeferredIdleDrainResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueFinishDeferredIdleDrainResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueFinishDeferredIdleDrainResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueHasPendingParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueHasPendingParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueHasPendingParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueHasPendingParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueHasPendingResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueHasPendingResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueHasPendingResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueHasPendingResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueInsertAtParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueInsertAtParams.java similarity index 95% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueInsertAtParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueInsertAtParams.java index 981aefb5fd..11f0f76533 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueInsertAtParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueInsertAtParams.java @@ -28,6 +28,7 @@ public record SessionQueueInsertAtParams( @JsonProperty("sessionId") String sessionId, /** Zero-based position in the public visible queue. Values outside the queue clamp to an end. */ @JsonProperty("position") Long position, + /** Queued message contents and delivery metadata. */ @JsonProperty("message") QueueInsertMessage message ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueInsertAtResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueInsertAtResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueInsertAtResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueInsertAtResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueMoveItemParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueMoveItemParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueMoveItemParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueMoveItemParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueMoveItemResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueMoveItemResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueMoveItemResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueMoveItemResult.java 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 82% 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 index 7b096480ca..a74d54e245 100644 --- 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 @@ -28,6 +28,8 @@ public record SessionQueuePendingItemsResult( /** Pending queued items in submission order. Includes user messages, queued slash commands, and queued model changes; omits internal system items. */ @JsonProperty("items") List items, /** Display text for messages currently in the immediate steering queue (interjections sent during a running turn). */ - @JsonProperty("steeringMessages") List steeringMessages + @JsonProperty("steeringMessages") List steeringMessages, + /** How many leading entries of `steeringMessages` have already been folded into the running turn (and so have an emitted `user.message`), as opposed to still waiting for one. Absent for hosts that do not distinguish the two. */ + @JsonProperty("inFlightSteeringCount") Long inFlightSteeringCount ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueProcessParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueProcessParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueProcessParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueProcessParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueRemoveAtParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueRemoveAtParams.java similarity index 95% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueRemoveAtParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueRemoveAtParams.java index bc7cd3e124..5e890f85d9 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueRemoveAtParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueRemoveAtParams.java @@ -26,6 +26,7 @@ public record SessionQueueRemoveAtParams( /** Target session identifier */ @JsonProperty("sessionId") String sessionId, + /** Stable opaque ID of the queued item to remove. */ @JsonProperty("id") String id ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueRemoveAtResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueRemoveAtResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueRemoveAtResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueRemoveAtResult.java 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/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSendNowParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSendNowParams.java similarity index 93% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSendNowParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSendNowParams.java index 6381636a89..9d23f47eb4 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSendNowParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSendNowParams.java @@ -26,6 +26,7 @@ public record SessionQueueSendNowParams( /** Target session identifier */ @JsonProperty("sessionId") String sessionId, + /** Stable opaque ID of the queued item to steer into the live turn. */ @JsonProperty("id") String id ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSendNowResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSendNowResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSendNowResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSendNowResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSetDrainPausedParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSetDrainPausedParams.java similarity index 96% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSetDrainPausedParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSetDrainPausedParams.java index f51e33ea16..4674978d3e 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSetDrainPausedParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSetDrainPausedParams.java @@ -26,6 +26,7 @@ public record SessionQueueSetDrainPausedParams( /** Target session identifier */ @JsonProperty("sessionId") String sessionId, + /** Whether queued-lane draining should be paused. */ @JsonProperty("paused") Boolean paused ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSnapshotParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSnapshotParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSnapshotParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSnapshotParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSnapshotResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSnapshotResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSnapshotResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSnapshotResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueUpdateTextParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueUpdateTextParams.java similarity index 88% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueUpdateTextParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueUpdateTextParams.java index 139a5ba2a4..5f315d5724 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueUpdateTextParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueUpdateTextParams.java @@ -26,8 +26,11 @@ public record SessionQueueUpdateTextParams( /** Target session identifier */ @JsonProperty("sessionId") String sessionId, + /** Stable opaque ID of the queued item to edit. */ @JsonProperty("id") String id, + /** Replacement prompt sent to the model. */ @JsonProperty("prompt") String prompt, + /** Optional replacement prompt displayed to the user. */ @JsonProperty("displayPrompt") String displayPrompt ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueUpdateTextResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueUpdateTextResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueUpdateTextResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueUpdateTextResult.java 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 98% 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 05cfd396d4..b9bd6b8226 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 @@ -29,6 +29,8 @@ public final class SessionRpc { private final RpcCaller caller; private final String sessionId; + /** API methods for the {@code sandbox} namespace. */ + public final SessionSandboxApi sandbox; /** API methods for the {@code gitHubAuth} namespace. */ public final SessionGitHubAuthApi gitHubAuth; /** API methods for the {@code debug} namespace. */ @@ -115,6 +117,7 @@ public final class SessionRpc { public SessionRpc(RpcCaller caller, String sessionId) { this.caller = caller; this.sessionId = sessionId; + this.sandbox = new SessionSandboxApi(caller, sessionId); this.gitHubAuth = new SessionGitHubAuthApi(caller, sessionId); this.debug = new SessionDebugApi(caller, sessionId); this.canvas = new SessionCanvasApi(caller, sessionId); diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSandboxApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSandboxApi.java new file mode 100644 index 0000000000..55efb9da78 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSandboxApi.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.github.copilot.CopilotExperimental; +import java.util.concurrent.CompletableFuture; +import javax.annotation.processing.Generated; + +/** + * API methods for the {@code sandbox} namespace. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SessionSandboxApi { + + private final RpcCaller caller; + private final String sessionId; + + /** @param caller the RPC transport function */ + SessionSandboxApi(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 getEnforcementStatus() { + return caller.invoke("session.sandbox.getEnforcementStatus", java.util.Map.of("sessionId", this.sessionId), SessionSandboxGetEnforcementStatusResult.class); + } + +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSandboxGetEnforcementStatusParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSandboxGetEnforcementStatusParams.java new file mode 100644 index 0000000000..ec92981e04 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSandboxGetEnforcementStatusParams.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 SessionSandboxGetEnforcementStatusParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSandboxGetEnforcementStatusResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSandboxGetEnforcementStatusResult.java new file mode 100644 index 0000000000..c1a0e77a1a --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSandboxGetEnforcementStatusResult.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; + +/** + * Managed sandbox enforcement state for a 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 SessionSandboxGetEnforcementStatusResult( + /** Whether the effective managed policy requires an available sandbox backend. */ + @JsonProperty("required") Boolean required, + /** Whether an enforcement failure has permanently blocked the session. */ + @JsonProperty("blocked") Boolean blocked, + /** The first sandbox enforcement failure that blocked the session. */ + @JsonProperty("reason") String reason +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddAtParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddAtParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddAtParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddAtParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddAtResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddAtResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddAtResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddAtResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddCronParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddCronParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddCronParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddCronParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddCronResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddCronResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddCronResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddCronResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddSelfPacedParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddSelfPacedParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddSelfPacedParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddSelfPacedParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddSelfPacedResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddSelfPacedResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddSelfPacedResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddSelfPacedResult.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/SessionScheduleApi.java similarity index 100% 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/SessionScheduleApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleHasSelfPacedParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleHasSelfPacedParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleHasSelfPacedParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleHasSelfPacedParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleHasSelfPacedResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleHasSelfPacedResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleHasSelfPacedResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleHasSelfPacedResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleHydrateParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleHydrateParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleHydrateParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleHydrateParams.java 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/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleRearmSelfPacedParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleRearmSelfPacedParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleRearmSelfPacedParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleRearmSelfPacedParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleRearmSelfPacedResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleRearmSelfPacedResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleRearmSelfPacedResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleRearmSelfPacedResult.java 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 100% 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 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 100% 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 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/src/generated/java/com/github/copilot/generated/rpc/SessionSendSystemNotificationParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSendSystemNotificationParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionSendSystemNotificationParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSendSystemNotificationParams.java 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 89% 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 index 98e6df29fa..8a714d5ccb 100644 --- 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 @@ -21,7 +21,9 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record SessionSettingsBuiltInToolAvailabilitySnapshot( + /** Whether the report-progress tool is available. */ @JsonProperty("reportProgress") Boolean reportProgress, + /** Whether the create-pull-request tool is available. */ @JsonProperty("createPullRequest") Boolean createPullRequest ) { } 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 93% 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 index 1f4a7ed320..3ab56b8ca6 100644 --- 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 @@ -24,6 +24,7 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record SessionSettingsEvaluatePredicateResult( + /** Whether the named settings predicate evaluated to enabled. */ @JsonProperty("enabled") Boolean enabled ) { } 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 87% 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 index 463660d8a1..8a807e391c 100644 --- 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 @@ -21,8 +21,11 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record SessionSettingsJobSnapshot( + /** GitHub Actions event type for the job. */ @JsonProperty("eventType") String eventType, + /** Whether this is the workflow's trigger job. */ @JsonProperty("isTriggerJob") Boolean isTriggerJob, + /** Availability of job-specific built-in tools. */ @JsonProperty("builtInToolAvailability") SessionSettingsBuiltInToolAvailabilitySnapshot builtInToolAvailability ) { } 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 83% 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 index ce515c74db..e1eb54a251 100644 --- 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 @@ -21,9 +21,13 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record SessionSettingsModelSnapshot( + /** Selected model identifier. */ @JsonProperty("model") String model, + /** Default reasoning effort for the selected model. */ @JsonProperty("defaultReasoningEffort") String defaultReasoningEffort, + /** Agent job identifier for the session. */ @JsonProperty("instanceId") String instanceId, + /** Agent service callback URL for job and progress updates. */ @JsonProperty("callbackUrl") String callbackUrl ) { } 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 89% 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 index b1a5be6f3a..999e1a2cbf 100644 --- 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 @@ -21,7 +21,9 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record SessionSettingsOnlineEvaluationSnapshot( + /** Whether online evaluation is disabled. */ @JsonProperty("disableOnlineEvaluation") Boolean disableOnlineEvaluation, + /** Whether online-evaluation output-file generation is enabled. */ @JsonProperty("enableOnlineEvaluationOutputFile") Boolean enableOnlineEvaluationOutputFile ) { } 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 74% 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 index 960853c527..3651346bd8 100644 --- 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 @@ -21,17 +21,29 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record SessionSettingsRepoSnapshot( + /** Repository name. */ @JsonProperty("name") String name, + /** GitHub repository database ID. */ @JsonProperty("id") Double id, + /** Checked-out repository branch. */ @JsonProperty("branch") String branch, + /** Checked-out commit SHA. */ @JsonProperty("commit") String commit, + /** Whether the repository is writable. */ @JsonProperty("readWrite") Boolean readWrite, + /** Repository owner login. */ @JsonProperty("ownerName") String ownerName, + /** GitHub repository owner database ID. */ @JsonProperty("ownerId") Double ownerId, + /** GitHub server base URL. */ @JsonProperty("serverUrl") String serverUrl, + /** GitHub server host name. */ @JsonProperty("host") String host, + /** Protocol used to access the GitHub host. */ @JsonProperty("hostProtocol") String hostProtocol, + /** GitHub secret-scanning service URL. */ @JsonProperty("secretScanningUrl") String secretScanningUrl, + /** Number of commits in the pull request. */ @JsonProperty("prCommitCount") Double prCommitCount ) { } 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 75% 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 index 0851474d64..1bcfe166fc 100644 --- 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 @@ -24,14 +24,23 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record SessionSettingsSnapshotResult( + /** Agent runtime version selector copied from the session settings, such as `latest` or a runtime release identifier. */ @JsonProperty("version") String version, + /** Name of the SDK client that created the session. */ @JsonProperty("clientName") String clientName, + /** Session timeout in milliseconds. */ @JsonProperty("timeoutMs") Double timeoutMs, + /** Session start time as Unix epoch milliseconds. */ @JsonProperty("startTimeMs") Double startTimeMs, + /** Redacted repository and host settings. */ @JsonProperty("repo") SessionSettingsRepoSnapshot repo, + /** Redacted model routing settings. */ @JsonProperty("model") SessionSettingsModelSnapshot model, + /** Redacted validation and memory-tool settings. */ @JsonProperty("validation") SessionSettingsValidationSnapshot validation, + /** Redacted job settings. */ @JsonProperty("job") SessionSettingsJobSnapshot job, + /** Online-evaluation settings safe for SDK consumers. */ @JsonProperty("onlineEvaluation") SessionSettingsOnlineEvaluationSnapshot onlineEvaluation ) { } 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 74% 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 index 375cfdfec1..314cc5b2b4 100644 --- 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 @@ -21,14 +21,23 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record SessionSettingsValidationSnapshot( + /** General validation timeout budget in seconds. */ @JsonProperty("timeout") Double timeout, + /** Dependabot validation timeout budget in seconds. */ @JsonProperty("dependabotTimeout") Double dependabotTimeout, + /** Whether CodeQL validation is enabled. */ @JsonProperty("codeqlEnabled") Boolean codeqlEnabled, + /** Whether code-review validation is enabled. */ @JsonProperty("codeReviewEnabled") Boolean codeReviewEnabled, + /** Model used for code-review validation. */ @JsonProperty("codeReviewModel") String codeReviewModel, + /** Whether advisory validation is enabled. */ @JsonProperty("advisoryEnabled") Boolean advisoryEnabled, + /** Whether secret-scanning validation is enabled. */ @JsonProperty("secretScanningEnabled") Boolean secretScanningEnabled, + /** Whether the memory-store tool is enabled. */ @JsonProperty("memoryStoreEnabled") Boolean memoryStoreEnabled, + /** Whether the memory-vote tool is enabled. */ @JsonProperty("memoryVoteEnabled") Boolean memoryVoteEnabled ) { } 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/src/generated/java/com/github/copilot/generated/rpc/SessionSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSource.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionSource.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSource.java 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 95% 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 index 82daeec5e6..87da305a6c 100644 --- 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 @@ -30,7 +30,7 @@ public record SessionTasksStartAgentParams( @JsonProperty("agentType") String agentType, /** Task prompt for the agent */ @JsonProperty("prompt") String prompt, - /** Short name for the agent, used to generate a human-readable ID */ + /** Friendly, non-unique name used when displaying the agent */ @JsonProperty("name") String name, /** Short description of the task */ @JsonProperty("description") String description, 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 52% 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 index 0f4b058827..62593a3588 100644 --- 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 @@ -30,6 +30,54 @@ public final class SessionToolsApi { this.sessionId = sessionId; } + /** + * A tool name and arguments to execute through the session's native invocation pipeline. + *

+ * 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 execute(SessionToolsExecuteParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.tools.execute", _p, Void.class); + } + + /** + * Options controlling how Rust-owned built-in tool descriptors are materialized. + *

+ * 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 getBuiltinDescriptors(SessionToolsGetBuiltinDescriptorsParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.tools.getBuiltinDescriptors", _p, SessionToolsGetBuiltinDescriptorsResult.class); + } + + /** + * Task-completion tool arguments and final result used to build a label-safe session event payload. + *

+ * 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 taskCompleteEventData(SessionToolsTaskCompleteEventDataParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.tools.taskCompleteEventData", _p, SessionToolsTaskCompleteEventDataResult.class); + } + /** * Pending external tool call request ID, with the tool result or an error describing why it failed. *

@@ -68,6 +116,22 @@ public CompletableFuture getCurrentMetadat return caller.invoke("session.tools.getCurrentMetadata", java.util.Map.of("sessionId", this.sessionId), SessionToolsGetCurrentMetadataResult.class); } + /** + * Complete externally implemented tool list for the calling connection. An empty list removes every tool previously supplied by that connection. + *

+ * 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 set(SessionToolsSetParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.tools.set", _p, Void.class); + } + /** * Subagent settings to apply to the current session *

diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsExecuteParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsExecuteParams.java new file mode 100644 index 0000000000..e60023c3aa --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsExecuteParams.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; + +/** + * A tool name and arguments to execute through the session's native invocation pipeline. + * + * @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 SessionToolsExecuteParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Name of the currently offered tool to execute. */ + @JsonProperty("name") String name, + /** Arguments supplied to the tool. */ + @JsonProperty("arguments") Object arguments, + /** Optional identifier used to correlate this invocation with its tool call. */ + @JsonProperty("toolCallId") String toolCallId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsGetBuiltinDescriptorsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsGetBuiltinDescriptorsParams.java new file mode 100644 index 0000000000..43dd20e722 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsGetBuiltinDescriptorsParams.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 com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Options controlling how Rust-owned built-in tool descriptors are materialized. + * + * @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 SessionToolsGetBuiltinDescriptorsParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Whether descriptors should favor fewer user-intervention prompts. */ + @JsonProperty("reduceUserIntervention") Boolean reduceUserIntervention, + /** Whether tool descriptors should include authoring metadata. */ + @JsonProperty("includeAuthor") Boolean includeAuthor, + /** Whether semantic skill lookup is available. */ + @JsonProperty("skillEmbeddingEnabled") Boolean skillEmbeddingEnabled, + /** Shell-specific names and description lines for shell tools. */ + @JsonProperty("shellConfig") ToolsShellDescriptorConfig shellConfig, + /** Whether the configured shell supports PowerShell 7 syntax. */ + @JsonProperty("shellSupportsPowerShell7Syntax") Boolean shellSupportsPowerShell7Syntax, + /** Default shell timeout in milliseconds. */ + @JsonProperty("shellTimeoutMs") Double shellTimeoutMs, + /** Whether background task completion notifications are enabled. */ + @JsonProperty("backgroundTaskNotificationsEnabled") Boolean backgroundTaskNotificationsEnabled +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsGetBuiltinDescriptorsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsGetBuiltinDescriptorsResult.java new file mode 100644 index 0000000000..1f33321e73 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsGetBuiltinDescriptorsResult.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; + +/** + * Rust-owned built-in tool descriptors 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 SessionToolsGetBuiltinDescriptorsResult( + /** Built-in tool descriptors materialized for the session. */ + @JsonProperty("tools") List tools +) { +} 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/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsSetParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsSetParams.java new file mode 100644 index 0000000000..3edd6afad3 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsSetParams.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; + +/** + * Complete externally implemented tool list for the calling connection. An empty list removes every tool previously supplied by that connection. + * + * @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 SessionToolsSetParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Complete replacement list for the calling connection. */ + @JsonProperty("tools") List tools +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsTaskCompleteEventDataParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsTaskCompleteEventDataParams.java new file mode 100644 index 0000000000..9902fb8a99 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsTaskCompleteEventDataParams.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; + +/** + * Task-completion tool arguments and final result used to build a label-safe session event payload. + * + * @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 SessionToolsTaskCompleteEventDataParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Arguments supplied to the completed task_complete tool call. */ + @JsonProperty("toolArgs") Object toolArgs, + /** Final expanded result returned by the task_complete tool. */ + @JsonProperty("finalResult") ToolResultExpanded finalResult +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsTaskCompleteEventDataResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsTaskCompleteEventDataResult.java new file mode 100644 index 0000000000..f95ad8a76e --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsTaskCompleteEventDataResult.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; + +/** + * Task completion notification with summary from the 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 SessionToolsTaskCompleteEventDataResult( + /** Summary of the completed task, provided by the agent */ + @JsonProperty("summary") String summary, + /** 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/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 79% 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 index ad754767e5..72207e55bb 100644 --- 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 @@ -11,6 +11,7 @@ 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; /** @@ -26,9 +27,15 @@ public record SessionUiElicitationParams( /** Target session identifier */ @JsonProperty("sessionId") String sessionId, + /** Elicitation mode. Omitted and form are equivalent for structured elicitation. */ + @JsonProperty("mode") McpElicitationFormMode mode, /** Message describing what information is needed from the user */ @JsonProperty("message") String message, /** JSON Schema describing the form fields to present to the user */ - @JsonProperty("requestedSchema") UIElicitationSchema requestedSchema + @JsonProperty("requestedSchema") UIElicitationSchema requestedSchema, + /** MCP request metadata. */ + @JsonProperty("_meta") Map> meta, + /** MCP task metadata. */ + @JsonProperty("task") McpTaskMetadata task ) { } 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 89% 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 index b290cc7c64..bf250f9d7d 100644 --- 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 @@ -28,6 +28,8 @@ public record SessionUiElicitationResult( /** The user's response: accept (submitted), decline (rejected), or cancel (dismissed) */ @JsonProperty("action") UIElicitationResponseAction action, /** The form values submitted by the user (present when action is 'accept') */ - @JsonProperty("content") Map content + @JsonProperty("content") Map content, + /** MCP response metadata. */ + @JsonProperty("_meta") Map> meta ) { } 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 80% 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 index b384238b28..0f02f7258d 100644 --- 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 @@ -28,9 +28,9 @@ public record SessionUiEphemeralQueryParams( @JsonProperty("sessionId") String sessionId, /** Question to answer from the current conversation context. */ @JsonProperty("question") String question, - /** 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 streaming callback `(text) => void` invoked with each token as the model emits it. Internal and excluded from the public SDK surface. */ @JsonProperty("onChunk") Object onChunk, - /** 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. */ + /** In-process `AbortSignal` forwarded to the model client to cancel an in-flight request. Internal and excluded from the public SDK surface. */ @JsonProperty("abortSignal") Object abortSignal ) { } 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 84% 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 index 6ac9058b04..e8b6dd90cb 100644 --- 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 @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Transient answer generated from current conversation context. + * Completed transient query. Ordered chunks and the terminal outcome are also delivered through `ui.ephemeral_query` session events while it runs. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 @@ -24,7 +24,7 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record SessionUiEphemeralQueryResult( - /** Full assistant response text. */ + /** Answer returned by the model */ @JsonProperty("answer") String answer ) { } 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 92% 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 index 69db5a56d3..a38afd7042 100644 --- 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 @@ -42,6 +42,8 @@ public record SessionUsageGetMetricsResult( @JsonProperty("codeChanges") UsageMetricsCodeChanges codeChanges, /** Per-model token and request metrics, keyed by model identifier */ @JsonProperty("modelMetrics") Map modelMetrics, + /** Per-agent usage metrics, keyed by agent instance identifier. The main conversation uses the stable key `main`. */ + @JsonProperty("agentMetrics") Map agentMetrics, /** Currently active model identifier */ @JsonProperty("currentModel") String currentModel, /** Input tokens from the most recent main-agent API call */ 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/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesAddSummaryParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesAddSummaryParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesAddSummaryParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesAddSummaryParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesAddSummaryResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesAddSummaryResult.java similarity index 91% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesAddSummaryResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesAddSummaryResult.java index a50a0b5f53..6dd474df60 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesAddSummaryResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesAddSummaryResult.java @@ -25,7 +25,9 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record SessionWorkspacesAddSummaryResult( + /** Metadata for the persisted summary. */ @JsonProperty("summary") Map summary, + /** Refreshed metadata for the containing workspace. */ @JsonProperty("workspace") Map workspace ) { } 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/SessionWorkspacesApi.java similarity index 100% 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/SessionWorkspacesApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesAutopilotObjectiveExistsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesAutopilotObjectiveExistsParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesAutopilotObjectiveExistsParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesAutopilotObjectiveExistsParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesAutopilotObjectiveExistsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesAutopilotObjectiveExistsResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesAutopilotObjectiveExistsResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesAutopilotObjectiveExistsResult.java 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/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesDeleteAutopilotObjectiveParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesDeleteAutopilotObjectiveParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesDeleteAutopilotObjectiveParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesDeleteAutopilotObjectiveParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesDeleteAutopilotObjectiveResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesDeleteAutopilotObjectiveResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesDeleteAutopilotObjectiveResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesDeleteAutopilotObjectiveResult.java 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 100% 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 diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesEnsureParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesEnsureParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesEnsureParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesEnsureParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesEnsureResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesEnsureResult.java similarity index 71% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesEnsureResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesEnsureResult.java index 4a810fe121..c2d7e9ce37 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesEnsureResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesEnsureResult.java @@ -34,23 +34,39 @@ public record SessionWorkspacesEnsureResult( @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public record SessionWorkspacesEnsureResultWorkspace( + /** Stable workspace identifier. */ @JsonProperty("id") String id, + /** Current working directory associated with the workspace. */ @JsonProperty("cwd") String cwd, + /** Git repository root associated with the workspace. */ @JsonProperty("git_root") String gitRoot, + /** Repository identifier associated with the workspace. */ @JsonProperty("repository") String repository, /** Allowed values for the `WorkspacesWorkspaceDetailsHostType` enumeration. */ @JsonProperty("host_type") WorkspacesWorkspaceDetailsHostType hostType, + /** Current Git branch. */ @JsonProperty("branch") String branch, + /** Workspace display name. */ @JsonProperty("name") String name, + /** Name of the client that created the workspace. */ @JsonProperty("client_name") String clientName, + /** Whether the workspace name was explicitly chosen by the user. */ @JsonProperty("user_named") Boolean userNamed, + /** Number of persisted summaries in the workspace. */ @JsonProperty("summary_count") Long summaryCount, + /** Timestamp when the workspace was created. */ @JsonProperty("created_at") OffsetDateTime createdAt, + /** Timestamp when the workspace was last updated. */ @JsonProperty("updated_at") OffsetDateTime updatedAt, + /** Whether the workspace session can be steered remotely. */ @JsonProperty("remote_steerable") Boolean remoteSteerable, + /** Mission Control task identifier associated with the workspace. */ @JsonProperty("mc_task_id") String mcTaskId, + /** Mission Control session identifier associated with the workspace. */ @JsonProperty("mc_session_id") String mcSessionId, + /** Most recent Mission Control event identifier observed for the workspace. */ @JsonProperty("mc_last_event_id") String mcLastEventId, + /** Whether the per-session Chronicle upgrade prompt was dismissed for the workspace. */ @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 71% 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 index 217ac7d449..6f4714a594 100644 --- 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 @@ -34,23 +34,39 @@ public record SessionWorkspacesGetWorkspaceResult( @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public record SessionWorkspacesGetWorkspaceResultWorkspace( + /** Stable workspace identifier. */ @JsonProperty("id") String id, + /** Current working directory associated with the workspace. */ @JsonProperty("cwd") String cwd, + /** Git repository root associated with the workspace. */ @JsonProperty("git_root") String gitRoot, + /** Repository identifier associated with the workspace. */ @JsonProperty("repository") String repository, /** Allowed values for the `WorkspacesWorkspaceDetailsHostType` enumeration. */ @JsonProperty("host_type") WorkspacesWorkspaceDetailsHostType hostType, + /** Current Git branch. */ @JsonProperty("branch") String branch, + /** Workspace display name. */ @JsonProperty("name") String name, + /** Name of the client that created the workspace. */ @JsonProperty("client_name") String clientName, + /** Whether the workspace name was explicitly chosen by the user. */ @JsonProperty("user_named") Boolean userNamed, + /** Number of persisted summaries in the workspace. */ @JsonProperty("summary_count") Long summaryCount, + /** Timestamp when the workspace was created. */ @JsonProperty("created_at") OffsetDateTime createdAt, + /** Timestamp when the workspace was last updated. */ @JsonProperty("updated_at") OffsetDateTime updatedAt, + /** Whether the workspace session can be steered remotely. */ @JsonProperty("remote_steerable") Boolean remoteSteerable, + /** Mission Control task identifier associated with the workspace. */ @JsonProperty("mc_task_id") String mcTaskId, + /** Mission Control session identifier associated with the workspace. */ @JsonProperty("mc_session_id") String mcSessionId, + /** Most recent Mission Control event identifier observed for the workspace. */ @JsonProperty("mc_last_event_id") String mcLastEventId, + /** Whether the per-session Chronicle upgrade prompt was dismissed for the workspace. */ @JsonProperty("chronicle_sync_dismissed") Boolean chronicleSyncDismissed ) { } 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/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadAutopilotObjectiveParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadAutopilotObjectiveParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadAutopilotObjectiveParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadAutopilotObjectiveParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadAutopilotObjectiveResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadAutopilotObjectiveResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadAutopilotObjectiveResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadAutopilotObjectiveResult.java 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/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesTruncateSummariesParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesTruncateSummariesParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesTruncateSummariesParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesTruncateSummariesParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesTruncateSummariesResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesTruncateSummariesResult.java similarity index 71% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesTruncateSummariesResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesTruncateSummariesResult.java index caa44d0b7a..1dc348e67e 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesTruncateSummariesResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesTruncateSummariesResult.java @@ -34,23 +34,39 @@ public record SessionWorkspacesTruncateSummariesResult( @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public record SessionWorkspacesTruncateSummariesResultWorkspace( + /** Stable workspace identifier. */ @JsonProperty("id") String id, + /** Current working directory associated with the workspace. */ @JsonProperty("cwd") String cwd, + /** Git repository root associated with the workspace. */ @JsonProperty("git_root") String gitRoot, + /** Repository identifier associated with the workspace. */ @JsonProperty("repository") String repository, /** Allowed values for the `WorkspacesWorkspaceDetailsHostType` enumeration. */ @JsonProperty("host_type") WorkspacesWorkspaceDetailsHostType hostType, + /** Current Git branch. */ @JsonProperty("branch") String branch, + /** Workspace display name. */ @JsonProperty("name") String name, + /** Name of the client that created the workspace. */ @JsonProperty("client_name") String clientName, + /** Whether the workspace name was explicitly chosen by the user. */ @JsonProperty("user_named") Boolean userNamed, + /** Number of persisted summaries in the workspace. */ @JsonProperty("summary_count") Long summaryCount, + /** Timestamp when the workspace was created. */ @JsonProperty("created_at") OffsetDateTime createdAt, + /** Timestamp when the workspace was last updated. */ @JsonProperty("updated_at") OffsetDateTime updatedAt, + /** Whether the workspace session can be steered remotely. */ @JsonProperty("remote_steerable") Boolean remoteSteerable, + /** Mission Control task identifier associated with the workspace. */ @JsonProperty("mc_task_id") String mcTaskId, + /** Mission Control session identifier associated with the workspace. */ @JsonProperty("mc_session_id") String mcSessionId, + /** Most recent Mission Control event identifier observed for the workspace. */ @JsonProperty("mc_last_event_id") String mcLastEventId, + /** Whether the per-session Chronicle upgrade prompt was dismissed for the workspace. */ @JsonProperty("chronicle_sync_dismissed") Boolean chronicleSyncDismissed ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesUpdateMetadataParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesUpdateMetadataParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesUpdateMetadataParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesUpdateMetadataParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesUpdateMetadataResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesUpdateMetadataResult.java similarity index 71% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesUpdateMetadataResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesUpdateMetadataResult.java index 84ec136616..03731650a8 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesUpdateMetadataResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesUpdateMetadataResult.java @@ -34,23 +34,39 @@ public record SessionWorkspacesUpdateMetadataResult( @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public record SessionWorkspacesUpdateMetadataResultWorkspace( + /** Stable workspace identifier. */ @JsonProperty("id") String id, + /** Current working directory associated with the workspace. */ @JsonProperty("cwd") String cwd, + /** Git repository root associated with the workspace. */ @JsonProperty("git_root") String gitRoot, + /** Repository identifier associated with the workspace. */ @JsonProperty("repository") String repository, /** Allowed values for the `WorkspacesWorkspaceDetailsHostType` enumeration. */ @JsonProperty("host_type") WorkspacesWorkspaceDetailsHostType hostType, + /** Current Git branch. */ @JsonProperty("branch") String branch, + /** Workspace display name. */ @JsonProperty("name") String name, + /** Name of the client that created the workspace. */ @JsonProperty("client_name") String clientName, + /** Whether the workspace name was explicitly chosen by the user. */ @JsonProperty("user_named") Boolean userNamed, + /** Number of persisted summaries in the workspace. */ @JsonProperty("summary_count") Long summaryCount, + /** Timestamp when the workspace was created. */ @JsonProperty("created_at") OffsetDateTime createdAt, + /** Timestamp when the workspace was last updated. */ @JsonProperty("updated_at") OffsetDateTime updatedAt, + /** Whether the workspace session can be steered remotely. */ @JsonProperty("remote_steerable") Boolean remoteSteerable, + /** Mission Control task identifier associated with the workspace. */ @JsonProperty("mc_task_id") String mcTaskId, + /** Mission Control session identifier associated with the workspace. */ @JsonProperty("mc_session_id") String mcSessionId, + /** Most recent Mission Control event identifier observed for the workspace. */ @JsonProperty("mc_last_event_id") String mcLastEventId, + /** Whether the per-session Chronicle upgrade prompt was dismissed for the workspace. */ @JsonProperty("chronicle_sync_dismissed") Boolean chronicleSyncDismissed ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesWriteAutopilotObjectiveParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesWriteAutopilotObjectiveParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesWriteAutopilotObjectiveParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesWriteAutopilotObjectiveParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesWriteAutopilotObjectiveResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesWriteAutopilotObjectiveResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesWriteAutopilotObjectiveResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesWriteAutopilotObjectiveResult.java 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/src/generated/java/com/github/copilot/generated/rpc/SessionsDeleteParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsDeleteParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsDeleteParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsDeleteParams.java 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/src/generated/java/com/github/copilot/generated/rpc/SessionsGetMetadataParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsGetMetadataParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsGetMetadataParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsGetMetadataParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsGetMetadataResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsGetMetadataResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsGetMetadataResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsGetMetadataResult.java 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/src/generated/java/com/github/copilot/generated/rpc/SessionsListNonEmptySessionIdsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsListNonEmptySessionIdsParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsListNonEmptySessionIdsParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsListNonEmptySessionIdsParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsListNonEmptySessionIdsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsListNonEmptySessionIdsResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsListNonEmptySessionIdsResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsListNonEmptySessionIdsResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsListParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsListParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsListParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsListParams.java 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/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenAttach.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenAttach.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenAttach.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenAttach.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenCloud.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenCloud.java similarity index 88% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenCloud.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenCloud.java index 8e4a74bd88..b0be6d42dd 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenCloud.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenCloud.java @@ -40,7 +40,7 @@ public final class SessionsOpenCloud extends SessionsOpenParams { @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. */ + /** In-process callback invoked when the cloud task is created, before connection. Internal because function references cannot cross the JSON-RPC boundary. */ @JsonProperty("onTaskCreated") private Object onTaskCreated; diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenCreate.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenCreate.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenCreate.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenCreate.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenHandoff.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenHandoff.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenHandoff.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenHandoff.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenHandoffTaskType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenHandoffTaskType.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenHandoffTaskType.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenHandoffTaskType.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenParams.java 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/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenRemote.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenRemote.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenRemote.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenRemote.java 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/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenResume.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenResume.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenResume.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenResume.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenResumeLast.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenResumeLast.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenResumeLast.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenResumeLast.java 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/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsReadPersistedEventsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsReadPersistedEventsParams.java new file mode 100644 index 0000000000..4da93409be --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsReadPersistedEventsParams.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; + +/** + * Pagination options for reading an inactive or active local session's persisted event journal. + * + * @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 SessionsReadPersistedEventsParams( + /** Session ID whose persisted event journal should be read. */ + @JsonProperty("sessionId") String sessionId, + /** Opaque cursor returned by a previous persisted-event read. Omit on the first call. */ + @JsonProperty("cursor") String cursor, + /** Maximum number of events to return in this batch (1–1000, default 200). */ + @JsonProperty("max") Long max, + /** Direction to page through persisted history. Forward starts at the beginning; backward starts with the newest events. Events in each page remain chronological. */ + @JsonProperty("direction") EventsReadDirection direction +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsReadPersistedEventsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsReadPersistedEventsResult.java new file mode 100644 index 0000000000..f022df5ae2 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsReadPersistedEventsResult.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 com.github.copilot.generated.SessionEvent; +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 SessionsReadPersistedEventsResult( + /** 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/SessionsRegisterExtensionToolsOnSessionOptions.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsRegisterExtensionToolsOnSessionOptions.java similarity index 84% 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 index 440c01b2a8..e19737e32e 100644 --- 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 @@ -21,7 +21,7 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record SessionsRegisterExtensionToolsOnSessionOptions( - /** In-process `() => boolean` gating callback (CLI-only optimization). Marked internal: replaced by runtime-side enable/disable RPCs in the SDK migration. */ + /** In-process `() => boolean` gating callback used only by the CLI. */ @JsonProperty("enabled") Object enabled ) { } 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 79% 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 index d7eb48f2b3..7fcb3322e9 100644 --- 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 @@ -26,7 +26,7 @@ public record SessionsRegisterExtensionToolsOnSessionParams( /** Session to register extension tools on. */ @JsonProperty("sessionId") String sessionId, - /** 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. */ + /** In-process ExtensionLoader handle used only by the CLI and excluded from the public SDK surface. */ @JsonProperty("loader") Object loader, /** Optional registration options. */ @JsonProperty("options") SessionsRegisterExtensionToolsOnSessionOptions options 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 87% 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 index 63cc5fb0fb..5cb5af063b 100644 --- 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 @@ -24,7 +24,7 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record SessionsRegisterExtensionToolsOnSessionResult( - /** In-process unsubscribe function (CLI-only optimization). Marked internal: replaced by an explicit `extensions.unregister` RPC in the SDK migration. */ + /** In-process unsubscribe function used only by the CLI. */ @JsonProperty("unsubscribe") Object unsubscribe ) { } 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/src/generated/java/com/github/copilot/generated/rpc/SessionsStopRemoteControlParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsStopRemoteControlParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsStopRemoteControlParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsStopRemoteControlParams.java 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/ShellCredentials.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ShellCredentials.java new file mode 100644 index 0000000000..53567c98db --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ShellCredentials.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 javax.annotation.processing.Generated; + +/** + * Command-scoped GitHub credential injection for the shell commands an agent runs. + +Each channel is opt-in and independent, and injection is scoped to the individual command +spawn: the credential is resolved from the session's *current* authentication at every spawn +and reaches only spawns whose script actually invokes `git` or `gh`. Because nothing is +retained between spawns, replacing the session credential (`session.gitHubAuth.setCredentials`) +changes what the next spawned command presents — which seeding a credential into the runtime +process's own environment cannot do, since a child's environment is fixed at `exec`. + +The credential is matched to the host it authenticates to, so a github.com credential is never +presented to a GitHub Enterprise host and vice versa. Where a channel cannot express that +boundary it injects nothing rather than crossing it -- see `gh` below. + +This is independent of `sandboxConfig`: it is a decision about which identity the agent +presents, not about what the agent may touch, and it works on every platform whether or not +an OS sandboxing backend is available. `sandboxConfig.auth` remains the sandbox-scoped +spelling and is additive with this one. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record ShellCredentials( + /** Whether to authenticate the agent's `git` commands as the session's GitHub credential, by +injecting an `http..extraheader` (plus `insteadOf` rewrites so SSH-spelled remotes for +that host use the authenticated HTTPS transport). Applied only to a spawn that runs a +remote-contacting `git` subcommand. Default: false (opt-in). */ + @JsonProperty("git") Boolean git, + /** Whether to authenticate the agent's `gh` commands as the session's GitHub credential, by +exporting `GH_TOKEN` to a spawn that runs `gh`. Any inherited `gh` credential is removed from +spawns that do not, so the credential stays command-scoped. + +Applies to a github.com credential only. `gh` picks its credential variable from the host a +command targets rather than the one the credential belongs to, and the command can choose that +target, so `GH_ENTERPRISE_TOKEN` would offer a single-tenant enterprise credential to every +other enterprise host. A session whose credential is enterprise-scoped therefore runs `gh` +unauthenticated; its `git` commands are unaffected, because `http..extraheader` is scoped +to one host by construction. Default: false (opt-in). */ + @JsonProperty("gh") Boolean gh +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ShellInitProfile.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ShellInitProfile.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ShellInitProfile.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ShellInitProfile.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ShellInitScript.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ShellInitScript.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ShellInitScript.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ShellInitScript.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ShellInitScriptShell.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ShellInitScriptShell.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ShellInitScriptShell.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ShellInitScriptShell.java 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/src/generated/java/com/github/copilot/generated/rpc/ShellOptions.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ShellOptions.java similarity index 91% rename from java/src/generated/java/com/github/copilot/generated/rpc/ShellOptions.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ShellOptions.java index 596de0ef98..ea152a59fc 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/ShellOptions.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ShellOptions.java @@ -37,6 +37,8 @@ public record ShellOptions( /** 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 + @JsonProperty("processFlags") List processFlags, + /** Command-scoped GitHub credential injection for shell commands. */ + @JsonProperty("credentials") ShellCredentials credentials ) { } 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 4ad4116871..88e7eb1169 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/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillProviderDescriptor.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillProviderDescriptor.java new file mode 100644 index 0000000000..a678b95fa3 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillProviderDescriptor.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 javax.annotation.processing.Generated; + +/** + * Catalog-only metadata for one SDK-provided skill. The complete SKILL.md is fetched separately and lazily. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SkillProviderDescriptor( + /** Invocation and display name. */ + @JsonProperty("name") String name, + /** Description used in skill catalogs without fetching content. */ + @JsonProperty("description") String description, + /** Whether users may invoke the skill directly. Defaults to true. */ + @JsonProperty("userInvocable") Boolean userInvocable, + /** Whether model invocation is disabled. Defaults to false. */ + @JsonProperty("disableModelInvocation") Boolean disableModelInvocation, + /** Optional freeform argument hint used by slash-command catalogs. */ + @JsonProperty("argumentHint") String argumentHint +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillProviderListParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillProviderListParams.java new file mode 100644 index 0000000000..e11fcf0fbd --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillProviderListParams.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 SkillProviderListParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillProviderListResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillProviderListResult.java new file mode 100644 index 0000000000..228ec9240b --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillProviderListResult.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; + +/** + * Catalog metadata returned by an SDK session's skill provider. Catalogs are limited to 1024 descriptors and 1 MiB of aggregate 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 SkillProviderListResult( + /** Skill descriptors in provider order. Invocation names must be unique under case-insensitive comparison. */ + @JsonProperty("skills") List skills +) { +} 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/SkillProviderReadParams.java similarity index 84% 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/SkillProviderReadParams.java index 4ea732727d..895078384b 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionModeSetParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillProviderReadParams.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Agent interaction mode to apply to the session. + * Identifies one SDK-provided skill by invocation name. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 @@ -23,10 +23,10 @@ @javax.annotation.processing.Generated("copilot-sdk-codegen") @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) -public record SessionModeSetParams( +public record SkillProviderReadParams( /** Target session identifier */ @JsonProperty("sessionId") String sessionId, - /** The session mode the agent is operating in */ - @JsonProperty("mode") SessionMode mode + /** Invocation name of the skill to read. */ + @JsonProperty("name") String name ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillProviderReadResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillProviderReadResult.java new file mode 100644 index 0000000000..0f17696565 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillProviderReadResult.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; + +/** + * Complete text-only SKILL.md content returned by an SDK session's skill provider. Related files and assets are not supported. + * + * @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 SkillProviderReadResult( + /** Complete SKILL.md text. The runtime enforces a 1 MiB UTF-8 byte limit. */ + @JsonProperty("markdown") String markdown +) { +} 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 94% 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 index 8d723548be..bef1995e6c 100644 --- 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 @@ -10,7 +10,7 @@ import javax.annotation.processing.Generated; /** - * Source location type (e.g., project, personal-copilot, plugin, builtin) + * Source location type (e.g., project, personal-copilot, plugin, builtin, sdk) * * @since 1.0.0 */ @@ -29,7 +29,9 @@ public enum SkillSource { /** The {@code custom} variant. */ CUSTOM("custom"), /** The {@code builtin} variant. */ - BUILTIN("builtin"); + BUILTIN("builtin"), + /** The {@code sdk} variant. */ + SDK("sdk"); private final String value; SkillSource(String value) { this.value = value; } 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/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillsConfigSetSkillDisabledParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillsConfigSetSkillDisabledParams.java new file mode 100644 index 0000000000..cce64217c2 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillsConfigSetSkillDisabledParams.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; + +/** + * Adds or removes a single skill from the global disabled list, leaving every other entry untouched. + * + * @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 SkillsConfigSetSkillDisabledParams( + /** Name of the skill to add to or remove from the disabled list */ + @JsonProperty("name") String name, + /** True to disable the skill, false to enable it */ + @JsonProperty("disabled") Boolean disabled +) { +} 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 83% 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 index a020c89ecf..5e1f6fd90e 100644 --- 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 @@ -24,12 +24,14 @@ public record SkillsInvokedSkill( /** Unique identifier for the skill */ @JsonProperty("name") String name, - /** Path to the SKILL.md file */ + /** Path to the SKILL.md file, or an empty string for an SDK-provided skill without a filesystem identity */ @JsonProperty("path") String path, /** Full content of the skill file */ @JsonProperty("content") String content, /** Tools that should be auto-approved when this skill is active, captured at invocation time */ @JsonProperty("allowedTools") List allowedTools, + /** Whether model invocation was disabled when this skill was invoked */ + @JsonProperty("disableModelInvocation") Boolean disableModelInvocation, /** Turn number when the skill was invoked */ @JsonProperty("invokedAtTurn") Long invokedAtTurn ) { 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 87% 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 index b2a1970a36..c457181435 100644 --- 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 @@ -32,6 +32,10 @@ public final class SlashCommandCompletedResult extends SlashCommandInvocationRes @JsonProperty("message") private String message; + /** Optional target session mode applied without submitting an agent prompt */ + @JsonProperty("mode") + private SessionMode mode; + /** True when the invocation mutated user runtime settings; consumers caching settings should refresh */ @JsonProperty("runtimeSettingsChanged") private Boolean runtimeSettingsChanged; @@ -39,6 +43,9 @@ public final class SlashCommandCompletedResult extends SlashCommandInvocationRes public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } + public SessionMode getMode() { return mode; } + public void setMode(SessionMode mode) { this.mode = mode; } + public Boolean getRuntimeSettingsChanged() { return runtimeSettingsChanged; } public void setRuntimeSettingsChanged(Boolean runtimeSettingsChanged) { this.runtimeSettingsChanged = runtimeSettingsChanged; } } 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 76% 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 index 336c0eda84..9c0d0979cc 100644 --- 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 @@ -22,7 +22,11 @@ @JsonSubTypes.Type(value = SlashCommandTextResult.class, name = "text"), @JsonSubTypes.Type(value = SlashCommandAgentPromptResult.class, name = "agent-prompt"), @JsonSubTypes.Type(value = SlashCommandCompletedResult.class, name = "completed"), - @JsonSubTypes.Type(value = SlashCommandSelectSubcommandResult.class, name = "select-subcommand") + @JsonSubTypes.Type(value = SlashCommandSelectSubcommandResult.class, name = "select-subcommand"), + @JsonSubTypes.Type(value = SlashCommandInvocationResultAddTimelineEntry.class, name = "add-timeline-entry"), + @JsonSubTypes.Type(value = SlashCommandInvocationResultShowDialog.class, name = "show-dialog"), + @JsonSubTypes.Type(value = SlashCommandInvocationResultSetModel.class, name = "set-model"), + @JsonSubTypes.Type(value = SlashCommandInvocationResultSetPlanModel.class, name = "set-plan-model") }) @JsonIgnoreProperties(ignoreUnknown = true) @javax.annotation.processing.Generated("copilot-sdk-codegen") diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInvocationResultAddTimelineEntry.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInvocationResultAddTimelineEntry.java new file mode 100644 index 0000000000..890ed79664 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInvocationResultAddTimelineEntry.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; + +/** + * Variant {@code add-timeline-entry} of {@link SlashCommandInvocationResult}. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SlashCommandInvocationResultAddTimelineEntry extends SlashCommandInvocationResult { + + @JsonProperty("kind") + private final String kind = "add-timeline-entry"; + + @Override + public String getKind() { return kind; } + + /** Timeline entry the host should append. */ + @JsonProperty("entry") + private SlashCommandTimelineEntry entry; + + /** Optional text the host should prefill into the input editor. */ + @JsonProperty("prefillInput") + private String prefillInput; + + /** Whether command execution changed persisted runtime settings. */ + @JsonProperty("runtimeSettingsChanged") + private Boolean runtimeSettingsChanged; + + public SlashCommandTimelineEntry getEntry() { return entry; } + public void setEntry(SlashCommandTimelineEntry entry) { this.entry = entry; } + + public String getPrefillInput() { return prefillInput; } + public void setPrefillInput(String prefillInput) { this.prefillInput = prefillInput; } + + public Boolean getRuntimeSettingsChanged() { return runtimeSettingsChanged; } + public void setRuntimeSettingsChanged(Boolean runtimeSettingsChanged) { this.runtimeSettingsChanged = runtimeSettingsChanged; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInvocationResultSetModel.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInvocationResultSetModel.java new file mode 100644 index 0000000000..95e4d8544c --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInvocationResultSetModel.java @@ -0,0 +1,80 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * Variant {@code set-model} of {@link SlashCommandInvocationResult}. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SlashCommandInvocationResultSetModel extends SlashCommandInvocationResult { + + @JsonProperty("kind") + private final String kind = "set-model"; + + @Override + public String getKind() { return kind; } + + /** Model selected by the command. */ + @JsonProperty("model") + private String model; + + /** Settings scope modified by the command. */ + @JsonProperty("scope") + private String scope; + + /** User-facing warning produced while selecting the model. */ + @JsonProperty("warning") + private String warning; + + /** Reasoning effort selected for the model. */ + @JsonProperty("reasoningEffort") + private String reasoningEffort; + + /** User-settings snapshot to restore if the host cancels the model switch. */ + @JsonProperty("revertOnCancel") + private Map revertOnCancel; + + /** Repository settings scope modified by the command. */ + @JsonProperty("repoScope") + private String repoScope; + + /** Whether command execution changed persisted runtime settings. */ + @JsonProperty("runtimeSettingsChanged") + private Boolean runtimeSettingsChanged; + + public String getModel() { return model; } + public void setModel(String model) { this.model = model; } + + public String getScope() { return scope; } + public void setScope(String scope) { this.scope = scope; } + + public String getWarning() { return warning; } + public void setWarning(String warning) { this.warning = warning; } + + public String getReasoningEffort() { return reasoningEffort; } + public void setReasoningEffort(String reasoningEffort) { this.reasoningEffort = reasoningEffort; } + + public Map getRevertOnCancel() { return revertOnCancel; } + public void setRevertOnCancel(Map revertOnCancel) { this.revertOnCancel = revertOnCancel; } + + public String getRepoScope() { return repoScope; } + public void setRepoScope(String repoScope) { this.repoScope = repoScope; } + + public Boolean getRuntimeSettingsChanged() { return runtimeSettingsChanged; } + public void setRuntimeSettingsChanged(Boolean runtimeSettingsChanged) { this.runtimeSettingsChanged = runtimeSettingsChanged; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInvocationResultSetPlanModel.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInvocationResultSetPlanModel.java new file mode 100644 index 0000000000..b57c5a0f52 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInvocationResultSetPlanModel.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; + +/** + * Variant {@code set-plan-model} of {@link SlashCommandInvocationResult}. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SlashCommandInvocationResultSetPlanModel extends SlashCommandInvocationResult { + + @JsonProperty("kind") + private final String kind = "set-plan-model"; + + @Override + public String getKind() { return kind; } + + /** Dedicated model selected for plan mode. */ + @JsonProperty("planModel") + private String planModel; + + /** User-facing confirmation message for the plan-model selection. */ + @JsonProperty("message") + private String message; + + /** Whether command execution changed persisted runtime settings. */ + @JsonProperty("runtimeSettingsChanged") + private Boolean runtimeSettingsChanged; + + public String getPlanModel() { return planModel; } + public void setPlanModel(String planModel) { this.planModel = planModel; } + + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } + + public Boolean getRuntimeSettingsChanged() { return runtimeSettingsChanged; } + public void setRuntimeSettingsChanged(Boolean runtimeSettingsChanged) { this.runtimeSettingsChanged = runtimeSettingsChanged; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInvocationResultShowDialog.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInvocationResultShowDialog.java new file mode 100644 index 0000000000..c8ef4b0702 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInvocationResultShowDialog.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; + +/** + * Variant {@code show-dialog} of {@link SlashCommandInvocationResult}. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SlashCommandInvocationResultShowDialog extends SlashCommandInvocationResult { + + @JsonProperty("kind") + private final String kind = "show-dialog"; + + @Override + public String getKind() { return kind; } + + /** Dialog the host should display. */ + @JsonProperty("dialog") + private SlashCommandModelPickerDialog dialog; + + /** Whether command execution changed persisted runtime settings. */ + @JsonProperty("runtimeSettingsChanged") + private Boolean runtimeSettingsChanged; + + public SlashCommandModelPickerDialog getDialog() { return dialog; } + public void setDialog(SlashCommandModelPickerDialog dialog) { this.dialog = dialog; } + + public Boolean getRuntimeSettingsChanged() { return runtimeSettingsChanged; } + public void setRuntimeSettingsChanged(Boolean runtimeSettingsChanged) { this.runtimeSettingsChanged = runtimeSettingsChanged; } +} 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/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandModelPickerDialog.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandModelPickerDialog.java new file mode 100644 index 0000000000..68ba4d29c5 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandModelPickerDialog.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; + +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SlashCommandModelPickerDialog( + /** Discriminator for a model-picker dialog. */ + @JsonProperty("kind") String kind, + /** Model that should be enabled before it can be selected. */ + @JsonProperty("modelToEnable") String modelToEnable, + /** Settings scope the picker should modify. */ + @JsonProperty("scope") String scope, + /** Model-selection target represented by the picker. */ + @JsonProperty("target") String target +) { +} 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/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandTimelineEntry.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandTimelineEntry.java new file mode 100644 index 0000000000..a238d7fb67 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandTimelineEntry.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; + +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SlashCommandTimelineEntry( + /** Timeline entry presentation type. */ + @JsonProperty("type") String type, + /** Text displayed for the timeline entry. */ + @JsonProperty("text") String text, + /** Optional URL associated with the timeline entry. */ + @JsonProperty("url") String url +) { +} 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 89% 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 index 29426d931b..4a366864c3 100644 --- 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 @@ -23,6 +23,8 @@ public record SubagentSettingsEntry( /** Model override for matching subagents */ @JsonProperty("model") String model, + /** Whether the configured model strategy is preferred or required */ + @JsonProperty("modelPolicy") AgentModelPolicy modelPolicy, /** Reasoning effort override for matching subagents */ @JsonProperty("effortLevel") String effortLevel, /** Context tier override for matching subagents */ 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/sdk/src/generated/java/com/github/copilot/generated/rpc/TaskCompletionDecision.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TaskCompletionDecision.java new file mode 100644 index 0000000000..e8ed6ef167 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TaskCompletionDecision.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 javax.annotation.processing.Generated; + +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record TaskCompletionDecision( + /** Semantic result of evaluating the task completion request. */ + @JsonProperty("outcome") TaskCompletionOutcome outcome, + /** Rationale for the completion decision, when one is available. */ + @JsonProperty("reason") String reason, + /** Whether the rationale was derived from completion-reviewer output. */ + @JsonProperty("reviewerDerived") Boolean reviewerDerived, + /** Information-flow metadata captured from the completion reviewer. */ + @JsonProperty("reviewerResultMeta") Object reviewerResultMeta, + /** Active autopilot objective evaluated by the completion reviewer. */ + @JsonProperty("objectiveId") Long objectiveId, + /** Whether completion was accepted after the reviewer-rejection budget was exhausted. */ + @JsonProperty("completionRejectionBudgetExhausted") Boolean completionRejectionBudgetExhausted, + /** Objective eligibility token captured when the decision was evaluated. */ + @JsonProperty("completionEligibilityToken") Long completionEligibilityToken +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TaskCompletionOutcome.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TaskCompletionOutcome.java new file mode 100644 index 0000000000..18bf78cdc8 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TaskCompletionOutcome.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; + +/** + * 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/sdk/src/generated/java/com/github/copilot/generated/rpc/TokenAuthInfo.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TokenAuthInfo.java new file mode 100644 index 0000000000..77f580b862 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TokenAuthInfo.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; + +/** + * Authentication-info input variant for SDK-configured token authentication, carrying host and the secret token value. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class TokenAuthInfo extends AuthInfo { + + @JsonProperty("type") + private final String type = "token"; + + @Override + public String getType() { return type; } + + /** Authentication host. */ + @JsonProperty("host") + private String host; + + /** The token value itself. Treat as a secret. */ + @JsonProperty("token") + private String token; + + /** Opaque native GitHub credential registration backing this token identity, when applicable. */ + @JsonProperty("registrationId") + private String registrationId; + + /** 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. */ + @JsonProperty("copilotUser") + private CopilotUserResponse copilotUser; + + public String getHost() { return host; } + public void setHost(String host) { this.host = host; } + + public String getToken() { return token; } + public void setToken(String token) { this.token = token; } + + public String getRegistrationId() { return registrationId; } + public void setRegistrationId(String registrationId) { this.registrationId = registrationId; } + + public CopilotUserResponse getCopilotUser() { return copilotUser; } + public void setCopilotUser(CopilotUserResponse copilotUser) { this.copilotUser = copilotUser; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TokenProviderAuthInfo.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TokenProviderAuthInfo.java new file mode 100644 index 0000000000..6bdb811e2b --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/TokenProviderAuthInfo.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; + +/** + * Authentication-info variant backed by an SDK GitHub token callback. It carries routing metadata but never a plaintext token. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class TokenProviderAuthInfo extends AuthInfo { + + @JsonProperty("type") + private final String type = "token-provider"; + + @Override + public String getType() { return type; } + + /** Authentication host. */ + @JsonProperty("host") + private String host; + + /** Opaque SDK callback registration identifier. */ + @JsonProperty("registrationId") + private String registrationId; + + /** Snapshot of the authenticated user's Copilot subscription info, if known. */ + @JsonProperty("copilotUser") + private CopilotUserResponse copilotUser; + + public String getHost() { return host; } + public void setHost(String host) { this.host = host; } + + public String getRegistrationId() { return registrationId; } + public void setRegistrationId(String registrationId) { this.registrationId = registrationId; } + + public CopilotUserResponse getCopilotUser() { return copilotUser; } + public void setCopilotUser(CopilotUserResponse copilotUser) { this.copilotUser = copilotUser; } +} 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/sdk/src/generated/java/com/github/copilot/generated/rpc/ToolResultExpanded.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ToolResultExpanded.java new file mode 100644 index 0000000000..47527cfec8 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ToolResultExpanded.java @@ -0,0 +1,61 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * Expanded canonical result returned by a session tool. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record ToolResultExpanded( + /** Text result returned to the model. */ + @JsonProperty("textResultForLlm") String textResultForLlm, + /** Execution outcome classification. */ + @JsonProperty("resultType") ToolResultType resultType, + /** Base64-encoded binary results returned to the model. */ + @JsonProperty("binaryResultsForLlm") List binaryResultsForLlm, + /** Detailed log content available for session display. */ + @JsonProperty("sessionLog") String sessionLog, + /** Error message for an unsuccessful execution. */ + @JsonProperty("error") String error, + /** Tool-specific telemetry payload. */ + @JsonProperty("toolTelemetry") Object toolTelemetry, + /** Whether large-output post-processing should be skipped. */ + @JsonProperty("skipLargeOutputProcessing") Boolean skipLargeOutputProcessing, + /** Messages to inject after the tool result. */ + @JsonProperty("newMessages") List newMessages, + /** Structured content blocks returned to the model. */ + @JsonProperty("contents") List contents, + /** Deferred tool names made available by this result. */ + @JsonProperty("toolReferences") List toolReferences, + /** Sources returned by the tool that the model may cite. */ + @JsonProperty("citableSources") List citableSources, + /** Skill invocation metadata produced by the tool. */ + @JsonProperty("skillInvocation") Object skillInvocation, + /** Whether post-tool-use failure hooks have already processed this result. */ + @JsonProperty("postToolUseFailureHooksProcessed") Boolean postToolUseFailureHooksProcessed, + /** Optional UI resource produced by the tool. */ + @JsonProperty("uiResource") Object uiResource, + /** Metadata propagated with the tool result, including information-flow labels. */ + @JsonProperty("mcpMeta") Map mcpMeta, + /** Structured result content in addition to the model-facing text. */ + @JsonProperty("structuredContent") Object structuredContent, + /** Completion-review decision produced by the task-completion tool. */ + @JsonProperty("taskCompletionDecision") TaskCompletionDecision taskCompletionDecision +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ToolResultNewMessage.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ToolResultNewMessage.java new file mode 100644 index 0000000000..da203a0e98 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ToolResultNewMessage.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 message injected by a tool result. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record ToolResultNewMessage( + /** Message content to inject after the tool result. */ + @JsonProperty("content") String content, + /** Source attributed to the injected message. */ + @JsonProperty("source") String source +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ToolResultType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ToolResultType.java new file mode 100644 index 0000000000..bc3b5ae2ac --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ToolResultType.java @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * 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; + +/** + * Execution outcome classification. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum ToolResultType { + /** The {@code success} variant. */ + SUCCESS("success"), + /** The {@code failure} variant. */ + FAILURE("failure"), + /** The {@code timeout} variant. */ + TIMEOUT("timeout"), + /** The {@code rejected} variant. */ + REJECTED("rejected"), + /** The {@code denied} variant. */ + DENIED("denied"); + + private final String value; + ToolResultType(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static ToolResultType fromValue(String value) { + for (ToolResultType v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown ToolResultType value: " + value); + } +} 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/sdk/src/generated/java/com/github/copilot/generated/rpc/ToolsShellDescriptorConfig.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ToolsShellDescriptorConfig.java new file mode 100644 index 0000000000..fdd5a716a5 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ToolsShellDescriptorConfig.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 java.util.List; +import javax.annotation.processing.Generated; + +/** + * Shell-specific names and description lines used to materialize built-in shell tool descriptors. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record ToolsShellDescriptorConfig( + /** Stable shell type identifier. */ + @JsonProperty("shellType") String shellType, + /** Human-readable shell name. */ + @JsonProperty("displayName") String displayName, + /** Tool name used to start shell commands. */ + @JsonProperty("shellToolName") String shellToolName, + /** Tool name used to read shell output. */ + @JsonProperty("readShellToolName") String readShellToolName, + /** Tool name used to stop shell commands. */ + @JsonProperty("stopShellToolName") String stopShellToolName, + /** Tool name used to list active shells. */ + @JsonProperty("listShellsToolName") String listShellsToolName, + /** Additional model-facing shell description lines. */ + @JsonProperty("descriptionLines") List descriptionLines +) { +} 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 87% 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 index e157ee727b..d611a8b447 100644 --- 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 @@ -25,6 +25,8 @@ public record UIElicitationResponse( /** The user's response: accept (submitted), decline (rejected), or cancel (dismissed) */ @JsonProperty("action") UIElicitationResponseAction action, /** The form values submitted by the user (present when action is 'accept') */ - @JsonProperty("content") Map content + @JsonProperty("content") Map content, + /** MCP response metadata. */ + @JsonProperty("_meta") Map> meta ) { } 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 100% 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 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/sdk/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsAgentMetric.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsAgentMetric.java new file mode 100644 index 0000000000..457ba2109e --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsAgentMetric.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 java.util.Map; +import javax.annotation.processing.Generated; + +/** + * Usage attributed to one agent instance, including its identity, API duration, AI units, and per-model breakdown. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record UsageMetricsAgentMetric( + /** Configured agent name, when this is a subagent */ + @JsonProperty("agentName") String agentName, + /** Human-readable label for this subagent invocation, copied from the originating `subagent.started` event. For task-tool subagents this is the invocation's task description rather than the agent's configured display name, so group by `agentName` for stable per-agent labels. */ + @JsonProperty("agentDisplayName") String agentDisplayName, + /** Time spent in model API calls by this agent, in milliseconds */ + @JsonProperty("totalApiDurationMs") Long totalApiDurationMs, + /** Accumulated nano-AI units cost for this agent */ + @JsonProperty("totalNanoAiu") Double totalNanoAiu, + /** Per-model usage for this agent, keyed by model identifier */ + @JsonProperty("modelMetrics") Map modelMetrics +) { +} 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/sdk/src/generated/java/com/github/copilot/generated/rpc/UserAuthInfo.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserAuthInfo.java new file mode 100644 index 0000000000..6f136a17c2 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserAuthInfo.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; + +/** + * Authentication-info variant for OAuth user auth, with host and login; the token remains in the runtime secret store. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class UserAuthInfo extends AuthInfo { + + @JsonProperty("type") + private final String type = "user"; + + @Override + public String getType() { return type; } + + /** Authentication host. */ + @JsonProperty("host") + private String host; + + /** OAuth user login. */ + @JsonProperty("login") + private String login; + + /** 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. */ + @JsonProperty("copilotUser") + private CopilotUserResponse copilotUser; + + public String getHost() { return host; } + public void setHost(String host) { this.host = host; } + + public String getLogin() { return login; } + public void setLogin(String login) { this.login = login; } + + public CopilotUserResponse getCopilotUser() { return copilotUser; } + public void setCopilotUser(CopilotUserResponse copilotUser) { this.copilotUser = copilotUser; } +} 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 100% 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 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 91% rename from java/src/main/java/com/github/copilot/CliServerManager.java rename to java/sdk/src/main/java/com/github/copilot/CliServerManager.java index acc683a720..ff0d121945 100644 --- a/java/src/main/java/com/github/copilot/CliServerManager.java +++ b/java/sdk/src/main/java/com/github/copilot/CliServerManager.java @@ -11,6 +11,7 @@ import java.net.Socket; import java.net.URI; import java.nio.charset.StandardCharsets; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -19,6 +20,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import com.github.copilot.ffi.NativeRuntimeLoader; import com.github.copilot.rpc.CopilotClientOptions; /** @@ -64,7 +66,7 @@ void setConnectionToken(String connectionToken) { ProcessInfo startCliServer() throws IOException, InterruptedException { clearStderrBuffer(); - String cliPath = options.getCliPath() != null ? options.getCliPath() : "copilot"; + RuntimeLaunch launch = resolveCliLaunch(); var args = new ArrayList(); if (options.getCliArgs() != null) { @@ -106,7 +108,7 @@ ProcessInfo startCliServer() throws IOException, InterruptedException { args.add("--remote"); } - List command = resolveCliCommand(cliPath, args); + List command = resolveCliCommand(launch.executable(), args); var pb = new ProcessBuilder(command); pb.redirectErrorStream(false); @@ -122,51 +124,7 @@ ProcessInfo startCliServer() throws IOException, InterruptedException { pb.directory(new File(options.getCwd())); } - if (options.getEnvironment() != null) { - pb.environment().clear(); - pb.environment().putAll(options.getEnvironment()); - } - pb.environment().remove("NODE_DEBUG"); - - // Set auth token in environment if provided - if (options.getGitHubToken() != null && !options.getGitHubToken().isEmpty()) { - pb.environment().put("COPILOT_SDK_AUTH_TOKEN", options.getGitHubToken()); - } - - // Set Copilot home directory if configured - if (options.getCopilotHome() != null && !options.getCopilotHome().isEmpty()) { - pb.environment().put("COPILOT_HOME", options.getCopilotHome()); - } - - // Set connection token for TCP mode - if (connectionToken != null && !connectionToken.isEmpty()) { - pb.environment().put("COPILOT_CONNECTION_TOKEN", connectionToken); - } - - // Set telemetry environment variables if configured - if (options.getTelemetry() != null) { - var telemetry = options.getTelemetry(); - pb.environment().put("COPILOT_OTEL_ENABLED", "true"); - if (telemetry.getOtlpEndpoint() != null) { - pb.environment().put("OTEL_EXPORTER_OTLP_ENDPOINT", telemetry.getOtlpEndpoint()); - } - if (telemetry.getOtlpProtocol() != null) { - pb.environment().put("OTEL_EXPORTER_OTLP_PROTOCOL", telemetry.getOtlpProtocol()); - } - if (telemetry.getFilePath() != null) { - pb.environment().put("COPILOT_OTEL_FILE_EXPORTER_PATH", telemetry.getFilePath()); - } - if (telemetry.getExporterType() != null) { - pb.environment().put("COPILOT_OTEL_EXPORTER_TYPE", telemetry.getExporterType()); - } - if (telemetry.getSourceName() != null) { - pb.environment().put("COPILOT_OTEL_SOURCE_NAME", telemetry.getSourceName()); - } - if (telemetry.getCaptureContent().isPresent()) { - pb.environment().put("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT", - telemetry.getCaptureContent().get() ? "true" : "false"); - } - } + configureProcessEnvironment(pb); Process process = pb.start(); @@ -310,6 +268,75 @@ private List resolveCliCommand(String cliPath, List args) { return result; } + void configureProcessEnvironment(ProcessBuilder pb) { + if (options.getEnvironment() != null) { + pb.environment().clear(); + pb.environment().putAll(options.getEnvironment()); + } + pb.environment().remove("NODE_DEBUG"); + + // Set auth token in environment if provided + if (options.getGitHubToken() != null && !options.getGitHubToken().isEmpty()) { + pb.environment().put("COPILOT_SDK_AUTH_TOKEN", options.getGitHubToken()); + } + + // Set Copilot home directory if configured + if (options.getCopilotHome() != null && !options.getCopilotHome().isEmpty()) { + pb.environment().put("COPILOT_HOME", options.getCopilotHome()); + } + + // Set connection token for TCP mode + if (connectionToken != null && !connectionToken.isEmpty()) { + pb.environment().put("COPILOT_CONNECTION_TOKEN", connectionToken); + } + + // Set telemetry environment variables if configured + if (options.getTelemetry() != null) { + var telemetry = options.getTelemetry(); + pb.environment().put("COPILOT_OTEL_ENABLED", "true"); + if (telemetry.getOtlpEndpoint() != null) { + pb.environment().put("OTEL_EXPORTER_OTLP_ENDPOINT", telemetry.getOtlpEndpoint()); + } + if (telemetry.getOtlpProtocol() != null) { + pb.environment().put("OTEL_EXPORTER_OTLP_PROTOCOL", telemetry.getOtlpProtocol()); + } + if (telemetry.getFilePath() != null) { + pb.environment().put("COPILOT_OTEL_FILE_EXPORTER_PATH", telemetry.getFilePath()); + } + if (telemetry.getExporterType() != null) { + pb.environment().put("COPILOT_OTEL_EXPORTER_TYPE", telemetry.getExporterType()); + } + if (telemetry.getSourceName() != null) { + pb.environment().put("COPILOT_OTEL_SOURCE_NAME", telemetry.getSourceName()); + } + if (telemetry.getCaptureContent().isPresent()) { + pb.environment().put("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT", + telemetry.getCaptureContent().get() ? "true" : "false"); + } + } + } + + RuntimeLaunch resolveCliLaunch() throws IOException { + return resolveCliLaunch(System.getenv(NativeRuntimeLoader.COPILOT_CLI_PATH_ENV)); + } + + RuntimeLaunch resolveCliLaunch(String inheritedCliPath) throws IOException { + if (options.getCliPath() != null) { + return new RuntimeLaunch(options.getCliPath()); + } + + var environment = options.getEnvironment(); + String envCliPath = environment != null + ? environment.get(NativeRuntimeLoader.COPILOT_CLI_PATH_ENV) + : inheritedCliPath; + if (envCliPath != null && !envCliPath.isBlank()) { + return new RuntimeLaunch(envCliPath); + } + + Path wrapper = NativeRuntimeLoader.resolveRuntimeWrapper(); + return new RuntimeLaunch(wrapper.toString()); + } + static URI parseCliUrl(String url) { // If it's just a port number, treat as localhost try { @@ -337,4 +364,7 @@ static URI parseCliUrl(String url) { */ record ProcessInfo(Process process, Integer port) { } + + record RuntimeLaunch(String executable) { + } } 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 75% rename from java/src/main/java/com/github/copilot/CopilotClient.java rename to java/sdk/src/main/java/com/github/copilot/CopilotClient.java index 44878b87ec..56c3487635 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,12 @@ package com.github.copilot; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.net.URI; +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; @@ -21,8 +25,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; @@ -107,11 +118,14 @@ public final class CopilotClient implements AutoCloseable { private final CliServerManager serverManager; private final LifecycleEventManager lifecycleManager = new LifecycleEventManager(); private final Map sessions = new ConcurrentHashMap<>(); + private final GitHubTokenProviderRegistry gitHubTokenProviders = new GitHubTokenProviderRegistry(); private volatile CompletableFuture connectionFuture; 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 +145,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()) { @@ -184,7 +221,10 @@ public CopilotClient(CopilotClientOptions options) { // Parse CliUrl if provided if (this.options.getCliUrl() != null && !this.options.getCliUrl().isEmpty()) { URI uri = CliServerManager.parseCliUrl(this.options.getCliUrl()); - this.optionsHost = uri.getHost(); + String host = uri.getHost(); + this.optionsHost = host != null && host.startsWith("[") && host.endsWith("]") + ? host.substring(1, host.length() - 1) + : host; this.optionsPort = uri.getPort(); } else { this.optionsHost = null; @@ -199,6 +239,267 @@ 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 { + Path explicitEntrypoint = NativeRuntimeLoader.resolveConfiguredEntrypoint(); + host.start(explicitEntrypoint == null ? null : explicitEntrypoint.toString(), options); + } catch (RuntimeException | Error e) { + host.close(); + throw e; + } + return new InProcessTransport(host.getReceiveStream(), host.getSendStream(), host); + } + + 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 +528,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 +550,14 @@ 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); + RpcHandlerDispatcher dispatcher = new RpcHandlerDispatcher(sessions, lifecycleManager::dispatch, executor, + gitHubTokenProviders); + dispatcher.registerHandlers(connectedRpc); // Register the LLM inference request handler when configured. com.github.copilot.CopilotRequestHandler requestHandler = this.options.getRequestHandler(); @@ -257,7 +565,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 +573,7 @@ private Connection startCoreBody() { .getOnGitHubTelemetry(); if (onGitHubTelemetry != null) { GitHubTelemetryAdapter telemetryAdapter = new GitHubTelemetryAdapter(onGitHubTelemetry); - telemetryAdapter.registerHandlers(rpc); + telemetryAdapter.registerHandlers(connectedRpc); } // Verify protocol version @@ -273,6 +581,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 +606,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( @@ -318,6 +645,13 @@ private void verifyProtocolVersion(Connection connection) throws Exception { if (this.options.getOnGitHubTelemetry() != null) { connectParams.put("enableGitHubTelemetryForwarding", true); } + // Declare the integrating application's identity so the runtime attributes the + // telemetry it emits on this connection to a consistent surface instead of + // its own build. Omitted when the app didn't supply it (or supplied no fields). + var clientInfo = this.options.getClientInfo(); + if (clientInfo != null && !clientInfo.isEmpty()) { + connectParams.put("clientInfo", clientInfo); + } var connectResponse = connection.rpc.invoke("connect", connectParams, ConnectResult.class).get(30, TimeUnit.SECONDS); serverVersion = connectResponse.protocolVersion() != null @@ -398,6 +732,7 @@ public CompletableFuture stop() { closeFutures.add(future); } sessions.clear(); + gitHubTokenProviders.clear(); return CompletableFuture.allOf(closeFutures.toArray(new CompletableFuture[0])) .thenCompose(v -> cleanupConnection(true)); @@ -411,6 +746,7 @@ public CompletableFuture stop() { public CompletableFuture forceStop() { disposed = true; sessions.clear(); + gitHubTokenProviders.clear(); // Dispatch the blocking shutdownOwnedExecutor() on a dedicated thread: // cleanupConnection() is chained off async work running on the owned // executor, so a plain whenComplete(...) here could land the awaitTermination @@ -438,7 +774,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 +801,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); @@ -541,6 +880,10 @@ public CompletableFuture createSession(SessionConfig config) { + "For example, to allow all permissions, use: " + "new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)")); } + if (config.getGitHubToken() != null && config.getGitHubTokenProvider() != null) { + return CompletableFuture.failedFuture( + new IllegalArgumentException("gitHubToken and gitHubTokenProvider are mutually exclusive")); + } return ensureConnected().thenCompose(connection -> { long totalNanos = System.nanoTime(); // For cloud sessions, let the CLI/server assign the session id @@ -646,6 +989,16 @@ public CompletableFuture createSession(SessionConfig config) { } } + GitHubTokenProviderRegistry.Registration tokenRegistration = config.getGitHubTokenProvider() == null + ? null + : gitHubTokenProviders.register(config.getGitHubTokenProvider()); + if (tokenRegistration != null) { + request.setGitHubTokenProviderRegistrationId(tokenRegistration.id()); + if (preRegisteredSessionHolder[0] != null) { + preRegisteredSessionHolder[0].setGitHubTokenProviderRegistration(tokenRegistration); + } + } + long rpcNanos = System.nanoTime(); return connection.rpc.invoke("session.create", request, CreateSessionResponse.class) .thenCompose(response -> { @@ -664,6 +1017,9 @@ public CompletableFuture createSession(SessionConfig config) { CopilotSession session = preRegisteredSessionHolder[0] != null ? preRegisteredSessionHolder[0] : initializeSession.apply(returnedId); + if (tokenRegistration != null) { + session.setGitHubTokenProviderRegistration(tokenRegistration); + } registeredIdHolder[0] = returnedId; CompletableFuture interest = config.getOnMcpAuthRequest() != null ? session.getRpc().eventLog.registerInterest( @@ -678,8 +1034,13 @@ public CompletableFuture createSession(SessionConfig config) { return updateSessionOptionsForMode(session, config.getSkipCustomInstructions().orElse(null), config.getCustomAgentsLocalOnly().orElse(null), config.getCoauthorEnabled().orElse(null), - config.getManageScheduleEnabled().orElse(null)); + config.getManageScheduleEnabled().orElse(null), config.getIncludedBuiltinSkills()); }).thenApply(v -> { + if (tokenRegistration != null) { + tokenRegistration.claim(session.getSessionId()); + } else { + gitHubTokenProviders.retire(session.getSessionId()); + } LoggingHelpers.logTiming(LOG, Level.FINE, "CopilotClient.createSession complete. Elapsed={Elapsed}, SessionId=" + session.getSessionId(), @@ -690,6 +1051,9 @@ public CompletableFuture createSession(SessionConfig config) { if (registeredIdHolder[0] != null) { sessions.remove(registeredIdHolder[0]); } + if (tokenRegistration != null) { + tokenRegistration.close(); + } LoggingHelpers.logTiming(LOG, Level.WARNING, ex, "CopilotClient.createSession failed. Elapsed={Elapsed}, SessionId=" + (registeredIdHolder[0] != null ? registeredIdHolder[0] : ""), @@ -737,10 +1101,15 @@ public CompletableFuture resumeSession(String sessionId, ResumeS + "For example, to allow all permissions, use: " + "new ResumeSessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)")); } + if (config.getGitHubToken() != null && config.getGitHubTokenProvider() != null) { + return CompletableFuture.failedFuture( + new IllegalArgumentException("gitHubToken and gitHubTokenProvider are mutually exclusive")); + } return ensureConnected().thenCompose(connection -> { long totalNanos = System.nanoTime(); // Register the session before the RPC call to avoid missing early events. long setupNanos = System.nanoTime(); + CopilotSession replacedSession = sessions.get(sessionId); var session = new CopilotSession(sessionId, connection.rpc); session.setExecutor(executor); SessionRequestBuilder.configureSession(session, config); @@ -805,6 +1174,14 @@ public CompletableFuture resumeSession(String sessionId, ResumeS } } + GitHubTokenProviderRegistry.Registration tokenRegistration = config.getGitHubTokenProvider() == null + ? null + : gitHubTokenProviders.register(config.getGitHubTokenProvider()); + if (tokenRegistration != null) { + request.setGitHubTokenProviderRegistrationId(tokenRegistration.id()); + session.setGitHubTokenProviderRegistration(tokenRegistration); + } + long rpcNanos = System.nanoTime(); return connection.rpc.invoke("session.resume", request, ResumeSessionResponse.class) .thenCompose(response -> { @@ -834,15 +1211,20 @@ public CompletableFuture resumeSession(String sessionId, ResumeS session.setActiveSessionId(returnedId); sessions.put(returnedId, session); } - return updateSessionOptionsForMode(session, config.getSkipCustomInstructions().orElse(null), config.getCustomAgentsLocalOnly().orElse(null), config.getCoauthorEnabled().orElse(null), - config.getManageScheduleEnabled().orElse(null)).thenApply(v -> { + config.getManageScheduleEnabled().orElse(null), config.getIncludedBuiltinSkills()) + .thenApply(v -> { LoggingHelpers.logTiming(LOG, Level.FINE, "CopilotClient.resumeSession complete. Elapsed={Elapsed}, SessionId=" + sessionId, totalNanos); + if (tokenRegistration != null) { + tokenRegistration.claim(session.getSessionId()); + } else { + gitHubTokenProviders.retire(session.getSessionId()); + } return session; }); }).exceptionally(ex -> { @@ -852,6 +1234,12 @@ public CompletableFuture resumeSession(String sessionId, ResumeS if (!sessionId.equals(activeId)) { sessions.remove(activeId); } + if (replacedSession != null) { + sessions.putIfAbsent(sessionId, replacedSession); + } + if (tokenRegistration != null) { + tokenRegistration.close(); + } LoggingHelpers.logTiming(LOG, Level.WARNING, ex, "CopilotClient.resumeSession failed. Elapsed={Elapsed}, SessionId=" + sessionId, totalNanos); @@ -860,14 +1248,22 @@ public CompletableFuture resumeSession(String sessionId, ResumeS }); } + CompletableFuture updateSessionOptionsForMode(CopilotSession session, Boolean skipCustomInstructions, + Boolean customAgentsLocalOnly, Boolean coauthorEnabled, Boolean manageScheduleEnabled) { + return updateSessionOptionsForMode(session, skipCustomInstructions, customAgentsLocalOnly, coauthorEnabled, + manageScheduleEnabled, null); + } + /** * Applies the post-create / post-resume {@code session.options.update} patch. *

* In {@link CopilotClientMode#EMPTY EMPTY} mode this defaults the four * overridable feature flags to safe values (caller values from the config win); - * {@code installedPlugins=[]} is unconditional under empty mode so apps that - * need plugins must switch modes. In {@link CopilotClientMode#COPILOT_CLI - * COPILOT_CLI} mode only explicitly-set fields are forwarded. + * {@code installedPlugins=[]} is unconditional under empty mode. + * {@code includedBuiltinSkills} defaults to an empty list, but callers can + * explicitly allow selected runtime-bundled skills. In + * {@link CopilotClientMode#COPILOT_CLI COPILOT_CLI} mode only explicitly-set + * fields are forwarded. * * @param session * the session to patch @@ -879,16 +1275,21 @@ public CompletableFuture resumeSession(String sessionId, ResumeS * caller-supplied value, or {@code null} if not set * @param manageScheduleEnabled * caller-supplied value, or {@code null} if not set + * @param includedBuiltinSkills + * caller-supplied built-in skill allowlist, or {@code null} if not + * set * @return a future that completes when the patch has been applied */ CompletableFuture updateSessionOptionsForMode(CopilotSession session, Boolean skipCustomInstructions, - Boolean customAgentsLocalOnly, Boolean coauthorEnabled, Boolean manageScheduleEnabled) { + Boolean customAgentsLocalOnly, Boolean coauthorEnabled, Boolean manageScheduleEnabled, + List includedBuiltinSkills) { Boolean patchSkip = null; Boolean patchAgents = null; Boolean patchCoauthor = null; Boolean patchSchedule = null; List patchPlugins = null; + List patchSkills = null; boolean hasAnyPatch = false; if (options.getMode() == CopilotClientMode.EMPTY) { @@ -897,6 +1298,7 @@ CompletableFuture updateSessionOptionsForMode(CopilotSession session, Bool patchCoauthor = coauthorEnabled != null ? coauthorEnabled : false; patchSchedule = manageScheduleEnabled != null ? manageScheduleEnabled : false; patchPlugins = List.of(); + patchSkills = includedBuiltinSkills != null ? includedBuiltinSkills : List.of(); hasAnyPatch = true; } else { if (skipCustomInstructions != null) { @@ -915,6 +1317,10 @@ CompletableFuture updateSessionOptionsForMode(CopilotSession session, Bool patchSchedule = manageScheduleEnabled; hasAnyPatch = true; } + if (includedBuiltinSkills != null) { + patchSkills = includedBuiltinSkills; + hasAnyPatch = true; + } } if (!hasAnyPatch) { @@ -945,10 +1351,12 @@ CompletableFuture updateSessionOptionsForMode(CopilotSession session, Bool null, // shellInitProfile null, // shellProcessFlags null, // sandboxConfig + null, // sandboxConfigSource null, // logInteractiveShells null, // envValueMode null, // allowAllMcpServerInstructions null, // skillDirectories + patchSkills, // includedBuiltinSkills null, // disabledSkills null, // enableOnDemandInstructionDiscovery null, // maxInlineBinaryBytes @@ -1171,7 +1579,10 @@ public CompletableFuture deleteSession(String sessionId) { if (!response.success()) { throw new RuntimeException("Failed to delete session " + sessionId + ": " + response.error()); } - sessions.remove(sessionId); + CopilotSession session = sessions.remove(sessionId); + if (session != null) { + session.releaseGitHubTokenProviderRegistration(); + } })); } @@ -1353,6 +1764,9 @@ public void close() { LOG.log(Level.FINE, "Error during close", e); } finally { shutdownOwnedExecutor(); + if (closeHook != null) { + closeHook.run(); + } } } @@ -1392,7 +1806,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 b977b69a12..f3a35967d3 100644 --- a/java/src/main/java/com/github/copilot/CopilotSession.java +++ b/java/sdk/src/main/java/com/github/copilot/CopilotSession.java @@ -57,6 +57,7 @@ import com.github.copilot.generated.SessionErrorEvent; import com.github.copilot.generated.SessionEvent; import com.github.copilot.generated.SessionIdleEvent; +import com.github.copilot.generated.SessionMode; import com.github.copilot.generated.rpc.OpenCanvasInstance; import com.github.copilot.rpc.AgentInfo; import com.github.copilot.rpc.AutoModeSwitchHandler; @@ -198,6 +199,7 @@ public final class CopilotSession implements AutoCloseable { private volatile Map>> transformCallbacks; private final ScheduledExecutorService timeoutScheduler; private volatile Executor executor; + private volatile GitHubTokenProviderRegistry.Registration gitHubTokenProviderRegistration; /** Tracks whether this session instance has been terminated via close(). */ private volatile boolean isTerminated = false; @@ -252,6 +254,18 @@ void setExecutor(Executor executor) { this.executor = executor; } + void setGitHubTokenProviderRegistration(GitHubTokenProviderRegistry.Registration registration) { + this.gitHubTokenProviderRegistration = registration; + } + + synchronized void releaseGitHubTokenProviderRegistration() { + GitHubTokenProviderRegistry.Registration registration = gitHubTokenProviderRegistration; + gitHubTokenProviderRegistration = null; + if (registration != null) { + registration.close(); + } + } + /** * Gets the unique identifier for this session. * @@ -549,7 +563,8 @@ public CompletableFuture sendAndWait(MessageOptions optio + sessionId, totalNanos); } - } else if (evt instanceof SessionIdleEvent) { + } else if (evt instanceof SessionIdleEvent idleEvent + && (idleEvent.getData() == null || idleEvent.getData().mode() != SessionMode.AUTOPILOT)) { LoggingHelpers.logTiming(LOG, Level.FINE, "CopilotSession.sendAndWait idle received. Elapsed={Elapsed}, SessionId=" + sessionId, totalNanos); @@ -1001,7 +1016,7 @@ private void executeToolAndRespondAsync(String requestId, String toolName, Strin * future completes exceptionally. */ private SessionUiHandlePendingElicitationParams buildElicitationCancelParams(String requestId) { - var cancelResult = new UIElicitationResponse(UIElicitationResponseAction.CANCEL, null); + var cancelResult = new UIElicitationResponse(UIElicitationResponseAction.CANCEL, null, null); return new SessionUiHandlePendingElicitationParams(sessionId, requestId, cancelResult); } @@ -1025,8 +1040,8 @@ 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); } @@ -1036,8 +1051,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 e) { LOG.log(Level.WARNING, "Error sending permission denied for requestId=" + requestId, e); } @@ -1049,7 +1064,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); } @@ -1212,7 +1228,7 @@ private void handleElicitationRequestAsync(ElicitationContext context, String re ? result.getAction().getValue() : ElicitationResultAction.CANCEL.getValue(); var parsedAction = UIElicitationResponseAction.fromValue(actionStr); - var elicitationResult = new UIElicitationResponse(parsedAction, result.getContent()); + var elicitationResult = new UIElicitationResponse(parsedAction, result.getContent(), null); getRpc().ui.handlePendingElicitation( new SessionUiHandlePendingElicitationParams(sessionId, requestId, elicitationResult)); } catch (Exception e) { @@ -1267,10 +1283,10 @@ private final class SessionUiApiImpl implements SessionUiApi { @Override public CompletableFuture elicitation(ElicitationParams params) { assertElicitation(); - return getRpc().ui.elicitation(new SessionUiElicitationParams(sessionId, params.getMessage(), + return getRpc().ui.elicitation(new SessionUiElicitationParams(sessionId, null, params.getMessage(), new UIElicitationSchema(params.getRequestedSchema().getType(), - params.getRequestedSchema().getProperties(), params.getRequestedSchema().getRequired()))) - .thenApply(resp -> { + params.getRequestedSchema().getProperties(), params.getRequestedSchema().getRequired()), + null, null)).thenApply(resp -> { var result = new ElicitationResult(); if (resp.action() != null) { for (ElicitationResultAction a : ElicitationResultAction.values()) { @@ -1292,8 +1308,9 @@ public CompletableFuture elicitation(ElicitationParams params public CompletableFuture confirm(String message) { assertElicitation(); var field = Map.of("type", "boolean", "default", (Object) true); - return getRpc().ui.elicitation(new SessionUiElicitationParams(sessionId, message, - new UIElicitationSchema("object", Map.of("confirmed", (Object) field), List.of("confirmed")))) + return getRpc().ui + .elicitation(new SessionUiElicitationParams(sessionId, null, message, new UIElicitationSchema( + "object", Map.of("confirmed", (Object) field), List.of("confirmed")), null, null)) .thenApply(resp -> { if (resp.action() == UIElicitationResponseAction.ACCEPT && resp.content() != null) { Object val = resp.content().get("confirmed"); @@ -1315,8 +1332,9 @@ public CompletableFuture confirm(String message) { public CompletableFuture select(String message, String[] options) { assertElicitation(); var field = Map.of("type", (Object) "string", "enum", (Object) options); - return getRpc().ui.elicitation(new SessionUiElicitationParams(sessionId, message, - new UIElicitationSchema("object", Map.of("selection", (Object) field), List.of("selection")))) + return getRpc().ui + .elicitation(new SessionUiElicitationParams(sessionId, null, message, new UIElicitationSchema( + "object", Map.of("selection", (Object) field), List.of("selection")), null, null)) .thenApply(resp -> { if (resp.action() == UIElicitationResponseAction.ACCEPT && resp.content() != null) { Object val = resp.content().get("selection"); @@ -1345,9 +1363,8 @@ public CompletableFuture input(String message, InputOptions options) { if (options.getDefaultValue() != null) field.put("default", options.getDefaultValue()); } - return getRpc().ui - .elicitation(new SessionUiElicitationParams(sessionId, message, - new UIElicitationSchema("object", Map.of("value", (Object) field), List.of("value")))) + return getRpc().ui.elicitation(new SessionUiElicitationParams(sessionId, null, message, + new UIElicitationSchema("object", Map.of("value", (Object) field), List.of("value")), null, null)) .thenApply(resp -> { if (resp.action() == UIElicitationResponseAction.ACCEPT && resp.content() != null) { Object val = resp.content().get("value"); @@ -1993,10 +2010,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, null)) - .thenApply(r -> null); + return getRpc().model.switchTo(new SessionModelSwitchToParams(sessionId, model, reasoningEffort, null, null, + null, null, null, null, null, null, null, null, null, null)).thenApply(r -> null); } /** @@ -2076,8 +2091,10 @@ public CompletableFuture setModel(String model, String reasoningEffort, St var generatedReasoningSummary = reasoningSummary == null ? null : com.github.copilot.generated.rpc.ReasoningSummary.fromValue(reasoningSummary); - return getRpc().model.switchTo(new SessionModelSwitchToParams(sessionId, model, reasoningEffort, - generatedReasoningSummary, null, generatedCapabilities, null, null)).thenApply(r -> null); + return getRpc().model + .switchTo(new SessionModelSwitchToParams(sessionId, model, reasoningEffort, generatedReasoningSummary, + null, generatedCapabilities, null, null, null, null, null, null, null, null, null)) + .thenApply(r -> null); } /** @@ -2299,6 +2316,7 @@ public void close() { } timeoutScheduler.shutdownNow(); + releaseGitHubTokenProviderRegistration(); try { rpc.invoke("session.destroy", Map.of("sessionId", sessionId), Void.class).get(5, TimeUnit.SECONDS); 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/sdk/src/main/java/com/github/copilot/GitHubTokenProviderRegistry.java b/java/sdk/src/main/java/com/github/copilot/GitHubTokenProviderRegistry.java new file mode 100644 index 0000000000..59b4f61469 --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/GitHubTokenProviderRegistry.java @@ -0,0 +1,79 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import com.github.copilot.rpc.GitHubTokenProvider; + +final class GitHubTokenProviderRegistry { + + private final Map providers = new HashMap<>(); + private final Map sessionOwners = new HashMap<>(); + + synchronized Registration register(GitHubTokenProvider provider) { + String id = UUID.randomUUID().toString(); + providers.put(id, provider); + return new Registration(this, id); + } + + synchronized GitHubTokenProvider get(String registrationId) { + return providers.get(registrationId); + } + + private synchronized void claim(String registrationId, String sessionId) { + String previous = sessionOwners.put(sessionId, registrationId); + if (previous != null && !previous.equals(registrationId)) { + providers.remove(previous); + } + } + + private synchronized void unregister(String registrationId) { + providers.remove(registrationId); + sessionOwners.values().removeIf(registrationId::equals); + } + + synchronized void retire(String sessionId) { + String registrationId = sessionOwners.remove(sessionId); + if (registrationId != null) { + providers.remove(registrationId); + } + } + + synchronized void clear() { + providers.clear(); + sessionOwners.clear(); + } + + static final class Registration implements AutoCloseable { + + private final GitHubTokenProviderRegistry registry; + private final String id; + private boolean closed; + + private Registration(GitHubTokenProviderRegistry registry, String id) { + this.registry = registry; + this.id = id; + } + + String id() { + return id; + } + + void claim(String sessionId) { + registry.claim(id, sessionId); + } + + @Override + public synchronized void close() { + if (!closed) { + closed = true; + registry.unregister(id); + } + } + } +} 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 82% rename from java/src/main/java/com/github/copilot/JsonRpcClient.java rename to java/sdk/src/main/java/com/github/copilot/JsonRpcClient.java index 5303c4f500..7eda069d23 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; @@ -27,6 +28,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.github.copilot.rpc.JsonRpcError; import com.github.copilot.rpc.JsonRpcRequest; @@ -46,6 +48,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 +56,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 +110,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). @@ -191,7 +221,39 @@ private synchronized void sendMessage(Object message) throws IOException { outputStream.write(content); outputStream.flush(); - LOG.fine("Sent: " + json); + if (LOG.isLoggable(Level.FINE)) { + LOG.fine("Sent: " + redactCredentialsForLogging(json)); + } + } + + static String redactCredentialsForLogging(String json) { + try { + JsonNode root = MAPPER.readTree(json); + redactCredentials(root); + return MAPPER.writeValueAsString(root); + } catch (JsonProcessingException error) { + return ""; + } + } + + private static void redactCredentials(JsonNode node) { + if (node.isObject()) { + var object = (ObjectNode) node; + object.properties().forEach(entry -> { + if (isCredentialField(entry.getKey())) { + object.put(entry.getKey(), ""); + } else { + redactCredentials(entry.getValue()); + } + }); + } else if (node.isArray()) { + node.forEach(JsonRpcClient::redactCredentials); + } + } + + private static boolean isCredentialField(String name) { + return name.equals("accessToken") || name.equals("gitHubToken") || name.equals("bearerToken") + || name.equals("apiKey"); } private void startReader() { @@ -344,6 +406,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 87% rename from java/src/main/java/com/github/copilot/RpcHandlerDispatcher.java rename to java/sdk/src/main/java/com/github/copilot/RpcHandlerDispatcher.java index d2dff958dc..0dd8a5dc98 100644 --- a/java/src/main/java/com/github/copilot/RpcHandlerDispatcher.java +++ b/java/sdk/src/main/java/com/github/copilot/RpcHandlerDispatcher.java @@ -20,7 +20,12 @@ import com.github.copilot.rpc.AutoModeSwitchRequest; import com.github.copilot.rpc.ExitPlanModeRequest; import com.github.copilot.rpc.BearerTokenProvider; +import com.github.copilot.rpc.GitHubTokenProviderArgs; +import com.github.copilot.rpc.GitHubTokenProviderResult; import com.github.copilot.rpc.ProviderTokenArgs; +import com.github.copilot.generated.rpc.GitHubTokenAcquireRequest; +import com.github.copilot.generated.rpc.GitHubTokenAcquireResultCancelled; +import com.github.copilot.generated.rpc.GitHubTokenAcquireResultToken; import com.github.copilot.rpc.PermissionRequestResult; import com.github.copilot.rpc.PermissionRequestResultKind; import com.github.copilot.rpc.SessionLifecycleEvent; @@ -51,6 +56,7 @@ final class RpcHandlerDispatcher { private final Map sessions; private final LifecycleEventDispatcher lifecycleDispatcher; private final Executor executor; + private final GitHubTokenProviderRegistry gitHubTokenProviders; /** * Creates a dispatcher with session registry and lifecycle dispatcher. @@ -63,10 +69,11 @@ final class RpcHandlerDispatcher { * the executor for async dispatch, or {@code null} for default */ RpcHandlerDispatcher(Map sessions, LifecycleEventDispatcher lifecycleDispatcher, - Executor executor) { + Executor executor, GitHubTokenProviderRegistry gitHubTokenProviders) { this.sessions = sessions; this.lifecycleDispatcher = lifecycleDispatcher; this.executor = executor; + this.gitHubTokenProviders = gitHubTokenProviders; } /** @@ -92,6 +99,71 @@ void registerHandlers(JsonRpcClient rpc) { (requestId, params) -> handleSystemMessageTransform(rpc, requestId, params)); rpc.registerMethodHandler("providerToken.getToken", (requestId, params) -> handleProviderTokenGetToken(rpc, requestId, params)); + rpc.registerMethodHandler("gitHubToken.getToken", + (requestId, params) -> handleGitHubTokenGetToken(rpc, requestId, params)); + } + + private void handleGitHubTokenGetToken(JsonRpcClient rpc, String requestId, JsonNode params) { + runAsync(() -> { + final long requestIdLong = parseRequestId(requestId, "gitHubToken.getToken"); + if (requestIdLong == -1) { + return; + } + try { + GitHubTokenAcquireRequest request = MAPPER.treeToValue(params, GitHubTokenAcquireRequest.class); + var provider = gitHubTokenProviders.get(request.registrationId()); + if (provider == null) { + rpc.sendErrorResponse(requestIdLong, -32603, "Unknown GitHub token provider registration"); + return; + } + + var resultFuture = provider + .getToken(new GitHubTokenProviderArgs(request.host(), request.sessionId(), request.reason())); + if (resultFuture == null) { + rpc.sendErrorResponse(requestIdLong, -32603, "GitHub token provider returned a null future"); + return; + } + resultFuture.thenAccept(result -> sendGitHubTokenResult(rpc, requestIdLong, result)) + .exceptionally(error -> { + try { + Throwable cause = error instanceof java.util.concurrent.CompletionException + && error.getCause() != null ? error.getCause() : error; + rpc.sendErrorResponse(requestIdLong, -32603, + "GitHub token provider failed: " + cause.getMessage()); + } catch (IOException sendError) { + LOG.log(Level.SEVERE, "Error sending GitHub token provider error", sendError); + } + return null; + }); + } catch (Exception error) { + try { + rpc.sendErrorResponse(requestIdLong, -32603, + "GitHub token provider handler failed: " + error.getMessage()); + } catch (IOException sendError) { + LOG.log(Level.SEVERE, "Error sending GitHub token provider handler error", sendError); + } + } + }); + } + + private void sendGitHubTokenResult(JsonRpcClient rpc, long requestId, GitHubTokenProviderResult result) { + try { + if (result == null) { + rpc.sendErrorResponse(requestId, -32603, "GitHub token provider returned a null result"); + return; + } + if (result.isCancelled()) { + rpc.sendResponse(requestId, new GitHubTokenAcquireResultCancelled()); + return; + } + var wireResult = new GitHubTokenAcquireResultToken(); + wireResult.setAccessToken(result.getAccessToken()); + wireResult.setExpiresIn(result.getExpiresIn()); + wireResult.setTokenType(result.getTokenType()); + rpc.sendResponse(requestId, wireResult); + } catch (IOException error) { + LOG.log(Level.SEVERE, "Error sending GitHub token provider result", error); + } } private void handleSessionEvent(JsonNode params) { 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 95% rename from java/src/main/java/com/github/copilot/SessionRequestBuilder.java rename to java/sdk/src/main/java/com/github/copilot/SessionRequestBuilder.java index 62c7120252..d54889cc4b 100644 --- a/java/src/main/java/com/github/copilot/SessionRequestBuilder.java +++ b/java/sdk/src/main/java/com/github/copilot/SessionRequestBuilder.java @@ -119,6 +119,7 @@ static CreateSessionRequest buildCreateRequest(SessionConfig config, String sess request.setReasoningEffort(config.getReasoningEffort()); request.setReasoningSummary(config.getReasoningSummary()); request.setContextTier(config.getContextTier()); + request.setAskUserVariant(config.getAskUserVariant()); request.setTools(config.getTools()); request.setSystemMessage(config.getSystemMessage()); request.setAvailableTools(config.getAvailableTools()); @@ -130,6 +131,7 @@ 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); @@ -160,6 +162,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); @@ -198,8 +201,10 @@ static CreateSessionRequest buildCreateRequest(SessionConfig config, String sess request.setGitHubToken(config.getGitHubToken()); request.setRemoteSession(config.getRemoteSession()); request.setCloud(config.getCloud()); + request.setFeatureFlags(config.getFeatureFlags()); request.setExpAssignments(config.getExpAssignments()); config.getEnableManagedSettings().ifPresent(request::setEnableManagedSettings); + request.setManagedSettings(config.getManagedSettings()); return request; } @@ -252,6 +257,7 @@ static ResumeSessionRequest buildResumeRequest(String sessionId, ResumeSessionCo request.setReasoningEffort(config.getReasoningEffort()); request.setReasoningSummary(config.getReasoningSummary()); request.setContextTier(config.getContextTier()); + request.setAskUserVariant(config.getAskUserVariant()); request.setTools(config.getTools()); request.setSystemMessage(config.getSystemMessage()); request.setAvailableTools(config.getAvailableTools()); @@ -263,6 +269,7 @@ 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); @@ -309,6 +316,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()); @@ -333,8 +341,10 @@ static ResumeSessionRequest buildResumeRequest(String sessionId, ResumeSessionCo } request.setGitHubToken(config.getGitHubToken()); request.setRemoteSession(config.getRemoteSession()); + request.setFeatureFlags(config.getFeatureFlags()); request.setExpAssignments(config.getExpAssignments()); config.getEnableManagedSettings().ifPresent(request::setEnableManagedSettings); + request.setManagedSettings(config.getManagedSettings()); return request; } @@ -372,7 +382,8 @@ static void configureSession(CopilotSession session, SessionConfig config) { if (config.getOnPermissionRequest() != null) { session.registerPermissionHandler(config.getOnPermissionRequest()); } - session.setManagedSettingsEnabled(config.getEnableManagedSettings().orElse(false)); + session.setManagedSettingsEnabled( + config.getEnableManagedSettings().orElse(false) || config.getManagedSettings() != null); if (config.getOnMcpAuthRequest() != null) { session.registerMcpAuthHandler(config.getOnMcpAuthRequest()); } @@ -423,7 +434,8 @@ static void configureSession(CopilotSession session, ResumeSessionConfig config) if (config.getOnPermissionRequest() != null) { session.registerPermissionHandler(config.getOnPermissionRequest()); } - session.setManagedSettingsEnabled(config.getEnableManagedSettings().orElse(false)); + 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 0000000000..4198f08f02 --- /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 0000000000..cb5bba1af1 --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/ffi/FfiRuntimeHost.java @@ -0,0 +1,348 @@ +/*--------------------------------------------------------------------------------------------- + * 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 + * optional explicit legacy CLI entrypoint 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(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 + "')."); + } + + // 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 != null) { + 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 0000000000..ba3c3c40a5 --- /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 0000000000..3aa8ca9e4d --- /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 0000000000..bd4b185a07 --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/ffi/NativeRuntimeLoader.java @@ -0,0 +1,643 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.ffi; + +import java.io.FileNotFoundException; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.channels.FileChannel; +import java.nio.file.AccessDeniedException; +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 RUNTIME_WRAPPER_FILENAME = "copilot-runtime"; + static final String RUNTIME_WRAPPER_FILENAME_WINDOWS = "copilot-runtime.exe"; + static final String PLATFORM_PROPERTIES_FILENAME = "platform.properties"; + static final String RUNTIME_ASSETS_FILENAME = "runtime-assets.list"; + /** 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 | AccessDeniedException ex) { + // Windows can report AccessDeniedException instead of + // FileAlreadyExistsException when another publisher wins the race. + 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 legacy copilot CLI entrypoint from the same location as the + * bundled {@code runtime.node}. Callers may pass this entrypoint through + * {@code copilot_runtime_host_start} when legacy extension hosting is + * requested. + * + *

+ * 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()); + } + + /** + * Resolves an explicitly configured legacy CLI entrypoint, if it has a + * compatible adjacent runtime library. + * + * @return the absolute CLI path, or {@code null} when no compatible override is + * configured + * @throws IOException + * if the configured files cannot be inspected + */ + public static Path resolveConfiguredEntrypoint() throws IOException { + String configuredCli = System.getenv(COPILOT_CLI_PATH_ENV); + if (configuredCli == null || configuredCli.isBlank()) { + return null; + } + Path configuredPath = Path.of(configuredCli).toAbsolutePath().normalize(); + return resolveFromCliPath(configuredCli) != null && Files.isRegularFile(configuredPath) + && Files.size(configuredPath) > 0 ? configuredPath : null; + } + + /** + * Resolves the out-of-process runtime wrapper from the platform classifier JAR + * and extracts it beside {@code runtime.node}. + * + * @return absolute path to the runtime wrapper executable + * @throws IOException + * if the classifier artifacts cannot be extracted + */ + public static Path resolveRuntimeWrapper() throws IOException { + ClassLoader loader = NativeRuntimeLoader.class.getClassLoader(); + String classifier = PlatformDetector.detectClassifier(); + String version = readVersion(loader); + return resolveRuntimeWrapper(defaultCacheBase(), loader, classifier, version); + } + + static Path resolveRuntimeWrapper(Path cacheBase, ClassLoader loader, String classifier, String version) + throws IOException { + Path runtimePath = extractRuntimeToCache(cacheBase, loader, classifier, version, DEFAULT_PUBLISHER, false); + Path cacheDir = runtimePath.getParent(); + String wrapperName = classifier.startsWith("win32-") + ? RUNTIME_WRAPPER_FILENAME_WINDOWS + : RUNTIME_WRAPPER_FILENAME; + Path cachedWrapper = cacheDir.resolve(wrapperName); + if (isValidCachedCli(cachedWrapper)) { + return cachedWrapper; + } + + String resourcePath = "native/" + classifier + "/" + wrapperName; + URL resource = loader.getResource(resourcePath); + if (resource == null) { + throw new FileNotFoundException("Runtime wrapper not found on classpath: " + resourcePath + + " — add the matching classifier JAR to the classpath"); + } + + Path temp = Files.createTempFile(cacheDir, "runtime-wrapper-tmp-", ""); + try { + copyResourceToTemp(resource, resourcePath, temp); + makeExecutable(temp); + DEFAULT_PUBLISHER.publish(temp, cachedWrapper); + } finally { + tryDelete(temp); + } + if (!isValidCachedCli(cachedWrapper)) { + throw new IOException("Published runtime wrapper is not a non-empty executable file: " + cachedWrapper); + } + return cachedWrapper; + } + + 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 { + return extractRuntimeToCache(cacheBase, loader, classifier, version, publisher, true); + } + + private static Path extractRuntimeToCache(Path cacheBase, ClassLoader loader, String classifier, String version, + AtomicPublisher publisher, boolean extractCli) 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)) { + extractRuntimeAssetsToCache(cacheDir, loader, classifier, publisher); + if (extractCli) { + 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); + } + + extractRuntimeAssetsToCache(cacheDir, loader, classifier, publisher); + if (extractCli) { + extractCliToCache(cacheDir, loader, classifier, publisher); + } + + return cached; + } + + private static void extractRuntimeAssetsToCache(Path cacheDir, ClassLoader loader, String classifier, + AtomicPublisher publisher) throws IOException { + String inventoryResourcePath = "native/" + classifier + "/" + RUNTIME_ASSETS_FILENAME; + URL inventoryResource = loader.getResource(inventoryResourcePath); + if (inventoryResource == null) { + return; + } + + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(inventoryResource.openStream(), StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + if (line.isBlank()) { + continue; + } + String[] fields = line.split("\\t", 2); + if (fields.length != 2) { + throw new IOException("Invalid runtime asset inventory entry: " + line); + } + boolean executable = (Integer.parseInt(fields[0], 8) & 0111) != 0; + Path relative = Path.of(fields[1]).normalize(); + if (relative.isAbsolute() || relative.startsWith("..")) { + throw new IOException("Unsafe runtime asset inventory path: " + fields[1]); + } + Path cached = cacheDir.resolve(relative).normalize(); + if (!cached.startsWith(cacheDir)) { + throw new IOException("Runtime asset escapes cache directory: " + fields[1]); + } + if (isValidCachedFile(cached) && (!executable || isWindows() || Files.isExecutable(cached))) { + continue; + } + + String resourcePath = "native/" + classifier + "/" + fields[1]; + URL resource = loader.getResource(resourcePath); + if (resource == null) { + throw new FileNotFoundException("Runtime asset not found on classpath: " + resourcePath); + } + Files.createDirectories(cached.getParent()); + Path temp = Files.createTempFile(cached.getParent(), "runtime-asset-tmp-", ""); + try { + copyResourceToTemp(resource, resourcePath, temp); + if (executable) { + makeExecutable(temp); + } + publisher.publish(temp, cached); + } finally { + tryDelete(temp); + } + } + } catch (NumberFormatException ex) { + throw new IOException("Invalid runtime asset mode in " + inventoryResourcePath, ex); + } + } + + /** + * 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 0000000000..597feea9e0 --- /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 0000000000..466cf794bc --- /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 0000000000..977182d5f4 --- /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 0000000000..b0824fa9a7 --- /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 0000000000..5bec1c327c --- /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/src/main/java/com/github/copilot/rpc/AgentStopHandler.java b/java/sdk/src/main/java/com/github/copilot/rpc/AgentStopHandler.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/AgentStopHandler.java rename to java/sdk/src/main/java/com/github/copilot/rpc/AgentStopHandler.java diff --git a/java/src/main/java/com/github/copilot/rpc/AgentStopHookInput.java b/java/sdk/src/main/java/com/github/copilot/rpc/AgentStopHookInput.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/AgentStopHookInput.java rename to java/sdk/src/main/java/com/github/copilot/rpc/AgentStopHookInput.java diff --git a/java/src/main/java/com/github/copilot/rpc/AgentStopHookOutput.java b/java/sdk/src/main/java/com/github/copilot/rpc/AgentStopHookOutput.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/AgentStopHookOutput.java rename to java/sdk/src/main/java/com/github/copilot/rpc/AgentStopHookOutput.java diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/AskUserVariant.java b/java/sdk/src/main/java/com/github/copilot/rpc/AskUserVariant.java new file mode 100644 index 0000000000..17c6a1333f --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/rpc/AskUserVariant.java @@ -0,0 +1,59 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.rpc; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Selects how the built-in {@code ask_user} tool collects user input. + */ +public enum AskUserVariant { + + /** Uses the legacy question-and-answer experience. */ + LEGACY("legacy"), + + /** Uses structured elicitation to collect user input. */ + ELICITATION("elicitation"); + + private final String value; + + AskUserVariant(String value) { + this.value = value; + } + + /** + * Returns the wire-format value. + * + * @return the value used in JSON serialization + */ + @JsonValue + public String getValue() { + return value; + } + + /** + * Creates an {@code AskUserVariant} from its wire-format value. + * + * @param value + * the wire-format value + * @return the matching variant, or {@code null} when {@code value} is + * {@code null} + * @throws IllegalArgumentException + * if the value is not {@code legacy} or {@code elicitation} + */ + @JsonCreator + public static AskUserVariant fromValue(String value) { + if (value == null) { + return null; + } + for (AskUserVariant variant : values()) { + if (variant.value.equals(value)) { + return variant; + } + } + throw new IllegalArgumentException("Unknown AskUserVariant value: " + value); + } +} 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/sdk/src/main/java/com/github/copilot/rpc/AutoTier.java b/java/sdk/src/main/java/com/github/copilot/rpc/AutoTier.java new file mode 100644 index 0000000000..f9117abfb2 --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/rpc/AutoTier.java @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.rpc; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Routing tier for the {@code auto} model with Auto mode V2. + * + * @see CapiSessionOptions#setAutoTier(AutoTier) + */ +public enum AutoTier { + + /** Prioritize efficiency. */ + EFFICIENCY("efficiency"), + + /** Balance efficiency and intelligence. */ + BALANCE("balance"), + + /** Prioritize intelligence. */ + INTELLIGENCE("intelligence"); + + private final String value; + + AutoTier(String value) { + this.value = value; + } + + /** + * Returns the JSON value for this routing tier. + * + * @return the string value used in JSON serialization + */ + @JsonValue + public String getValue() { + return value; + } + + /** + * Deserializes a JSON string into its routing tier. + * + * @param value + * the JSON string value + * @return the matching tier, or {@code null} if value is {@code null} + * @throws IllegalArgumentException + * if the value does not match a known routing tier + */ + @JsonCreator + public static AutoTier fromValue(String value) { + if (value == null) { + return null; + } + for (AutoTier tier : values()) { + if (tier.value.equals(value)) { + return tier; + } + } + throw new IllegalArgumentException("Unknown AutoTier value: " + value); + } +} 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 100% 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 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 71% 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 index d94d59f67b..e401762302 100644 --- a/java/src/main/java/com/github/copilot/rpc/CapiSessionOptions.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/CapiSessionOptions.java @@ -29,9 +29,40 @@ @JsonInclude(JsonInclude.Include.NON_NULL) public class CapiSessionOptions { + @JsonProperty("autoTier") + private AutoTier autoTier; + @JsonProperty("enableWebSocketResponses") private Boolean enableWebSocketResponses; + /** + * Gets the routing tier for the {@code auto} model (Auto mode V2). + * + * @return the explicit tier, or {@code null} to leave tier selection to the + * runtime + */ + public AutoTier getAutoTier() { + return autoTier; + } + + /** + * Sets the routing tier, meaningful only with model {@code auto} (Auto mode + * V2). Requires a runtime version that supports {@code capi.autoTier}. + *

+ * When omitted, the runtime chooses its default on create and preserves the + * persisted or current tier on resume. An explicit tier overrides the persisted + * tier on cold resume; the runtime rejects a conflicting tier when resuming a + * session already resident in memory. + * + * @param autoTier + * the routing tier, or {@code null} to omit it from the request + * @return this config for method chaining + */ + public CapiSessionOptions setAutoTier(AutoTier autoTier) { + this.autoTier = autoTier; + return this; + } + /** * Gets whether CAPI Responses API WebSocket transport is enabled. * diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/ClientInfo.java b/java/sdk/src/main/java/com/github/copilot/rpc/ClientInfo.java new file mode 100644 index 0000000000..ee9d97e1e9 --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/rpc/ClientInfo.java @@ -0,0 +1,151 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Identity of the integrating application, declared on the + * {@code server.connect} handshake. + *

+ * Declaring it lets the telemetry the runtime emits on the connection be + * attributed to a single, consistent surface (the application and its Copilot + * integration) instead of the runtime's own build. All fields are optional; an + * empty field is omitted from the handshake. + * + *

Example Usage

+ * + *
{@code
+ * var options = new CopilotClientOptions().setClientInfo(new ClientInfo().setApplicationName("acme-developer-portal")
+ * 		.setApplicationVersion("2.4.0").setIntegrationName("copilot-assistant").setIntegrationVersion("1.5.0"));
+ * }
+ * + * @see CopilotClientOptions#setClientInfo(ClientInfo) + * @since 1.6.0 + */ +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public class ClientInfo { + + private String applicationName; + + private String applicationVersion; + + private String integrationName; + + private String integrationVersion; + + /** + * Gets the name of the application using the SDK. + * + * @return the application name, or {@code null} + */ + @JsonProperty("editorName") + public String getApplicationName() { + return applicationName; + } + + /** + * Sets the name of the application using the SDK. + * + * @param applicationName + * the application name + * @return this client info for method chaining + */ + @JsonProperty("editorName") + public ClientInfo setApplicationName(String applicationName) { + this.applicationName = applicationName; + return this; + } + + /** + * Gets the version of the application using the SDK. + * + * @return the application version, or {@code null} + */ + @JsonProperty("editorVersion") + public String getApplicationVersion() { + return applicationVersion; + } + + /** + * Sets the version of the application using the SDK. + * + * @param applicationVersion + * the application version + * @return this client info for method chaining + */ + @JsonProperty("editorVersion") + public ClientInfo setApplicationVersion(String applicationVersion) { + this.applicationVersion = applicationVersion; + return this; + } + + /** + * Gets the optional name of a specific integration within the application, such + * as an extension or plugin. + * + * @return the integration name, or {@code null} + */ + @JsonProperty("extensionName") + public String getIntegrationName() { + return integrationName; + } + + /** + * Sets the optional name of a specific integration within the application, such + * as an extension or plugin. + * + * @param integrationName + * the integration name + * @return this client info for method chaining + */ + @JsonProperty("extensionName") + public ClientInfo setIntegrationName(String integrationName) { + this.integrationName = integrationName; + return this; + } + + /** + * Gets the optional version of the named integration. + * + * @return the integration version, or {@code null} + */ + @JsonProperty("extensionVersion") + public String getIntegrationVersion() { + return integrationVersion; + } + + /** + * Sets the optional version of the named integration. + * + * @param integrationVersion + * the integration version + * @return this client info for method chaining + */ + @JsonProperty("extensionVersion") + public ClientInfo setIntegrationVersion(String integrationVersion) { + this.integrationVersion = integrationVersion; + return this; + } + + /** + * Returns whether no field carries a non-empty value, in which case the SDK + * omits {@code clientInfo} from the handshake so the runtime keeps its default + * attribution. + * + * @return {@code true} when every field is {@code null} or empty + */ + @JsonIgnore + public boolean isEmpty() { + return isBlank(applicationName) && isBlank(applicationVersion) && isBlank(integrationName) + && isBlank(integrationVersion); + } + + private static boolean isBlank(String value) { + return value == null || value.isEmpty(); + } +} 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 92% 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 index 20b8d5b63d..892f124d58 100644 --- a/java/src/main/java/com/github/copilot/rpc/CommandWireDefinition.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/CommandWireDefinition.java @@ -31,7 +31,7 @@ public CommandWireDefinition() { /** Creates a definition with name and description. */ public CommandWireDefinition(String name, String description) { this.name = name; - this.description = description; + this.description = description == null ? "" : description; } /** Gets the command name. @return the name */ @@ -52,7 +52,7 @@ public String getDescription() { /** Sets the description. @param description the description @return this */ public CommandWireDefinition setDescription(String description) { - this.description = description; + this.description = description == null ? "" : description; return this; } } 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 84% 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 0d4494d738..c67947bf48 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,12 @@ 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 ClientInfo clientInfo; private String copilotHome; private String cwd; private Map environment; @@ -119,6 +124,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. *

@@ -154,19 +193,20 @@ public CopilotClientOptions setCliArgs(String[] cliArgs) { } /** - * Gets the path to the Copilot CLI executable. + * Gets the path to an explicitly configured Copilot executable. * - * @return the CLI path, or {@code null} to use "copilot" from PATH + * @return the executable path, or {@code null} to use the bundled runtime + * wrapper */ public String getCliPath() { return cliPath; } /** - * Sets the path to the Copilot CLI executable. + * Sets the path to the Copilot CLI or runtime wrapper executable. * * @param cliPath - * the path to the CLI executable + * the path to the executable * @return this options instance for method chaining */ public CopilotClientOptions setCliPath(String cliPath) { @@ -204,6 +244,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 +323,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; } @@ -606,6 +679,36 @@ public CopilotClientOptions setTelemetry(TelemetryConfig telemetry) { return this; } + /** + * Gets the integrating application's declared identity. + * + * @return the client info, or {@code null} + * @since 1.6.0 + */ + public ClientInfo getClientInfo() { + return clientInfo; + } + + /** + * Declares the integrating application's identity, forwarded to the runtime on + * the {@code server.connect} handshake. + *

+ * Declaring it lets the telemetry the runtime emits on this connection be + * attributed to a consistent surface (the application and its Copilot + * integration) instead of the runtime's own build. All fields on + * {@link ClientInfo} are optional; leave this unset to keep the runtime's + * default attribution. + * + * @param clientInfo + * the application identity to declare + * @return this options instance for method chaining + * @since 1.6.0 + */ + public CopilotClientOptions setClientInfo(ClientInfo clientInfo) { + this.clientInfo = Objects.requireNonNull(clientInfo, "clientInfo must not be null"); + return this; + } + /** * Gets the server-wide idle timeout for sessions in seconds. * @@ -751,9 +854,14 @@ 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.clientInfo = this.clientInfo; 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 91% 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 44cb8145c3..f6b5001e7a 100644 --- a/java/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java @@ -45,6 +45,9 @@ public final class CreateSessionRequest { @JsonProperty("contextTier") private String contextTier; + @JsonProperty("askUserVariant") + private AskUserVariant askUserVariant; + @JsonProperty("tools") private List tools; @@ -80,6 +83,9 @@ public final class CreateSessionRequest { @JsonProperty("enableCitations") private Boolean enableCitations; + @JsonProperty("enableFileChangeTracking") + private Boolean enableFileChangeTracking; + @JsonProperty("sessionLimits") private SessionLimitsConfig sessionLimits; @@ -152,6 +158,9 @@ public final class CreateSessionRequest { @JsonProperty("disabledSkills") private List disabledSkills; + @JsonProperty("disabledMcpServers") + private List disabledMcpServers; + @JsonProperty("configDir") private String configDirectory; @@ -218,6 +227,9 @@ public final class CreateSessionRequest { @JsonProperty("gitHubToken") private String gitHubToken; + @JsonProperty("gitHubTokenProviderRegistrationId") + private String gitHubTokenProviderRegistrationId; + @JsonProperty("remoteSession") private String remoteSession; @@ -227,10 +239,17 @@ public final class CreateSessionRequest { @JsonProperty("expAssignments") private CopilotExpAssignmentResponse expAssignments; + @JsonProperty("featureFlags") + private Map featureFlags; + @JsonProperty("enableManagedSettings") @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; @@ -296,6 +315,16 @@ public void setContextTier(String contextTier) { this.contextTier = contextTier; } + /** Gets the ask-user variant. @return the ask-user variant */ + public AskUserVariant getAskUserVariant() { + return askUserVariant; + } + + /** Sets the ask-user variant. @param askUserVariant the ask-user variant */ + public void setAskUserVariant(AskUserVariant askUserVariant) { + this.askUserVariant = askUserVariant; + } + /** Gets the tools. @return the tool definitions */ public List getTools() { return tools == null ? null : Collections.unmodifiableList(tools); @@ -427,6 +456,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; @@ -697,6 +741,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; @@ -1024,6 +1080,21 @@ public void setGitHubToken(String gitHubToken) { this.gitHubToken = gitHubToken; } + /** + * Gets the token-provider registration ID. @return the opaque registration ID + */ + public String getGitHubTokenProviderRegistrationId() { + return gitHubTokenProviderRegistrationId; + } + + /** + * Sets the token-provider registration ID. @param registrationId the opaque + * registration ID + */ + public void setGitHubTokenProviderRegistrationId(String registrationId) { + this.gitHubTokenProviderRegistrationId = registrationId; + } + /** Gets the remote session mode. @return the remote session mode */ public String getRemoteSession() { return remoteSession; @@ -1058,6 +1129,16 @@ public void setExpAssignments(CopilotExpAssignmentResponse expAssignments) { this.expAssignments = expAssignments; } + /** Gets host-resolved feature flags. @return the feature flags */ + public Map getFeatureFlags() { + return featureFlags; + } + + /** Sets host-resolved feature flags. @param featureFlags the feature flags */ + public void setFeatureFlags(Map featureFlags) { + this.featureFlags = featureFlags; + } + /** * Gets the self-fetch managed settings flag. @return the flag, or {@code null} * if not set @@ -1080,4 +1161,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 100% 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 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/sdk/src/main/java/com/github/copilot/rpc/DisableBypassPermissionsModes.java b/java/sdk/src/main/java/com/github/copilot/rpc/DisableBypassPermissionsModes.java new file mode 100644 index 0000000000..cf98f0526d --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/rpc/DisableBypassPermissionsModes.java @@ -0,0 +1,23 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ +package com.github.copilot.rpc; + +/** + * Known values for the managed bypass-permissions policy. + * + *

+ * The wire contract is an open string so callers can pass newer fail-closed + * modes directly to + * {@link ManagedSettingsPermissions#setDisableBypassPermissionsMode(String)}. + */ +public final class DisableBypassPermissionsModes { + /** Turns off bypass-permissions mode. */ + public static final String DISABLE = "disable"; + + /** Permits bypass only for automatic operations. */ + public static final String ALLOW_AUTO_ONLY = "allow-auto-only"; + + private DisableBypassPermissionsModes() { + } +} 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/src/main/java/com/github/copilot/rpc/GitHubMcpToolConfig.java b/java/sdk/src/main/java/com/github/copilot/rpc/GitHubMcpToolConfig.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/GitHubMcpToolConfig.java rename to java/sdk/src/main/java/com/github/copilot/rpc/GitHubMcpToolConfig.java diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/GitHubTokenProvider.java b/java/sdk/src/main/java/com/github/copilot/rpc/GitHubTokenProvider.java new file mode 100644 index 0000000000..daefc2f8d8 --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/rpc/GitHubTokenProvider.java @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.rpc; + +import java.util.concurrent.CompletableFuture; + +/** + * Acquires rotating GitHub tokens for one session. + *

+ * Implementations return either a token with a positive remaining lifetime or + * an explicit cancellation. Production GitHub tokens typically last eight + * hours. Initial cancellation, callback errors, and invalid token responses + * reject session creation or resume instead of falling back to ambient + * authentication. + * + * @since 1.0.0 + */ +@FunctionalInterface +public interface GitHubTokenProvider { + + /** + * Acquires a GitHub token for the supplied host and session context. + * + * @param args + * callback context + * @return a future containing a token or cancellation result + */ + CompletableFuture getToken(GitHubTokenProviderArgs args); +} diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/GitHubTokenProviderArgs.java b/java/sdk/src/main/java/com/github/copilot/rpc/GitHubTokenProviderArgs.java new file mode 100644 index 0000000000..283d3c4c7f --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/rpc/GitHubTokenProviderArgs.java @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.rpc; + +import com.github.copilot.generated.rpc.GitHubTokenAcquireReason; + +/** + * Context supplied when a session needs a GitHub token. + * + * @param host + * effective GitHub host for which a token is required + * @param sessionId + * session receiving the token, or {@code null} before a cloud + * session has been assigned an ID + * @param reason + * whether this is the initial acquisition or a refresh + * @since 1.0.0 + */ +public record GitHubTokenProviderArgs(String host, String sessionId, GitHubTokenAcquireReason reason) { +} diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/GitHubTokenProviderResult.java b/java/sdk/src/main/java/com/github/copilot/rpc/GitHubTokenProviderResult.java new file mode 100644 index 0000000000..9425286431 --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/rpc/GitHubTokenProviderResult.java @@ -0,0 +1,113 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.rpc; + +import java.util.Objects; + +/** + * Result of acquiring a session-scoped GitHub token. + *

+ * Token values are redacted from {@link #toString()}. + * + * @since 1.0.0 + */ +public final class GitHubTokenProviderResult { + + private final String accessToken; + private final long expiresIn; + private final String tokenType; + private final boolean cancelled; + + private GitHubTokenProviderResult(String accessToken, long expiresIn, String tokenType, boolean cancelled) { + this.accessToken = accessToken; + this.expiresIn = expiresIn; + this.tokenType = tokenType; + this.cancelled = cancelled; + } + + /** + * Creates a token result. + * + * @param accessToken + * GitHub access token + * @param expiresIn + * positive remaining lifetime in seconds when the callback completes + * @return the token result + */ + public static GitHubTokenProviderResult token(String accessToken, long expiresIn) { + return token(accessToken, expiresIn, null); + } + + /** + * Creates a token result with an explicit OAuth token type. + * + * @param accessToken + * GitHub access token + * @param expiresIn + * positive remaining lifetime in seconds when the callback completes + * @param tokenType + * OAuth token type, or {@code null} to use the runtime's bearer + * default + * @return the token result + */ + public static GitHubTokenProviderResult token(String accessToken, long expiresIn, String tokenType) { + Objects.requireNonNull(accessToken, "accessToken must not be null"); + return new GitHubTokenProviderResult(accessToken, expiresIn, tokenType, false); + } + + /** + * Creates an explicit cancellation result. + * + * @return the cancellation result + */ + public static GitHubTokenProviderResult cancelled() { + return new GitHubTokenProviderResult(null, 0, null, true); + } + + /** + * Gets whether acquisition was cancelled. + * + * @return {@code true} for a cancellation result + */ + public boolean isCancelled() { + return cancelled; + } + + /** + * Gets the access token. + * + * @return the token, or {@code null} for cancellation + */ + public String getAccessToken() { + return accessToken; + } + + /** + * Gets the remaining token lifetime. + * + * @return remaining lifetime in seconds + */ + public long getExpiresIn() { + return expiresIn; + } + + /** + * Gets the OAuth token type. + * + * @return token type, or {@code null} for the runtime default + */ + public String getTokenType() { + return tokenType; + } + + @Override + public String toString() { + if (cancelled) { + return "GitHubTokenProviderResult{cancelled}"; + } + return "GitHubTokenProviderResult{accessToken=, expiresIn=" + expiresIn + ", tokenType=" + tokenType + + "}"; + } +} 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 0000000000..274f8b89dc --- /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 0000000000..39e8fcf55a --- /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 0000000000..6755d959b3 --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/rpc/ManagedSettingsPermissions.java @@ -0,0 +1,91 @@ +/*--------------------------------------------------------------------------------------------- + * 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 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 String 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 String getDisableBypassPermissionsMode() { + return disableBypassPermissionsMode; + } + + /** + * Restricts bypass/allow-all permission modes. See + * {@link DisableBypassPermissionsModes} for known values. Newer values are + * forwarded unchanged so runtime policies remain fail-closed. + * + * @param value + * bypass-permissions policy + * @return this policy + */ + public ManagedSettingsPermissions setDisableBypassPermissionsMode(String 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 99% 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 index 8ce739ffbd..c68aecb63f 100644 --- a/java/src/main/java/com/github/copilot/rpc/McpStdioServerConfig.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/McpStdioServerConfig.java @@ -45,7 +45,7 @@ public final class McpStdioServerConfig extends McpServerConfig { @JsonProperty("env") private Map env; - @JsonProperty("workingDirectory") + @JsonProperty("cwd") private String workingDirectory; /** 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 100% 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 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 100% 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 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 100% 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 diff --git a/java/src/main/java/com/github/copilot/rpc/PermissionRequest.java b/java/sdk/src/main/java/com/github/copilot/rpc/PermissionRequest.java similarity index 93% rename from java/src/main/java/com/github/copilot/rpc/PermissionRequest.java rename to java/sdk/src/main/java/com/github/copilot/rpc/PermissionRequest.java index a3297bfb2c..fc49332b8a 100644 --- a/java/src/main/java/com/github/copilot/rpc/PermissionRequest.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/PermissionRequest.java @@ -5,8 +5,10 @@ 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; @@ -45,6 +47,14 @@ public class PermissionRequest { 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 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 2e5c60100a..6546291cf7 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 90% 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 05b73e88a9..ac4f71e07b 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,6 +53,7 @@ public class ResumeSessionConfig { private List models; private Boolean enableSessionTelemetry; private Boolean enableCitations; + private Boolean enableFileChangeTracking; private SessionLimitsConfig sessionLimits; private Boolean enableExperimentalMode; private Boolean skipCustomInstructions; @@ -62,6 +63,7 @@ public class ResumeSessionConfig { private String reasoningEffort; private String reasoningSummary; private String contextTier; + private AskUserVariant askUserVariant; private ModelCapabilitiesOverride modelCapabilities; private PermissionHandler onPermissionRequest; private McpAuthHandler onMcpAuthRequest; @@ -88,12 +90,14 @@ public class ResumeSessionConfig { private DefaultAgentConfig defaultAgent; private String agent; private List skillDirectories; + private List includedBuiltinSkills; private List instructionDirectories; private List pluginDirectories; private LargeToolOutputConfig largeOutput; private ToolSearchConfig toolSearch; private MemoryConfiguration memory; private List disabledSkills; + private List disabledMcpServers; private InfiniteSessionConfig infiniteSessions; private Consumer onEvent; private List commands; @@ -103,9 +107,13 @@ public class ResumeSessionConfig { private boolean enableMcpApps; private GitHubMcpToolConfig githubMcpToolConfig; private String gitHubToken; + @JsonIgnore + private GitHubTokenProvider gitHubTokenProvider; private String remoteSession; private CopilotExpAssignmentResponse expAssignments; private Boolean enableManagedSettings; + private Map featureFlags; + private ManagedSettings managedSettings; /** * Gets the AI model to use. @@ -451,6 +459,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. * @@ -752,6 +795,31 @@ public ResumeSessionConfig setContextTier(String contextTier) { return this; } + /** + * Gets the experience used by the built-in {@code ask_user} tool. + * + * @return the ask-user variant, or {@code null} to use the legacy experience + */ + public AskUserVariant getAskUserVariant() { + return askUserVariant; + } + + /** + * Sets the model-facing shape of the built-in {@code ask_user} tool when the + * session is resumed by a new client. + *

+ * When unset, the option is omitted and the legacy shape is used. Set an + * elicitation handler when selecting {@link AskUserVariant#ELICITATION}. + * + * @param askUserVariant + * the ask-user variant + * @return this config instance for method chaining + */ + public ResumeSessionConfig setAskUserVariant(AskUserVariant askUserVariant) { + this.askUserVariant = askUserVariant; + return this; + } + /** * Gets the permission request handler. * @@ -1451,6 +1519,28 @@ public ResumeSessionConfig setSkillDirectories(List skillDirectories) { return this; } + /** + * Gets the runtime-bundled skill allowlist. + * + * @return the built-in skill names, or {@code null} when unspecified + */ + public List getIncludedBuiltinSkills() { + return includedBuiltinSkills == null ? null : Collections.unmodifiableList(includedBuiltinSkills); + } + + /** + * Sets the runtime-bundled skill allowlist. In empty mode, omitting this option + * excludes all built-in skills; specifying names opts those built-ins back in. + * + * @param includedBuiltinSkills + * the built-in skill names to allow + * @return this config for method chaining + */ + public ResumeSessionConfig setIncludedBuiltinSkills(List includedBuiltinSkills) { + this.includedBuiltinSkills = includedBuiltinSkills; + return this; + } + /** * Gets the additional directories to search for custom instruction files. * @@ -1577,6 +1667,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. * @@ -1803,6 +1916,33 @@ public ResumeSessionConfig setGitHubToken(String gitHubToken) { return this; } + /** + * Gets the rotating GitHub token provider for the resumed session. + * + * @return the provider, or {@code null} when a static token is used + */ + public GitHubTokenProvider getGitHubTokenProvider() { + return gitHubTokenProvider; + } + + /** + * Sets the rotating GitHub token provider for the resumed session. + *

+ * The provider receives only the effective host, optional assigned session ID, + * and acquisition reason. It must return a positive remaining lifetime in + * seconds when its callback completes. Production GitHub tokens typically last + * eight hours. This option is mutually exclusive with + * {@link #setGitHubToken(String)}. + * + * @param gitHubTokenProvider + * provider used for initial acquisition and refresh + * @return this config instance for method chaining + */ + public ResumeSessionConfig setGitHubTokenProvider(GitHubTokenProvider gitHubTokenProvider) { + this.gitHubTokenProvider = gitHubTokenProvider; + return this; + } + /** * Gets the per-session remote behavior control. *

@@ -1856,6 +1996,23 @@ public ResumeSessionConfig setExpAssignments(CopilotExpAssignmentResponse expAss return this; } + /** Gets host-resolved feature-flag values. @return the feature flags */ + public Map getFeatureFlags() { + return featureFlags; + } + + /** + * Sets feature-flag values resolved by the host to apply on resume. + * + * @param featureFlags + * the feature flags + * @return this config for method chaining + */ + public ResumeSessionConfig setFeatureFlags(Map featureFlags) { + this.featureFlags = featureFlags; + return this; + } + /** * Gets whether the runtime self-fetches enterprise managed settings at session * bootstrap on resume. @@ -1886,6 +2043,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. *

@@ -1915,11 +2090,13 @@ 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; + copy.askUserVariant = this.askUserVariant; copy.modelCapabilities = this.modelCapabilities; copy.onPermissionRequest = this.onPermissionRequest; copy.onUserInputRequest = this.onUserInputRequest; @@ -1946,6 +2123,9 @@ public ResumeSessionConfig clone() { copy.defaultAgent = this.defaultAgent; copy.agent = this.agent; copy.skillDirectories = this.skillDirectories != null ? new ArrayList<>(this.skillDirectories) : null; + copy.includedBuiltinSkills = this.includedBuiltinSkills != null + ? new ArrayList<>(this.includedBuiltinSkills) + : null; copy.instructionDirectories = this.instructionDirectories != null ? new ArrayList<>(this.instructionDirectories) : null; @@ -1954,6 +2134,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; @@ -1964,9 +2145,12 @@ public ResumeSessionConfig clone() { copy.enableMcpApps = this.enableMcpApps; copy.githubMcpToolConfig = this.githubMcpToolConfig; copy.gitHubToken = this.gitHubToken; + copy.gitHubTokenProvider = this.gitHubTokenProvider; copy.remoteSession = this.remoteSession; + copy.featureFlags = this.featureFlags != null ? new java.util.HashMap<>(this.featureFlags) : null; 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 91% 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 a0ecc2ed5c..776d58137b 100644 --- a/java/src/main/java/com/github/copilot/rpc/ResumeSessionRequest.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/ResumeSessionRequest.java @@ -47,6 +47,9 @@ public final class ResumeSessionRequest { @JsonProperty("contextTier") private String contextTier; + @JsonProperty("askUserVariant") + private AskUserVariant askUserVariant; + @JsonProperty("tools") private List tools; @@ -82,6 +85,9 @@ public final class ResumeSessionRequest { @JsonProperty("enableCitations") private Boolean enableCitations; + @JsonProperty("enableFileChangeTracking") + private Boolean enableFileChangeTracking; + @JsonProperty("sessionLimits") private SessionLimitsConfig sessionLimits; @@ -192,6 +198,9 @@ public final class ResumeSessionRequest { @JsonProperty("disabledSkills") private List disabledSkills; + @JsonProperty("disabledMcpServers") + private List disabledMcpServers; + @JsonProperty("infiniteSessions") private InfiniteSessionConfig infiniteSessions; @@ -223,16 +232,26 @@ public final class ResumeSessionRequest { @JsonProperty("gitHubToken") private String gitHubToken; + @JsonProperty("gitHubTokenProviderRegistrationId") + private String gitHubTokenProviderRegistrationId; + @JsonProperty("remoteSession") private String remoteSession; @JsonProperty("expAssignments") private CopilotExpAssignmentResponse expAssignments; + @JsonProperty("featureFlags") + private Map featureFlags; + @JsonProperty("enableManagedSettings") @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; @@ -298,6 +317,16 @@ public void setContextTier(String contextTier) { this.contextTier = contextTier; } + /** Gets the ask-user variant. @return the ask-user variant */ + public AskUserVariant getAskUserVariant() { + return askUserVariant; + } + + /** Sets the ask-user variant. @param askUserVariant the ask-user variant */ + public void setAskUserVariant(AskUserVariant askUserVariant) { + this.askUserVariant = askUserVariant; + } + /** Gets the tools. @return the tool definitions */ public List getTools() { return tools == null ? null : Collections.unmodifiableList(tools); @@ -432,6 +461,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; @@ -913,6 +957,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; @@ -1049,6 +1105,21 @@ public void setGitHubToken(String gitHubToken) { this.gitHubToken = gitHubToken; } + /** + * Gets the token-provider registration ID. @return the opaque registration ID + */ + public String getGitHubTokenProviderRegistrationId() { + return gitHubTokenProviderRegistrationId; + } + + /** + * Sets the token-provider registration ID. @param registrationId the opaque + * registration ID + */ + public void setGitHubTokenProviderRegistrationId(String registrationId) { + this.gitHubTokenProviderRegistrationId = registrationId; + } + /** Gets the remote session mode. @return the remote session mode */ public String getRemoteSession() { return remoteSession; @@ -1073,6 +1144,16 @@ public void setExpAssignments(CopilotExpAssignmentResponse expAssignments) { this.expAssignments = expAssignments; } + /** Gets host-resolved feature flags. @return the feature flags */ + public Map getFeatureFlags() { + return featureFlags; + } + + /** Sets host-resolved feature flags. @param featureFlags the feature flags */ + public void setFeatureFlags(Map featureFlags) { + this.featureFlags = featureFlags; + } + /** * Gets the self-fetch managed settings flag. @return the flag, or {@code null} * if not set @@ -1095,4 +1176,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 0000000000..a0c8eec5c6 --- /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 e36a027933..cbcacd9771 100644 --- a/java/src/main/java/com/github/copilot/rpc/SessionConfig.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/SessionConfig.java @@ -46,6 +46,7 @@ public class SessionConfig { private String reasoningEffort; private String reasoningSummary; private String contextTier; + private AskUserVariant askUserVariant; private List tools; private SystemMessageConfig systemMessage; private List availableTools; @@ -57,6 +58,7 @@ public class SessionConfig { private List models; private Boolean enableSessionTelemetry; private Boolean enableCitations; + private Boolean enableFileChangeTracking; private SessionLimitsConfig sessionLimits; private Boolean enableExperimentalMode; private Boolean skipCustomInstructions; @@ -78,12 +80,14 @@ public class SessionConfig { private String agent; private InfiniteSessionConfig infiniteSessions; private List skillDirectories; + private List includedBuiltinSkills; private List instructionDirectories; private List pluginDirectories; private LargeToolOutputConfig largeOutput; private ToolSearchConfig toolSearch; private MemoryConfiguration memory; private List disabledSkills; + private List disabledMcpServers; private String configDirectory; private Boolean enableConfigDiscovery; private Boolean skipEmbeddingRetrieval; @@ -103,10 +107,14 @@ public class SessionConfig { private boolean enableMcpApps; private GitHubMcpToolConfig githubMcpToolConfig; private String gitHubToken; + @JsonIgnore + private GitHubTokenProvider gitHubTokenProvider; private String remoteSession; private CloudSessionOptions cloud; private CopilotExpAssignmentResponse expAssignments; private Boolean enableManagedSettings; + private Map featureFlags; + private ManagedSettings managedSettings; /** * Gets the custom session ID. @@ -248,6 +256,30 @@ public SessionConfig setContextTier(String contextTier) { return this; } + /** + * Gets the experience used by the built-in {@code ask_user} tool. + * + * @return the ask-user variant, or {@code null} to use the legacy experience + */ + public AskUserVariant getAskUserVariant() { + return askUserVariant; + } + + /** + * Sets the model-facing shape of the built-in {@code ask_user} tool. + *

+ * When unset, the option is omitted and the legacy shape is used. Set an + * elicitation handler when selecting {@link AskUserVariant#ELICITATION}. + * + * @param askUserVariant + * the ask-user variant + * @return this config instance for method chaining + */ + public SessionConfig setAskUserVariant(AskUserVariant askUserVariant) { + this.askUserVariant = askUserVariant; + return this; + } + /** * Gets the custom tools for this session. * @@ -554,6 +586,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. * @@ -854,7 +920,9 @@ public UserInputHandler getOnUserInputRequest() { /** * Sets a handler for user input requests from the agent. *

- * When provided, enables the ask_user tool for the agent to request user input. + * When provided, enables the legacy question-and-answer form of the + * {@code ask_user} tool. Use an elicitation handler with + * {@link AskUserVariant#ELICITATION}. * * @param onUserInputRequest * the user input handler @@ -1138,6 +1206,28 @@ public SessionConfig setSkillDirectories(List skillDirectories) { return this; } + /** + * Gets the runtime-bundled skill allowlist. + * + * @return the built-in skill names, or {@code null} when unspecified + */ + public List getIncludedBuiltinSkills() { + return includedBuiltinSkills == null ? null : Collections.unmodifiableList(includedBuiltinSkills); + } + + /** + * Sets the runtime-bundled skill allowlist. In empty mode, omitting this option + * excludes all built-in skills; specifying names opts those built-ins back in. + * + * @param includedBuiltinSkills + * the built-in skill names to allow + * @return this config instance for method chaining + */ + public SessionConfig setIncludedBuiltinSkills(List includedBuiltinSkills) { + this.includedBuiltinSkills = includedBuiltinSkills; + return this; + } + /** * Gets the additional directories to search for custom instruction files. * @@ -1268,6 +1358,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. * @@ -1885,6 +1998,33 @@ public SessionConfig setGitHubToken(String gitHubToken) { return this; } + /** + * Gets the rotating GitHub token provider for this session. + * + * @return the provider, or {@code null} when a static token is used + */ + public GitHubTokenProvider getGitHubTokenProvider() { + return gitHubTokenProvider; + } + + /** + * Sets the rotating GitHub token provider for this session. + *

+ * The provider receives only the effective host, optional assigned session ID, + * and acquisition reason. It must return a positive remaining lifetime in + * seconds when its callback completes. Production GitHub tokens typically last + * eight hours. This option is mutually exclusive with + * {@link #setGitHubToken(String)}. + * + * @param gitHubTokenProvider + * provider used for initial acquisition and refresh + * @return this config instance for method chaining + */ + public SessionConfig setGitHubTokenProvider(GitHubTokenProvider gitHubTokenProvider) { + this.gitHubTokenProvider = gitHubTokenProvider; + return this; + } + /** * Gets the per-session remote behavior control. *

@@ -1985,6 +2125,23 @@ public SessionConfig setExpAssignments(CopilotExpAssignmentResponse expAssignmen return this; } + /** Gets host-resolved feature-flag values. @return the feature flags */ + public Map getFeatureFlags() { + return featureFlags; + } + + /** + * Sets feature-flag values resolved by the host for this session. + * + * @param featureFlags + * the feature flags + * @return this config instance for method chaining + */ + public SessionConfig setFeatureFlags(Map featureFlags) { + this.featureFlags = featureFlags; + return this; + } + /** * Gets whether the runtime self-fetches enterprise managed settings at session * bootstrap. @@ -2003,10 +2160,11 @@ public Optional getEnableManagedSettings() { * (bypass-permissions policy) at session bootstrap. *

* When {@code true}, the runtime self-fetches enterprise managed settings using - * the session's {@link #getGitHubToken() gitHubToken}. Requires - * {@code gitHubToken} to be set; if omitted, the runtime is expected to reject - * session creation (fail-closed). When unset, behaves exactly as before. - * Serialized on the wire as {@code enableManagedSettings}. + * the session's static {@link #getGitHubToken() gitHubToken} or + * {@link #getGitHubTokenProvider() gitHubTokenProvider}. Requires one of those + * credentials; if both are omitted, the runtime is expected to reject session + * creation (fail-closed). When unset, behaves exactly as before. Serialized on + * the wire as {@code enableManagedSettings}. * * @param enableManagedSettings * {@code true} to opt into self-fetching managed settings @@ -2017,6 +2175,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. *

@@ -2037,6 +2218,7 @@ public SessionConfig clone() { copy.reasoningEffort = this.reasoningEffort; copy.reasoningSummary = this.reasoningSummary; copy.contextTier = this.contextTier; + copy.askUserVariant = this.askUserVariant; copy.tools = this.tools != null ? new ArrayList<>(this.tools) : null; copy.systemMessage = this.systemMessage; copy.availableTools = this.availableTools != null ? new ArrayList<>(this.availableTools) : null; @@ -2050,6 +2232,7 @@ 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; @@ -2071,6 +2254,9 @@ public SessionConfig clone() { copy.agent = this.agent; copy.infiniteSessions = this.infiniteSessions; copy.skillDirectories = this.skillDirectories != null ? new ArrayList<>(this.skillDirectories) : null; + copy.includedBuiltinSkills = this.includedBuiltinSkills != null + ? new ArrayList<>(this.includedBuiltinSkills) + : null; copy.instructionDirectories = this.instructionDirectories != null ? new ArrayList<>(this.instructionDirectories) : null; @@ -2079,6 +2265,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; @@ -2099,10 +2286,13 @@ public SessionConfig clone() { copy.enableMcpApps = this.enableMcpApps; copy.githubMcpToolConfig = this.githubMcpToolConfig; copy.gitHubToken = this.gitHubToken; + copy.gitHubTokenProvider = this.gitHubTokenProvider; copy.remoteSession = this.remoteSession; copy.cloud = this.cloud; + copy.featureFlags = this.featureFlags != null ? new java.util.HashMap<>(this.featureFlags) : null; 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 100% 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 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 0000000000..7d0923e0fe --- /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 0000000000..648321a219 --- /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 ccf0ef5309..de274b66a1 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 0000000000..c260985845 --- /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/src/main/java/com/github/copilot/rpc/UserPromptTransformedHandler.java b/java/sdk/src/main/java/com/github/copilot/rpc/UserPromptTransformedHandler.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/UserPromptTransformedHandler.java rename to java/sdk/src/main/java/com/github/copilot/rpc/UserPromptTransformedHandler.java diff --git a/java/src/main/java/com/github/copilot/rpc/UserPromptTransformedHookInput.java b/java/sdk/src/main/java/com/github/copilot/rpc/UserPromptTransformedHookInput.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/UserPromptTransformedHookInput.java rename to java/sdk/src/main/java/com/github/copilot/rpc/UserPromptTransformedHookInput.java diff --git a/java/src/main/java/com/github/copilot/rpc/UserPromptTransformedHookOutput.java b/java/sdk/src/main/java/com/github/copilot/rpc/UserPromptTransformedHookOutput.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/UserPromptTransformedHookOutput.java rename to java/sdk/src/main/java/com/github/copilot/rpc/UserPromptTransformedHookOutput.java 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 db9e3ca62d..28cd759288 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 100% 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 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 99% 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 dbc6921afa..f88c1ac7c8 100644 --- a/java/src/main/java/com/github/copilot/tool/CopilotToolProcessor.java +++ b/java/sdk/src/main/java/com/github/copilot/tool/CopilotToolProcessor.java @@ -317,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, @@ -329,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("); @@ -341,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(" )"); } 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 100% 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 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 38bc1f93d5..8bc2dbd55c 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 0000000000..a67346b889 --- /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 0000000000..2900464443 --- /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 0000000000..fa353e6277 --- /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 92% rename from java/src/test/java/com/github/copilot/CapiProxy.java rename to java/sdk/src/test/java/com/github/copilot/CapiProxy.java index 6484f05818..3124cc3d6b 100644 --- a/java/src/test/java/com/github/copilot/CapiProxy.java +++ b/java/sdk/src/test/java/com/github/copilot/CapiProxy.java @@ -57,16 +57,15 @@ public class CapiProxy implements AutoCloseable { private static final ObjectMapper MAPPER = new ObjectMapper(); private static final Pattern LISTENING_PATTERN = Pattern.compile("Listening: (http://[^\\s]+)(?:\\s+(\\{.*\\}))?$"); + private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient(); private Process process; private String proxyUrl; private String connectProxyUrl; private String caFilePath; - private final HttpClient httpClient; private BufferedReader stdoutReader; public CapiProxy() { - this.httpClient = HttpClient.newHttpClient(); } /** @@ -93,9 +92,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 @@ -211,7 +211,7 @@ public void configure(String filePath, String workDir, TestInfo testInfo) throws HttpRequest request = HttpRequest.newBuilder().uri(URI.create(proxyUrl + "/config")) .header("Content-Type", "application/json").POST(HttpRequest.BodyPublishers.ofString(body)).build(); - HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + HttpResponse response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { throw new IOException("Proxy config failed with status " + response.statusCode() + ": " + response.body()); } @@ -233,7 +233,7 @@ public List> getExchanges() throws IOException, InterruptedE HttpRequest request = HttpRequest.newBuilder().uri(URI.create(proxyUrl + "/exchanges")).GET().build(); - HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + HttpResponse response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { throw new IOException("Failed to get exchanges: " + response.statusCode()); } @@ -283,7 +283,7 @@ public void setCopilotUserByToken(String token, String login, String copilotPlan HttpRequest request = HttpRequest.newBuilder().uri(URI.create(proxyUrl + "/copilot-user-config")) .header("Content-Type", "application/json").POST(HttpRequest.BodyPublishers.ofString(body)).build(); - HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + HttpResponse response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { throw new IOException( "Failed to set copilot user config: " + response.statusCode() + ": " + response.body()); @@ -330,7 +330,7 @@ public void setCopilotUserByToken(String token, Map response) HttpRequest request = HttpRequest.newBuilder().uri(URI.create(proxyUrl + "/copilot-user-config")) .header("Content-Type", "application/json").POST(HttpRequest.BodyPublishers.ofString(body)).build(); - HttpResponse response2 = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + HttpResponse response2 = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); if (response2.statusCode() != 200) { throw new IOException( "Failed to set copilot user config: " + response2.statusCode() + ": " + response2.body()); @@ -375,7 +375,7 @@ public void stop(boolean skipWritingCache) throws IOException, InterruptedExcept HttpRequest request = HttpRequest.newBuilder().uri(URI.create(stopUrl)) .POST(HttpRequest.BodyPublishers.noBody()).build(); - httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); } catch (Exception e) { // Best effort - ignore errors } @@ -516,6 +516,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/sdk/src/test/java/com/github/copilot/CapiProxyTest.java b/java/sdk/src/test/java/com/github/copilot/CapiProxyTest.java new file mode 100644 index 0000000000..9ac044a2b8 --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/CapiProxyTest.java @@ -0,0 +1,21 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.reflect.Modifier; + +import org.junit.jupiter.api.Test; + +class CapiProxyTest { + + @Test + void proxyInstancesShareHttpClientResources() throws Exception { + var field = CapiProxy.class.getDeclaredField("HTTP_CLIENT"); + assertTrue(Modifier.isStatic(field.getModifiers()), + "E2E contexts must not retain one HttpClient selector manager per test class"); + } +} 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 64% rename from java/src/test/java/com/github/copilot/CapiSessionOptionsTest.java rename to java/sdk/src/test/java/com/github/copilot/CapiSessionOptionsTest.java index 17e8f131f7..dccb4e9add 100644 --- a/java/src/test/java/com/github/copilot/CapiSessionOptionsTest.java +++ b/java/sdk/src/test/java/com/github/copilot/CapiSessionOptionsTest.java @@ -9,12 +9,16 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import com.fasterxml.jackson.databind.JsonNode; +import com.github.copilot.rpc.AutoTier; import com.github.copilot.rpc.CapiSessionOptions; import com.github.copilot.rpc.ResumeSessionConfig; import com.github.copilot.rpc.SessionConfig; @@ -29,6 +33,7 @@ void defaultsAreNull() { var capi = new CapiSessionOptions(); assertNull(capi.getEnableWebSocketResponses()); + assertNull(capi.getAutoTier()); } @Test @@ -37,6 +42,8 @@ void fluentSetterReturnsSameInstance() { assertSame(capi, capi.setEnableWebSocketResponses(true)); assertEquals(Boolean.TRUE, capi.getEnableWebSocketResponses()); + assertSame(capi, capi.setAutoTier(AutoTier.BALANCE)); + assertEquals(AutoTier.BALANCE, capi.getAutoTier()); } @Test @@ -46,6 +53,7 @@ void serializesEnableWebSocketResponses() { JsonNode json = JsonRpcClient.getObjectMapper().valueToTree(capi); assertTrue(json.get("enableWebSocketResponses").asBoolean()); + assertTrue(json.path("autoTier").isMissingNode()); } @Test @@ -55,6 +63,44 @@ void omitsUnsetEnableWebSocketResponses() { JsonNode json = JsonRpcClient.getObjectMapper().valueToTree(capi); assertTrue(json.path("enableWebSocketResponses").isMissingNode()); + assertTrue(json.path("autoTier").isMissingNode()); + assertEquals(0, json.size()); + } + + @ParameterizedTest + @CsvSource({"EFFICIENCY,efficiency", "BALANCE,balance", "INTELLIGENCE,intelligence"}) + void autoTierCanonicalValuesRoundTripAndForward(AutoTier tier, String value) throws Exception { + var mapper = JsonRpcClient.getObjectMapper(); + var capi = new CapiSessionOptions().setAutoTier(tier); + JsonNode json = mapper.valueToTree(capi); + assertEquals(value, json.get("autoTier").asText()); + assertEquals(1, json.size()); + assertEquals(tier, mapper.treeToValue(json, CapiSessionOptions.class).getAutoTier()); + + capi.setEnableWebSocketResponses(false); + var create = SessionRequestBuilder.buildCreateRequest(new SessionConfig().setModel("auto").setCapi(capi), + "session-1"); + var resume = SessionRequestBuilder.buildResumeRequest("session-1", new ResumeSessionConfig().setCapi(capi)); + for (Object request : new Object[]{create, resume}) { + JsonNode requestJson = mapper.valueToTree(request); + assertEquals(value, requestJson.get("capi").get("autoTier").asText()); + assertFalse(requestJson.get("capi").get("enableWebSocketResponses").asBoolean()); + assertEquals(2, requestJson.get("capi").size()); + } + } + + @Test + void autoTierRejectsNoncanonicalValues() { + for (String value : new String[]{"balanced", "Balance", "unknown"}) { + assertThrows(IllegalArgumentException.class, () -> AutoTier.fromValue(value)); + } + assertNull(AutoTier.fromValue(null)); + } + + @Test + void clearingAutoTierOmitsIt() { + var capi = new CapiSessionOptions().setAutoTier(AutoTier.BALANCE).setAutoTier(null); + JsonNode json = JsonRpcClient.getObjectMapper().valueToTree(capi); assertEquals(0, json.size()); } @@ -67,6 +113,7 @@ void createRequestIncludesCapiWhenSet() { assertNotNull(request.getCapi()); assertTrue(json.get("capi").get("enableWebSocketResponses").asBoolean()); + assertTrue(json.get("capi").path("autoTier").isMissingNode()); } @Test @@ -89,6 +136,7 @@ void resumeRequestIncludesCapiWhenSet() { assertNotNull(request.getCapi()); assertTrue(json.get("capi").get("enableWebSocketResponses").asBoolean()); + assertTrue(json.get("capi").path("autoTier").isMissingNode()); } @Test 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 82% rename from java/src/test/java/com/github/copilot/CliServerManagerTest.java rename to java/sdk/src/test/java/com/github/copilot/CliServerManagerTest.java index b445d6153a..b41f99adb7 100644 --- a/java/src/test/java/com/github/copilot/CliServerManagerTest.java +++ b/java/sdk/src/test/java/com/github/copilot/CliServerManagerTest.java @@ -9,9 +9,13 @@ import java.io.IOException; import java.net.ServerSocket; import java.net.URI; +import java.nio.file.Path; +import java.util.Map; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import com.github.copilot.ffi.NativeRuntimeLoader; import com.github.copilot.rpc.CopilotClientOptions; import com.github.copilot.rpc.TelemetryConfig; @@ -22,6 +26,47 @@ */ class CliServerManagerTest { + @TempDir + Path tempDir; + + @Test + void explicitCliPathDoesNotRequireRuntimeBundle() throws Exception { + Path explicit = tempDir.resolve("copilot"); + var manager = new CliServerManager(new CopilotClientOptions().setCliPath(explicit.toString())); + + assertEquals(explicit.toString(), manager.resolveCliLaunch().executable()); + } + + @Test + void inheritedCliPathEnvironmentOverrideDoesNotRequireRuntimeBundle() throws Exception { + Path inherited = tempDir.resolve("copilot-runtime"); + var manager = new CliServerManager(new CopilotClientOptions()); + + assertEquals(inherited.toString(), manager.resolveCliLaunch(inherited.toString()).executable()); + } + + @Test + void configuredEnvironmentCliPathOverridesInheritedEnvironment() throws Exception { + Path inherited = tempDir.resolve("inherited-copilot-runtime"); + Path configured = tempDir.resolve("configured-copilot-runtime"); + var options = new CopilotClientOptions() + .setEnvironment(Map.of(NativeRuntimeLoader.COPILOT_CLI_PATH_ENV, configured.toString())); + var manager = new CliServerManager(options); + + assertEquals(configured.toString(), manager.resolveCliLaunch(inherited.toString()).executable()); + } + + @Test + void explicitCliPathOverridesEnvironment() throws Exception { + Path explicit = tempDir.resolve("explicit-copilot-runtime"); + Path configured = tempDir.resolve("configured-copilot-runtime"); + var options = new CopilotClientOptions().setCliPath(explicit.toString()) + .setEnvironment(Map.of(NativeRuntimeLoader.COPILOT_CLI_PATH_ENV, configured.toString())); + var manager = new CliServerManager(options); + + assertEquals(explicit.toString(), manager.resolveCliLaunch("inherited-copilot-runtime").executable()); + } + // ===== parseCliUrl tests ===== @Test @@ -48,6 +93,13 @@ void parseCliUrlWithHttpsPrefix() { assertEquals("https://secure.host:443", uri.toString()); } + @Test + void parseCliUrlWithBracketedIpv6() { + URI uri = CliServerManager.parseCliUrl("[::1]:4321"); + assertNotNull(uri.getHost()); + assertEquals(4321, uri.getPort()); + } + @Test void parseCliUrlWithHostOnly() { URI uri = CliServerManager.parseCliUrl("copilot.example.com"); @@ -70,18 +122,13 @@ 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(); - } - @Test void connectToServerStdioMode() throws Exception { var options = new CopilotClientOptions(); var manager = new CliServerManager(options); // Create a dummy process for stdio mode - Process process = startBlockingProcess(); + Process process = new TestProcess(); try { JsonRpcClient client = manager.connectToServer(process, null, null); assertNotNull(client); 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 97% rename from java/src/test/java/com/github/copilot/CommandsTest.java rename to java/sdk/src/test/java/com/github/copilot/CommandsTest.java index 0da8822a2b..f5f8424391 100644 --- a/java/src/test/java/com/github/copilot/CommandsTest.java +++ b/java/sdk/src/test/java/com/github/copilot/CommandsTest.java @@ -95,7 +95,7 @@ void buildCreateRequestIncludesCommandWireDefinitions() { assertEquals("deploy", request.getCommands().get(0).getName()); assertEquals("Deploy", request.getCommands().get(0).getDescription()); assertEquals("rollback", request.getCommands().get(1).getName()); - assertNull(request.getCommands().get(1).getDescription()); + assertEquals("", request.getCommands().get(1).getDescription()); } @Test @@ -130,11 +130,11 @@ void commandWireDefinitionHasNameAndDescription() { } @Test - void commandWireDefinitionNullDescriptionAllowed() { + void commandWireDefinitionNormalizesNullDescription() { var wire = new CommandWireDefinition("rollback", null); assertEquals("rollback", wire.getName()); - assertNull(wire.getDescription()); + assertEquals("", wire.getDescription()); } @Test 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 93% rename from java/src/test/java/com/github/copilot/ConfigCloneTest.java rename to java/sdk/src/test/java/com/github/copilot/ConfigCloneTest.java index a8e7fb2e0b..2433e5f67a 100644 --- a/java/src/test/java/com/github/copilot/ConfigCloneTest.java +++ b/java/sdk/src/test/java/com/github/copilot/ConfigCloneTest.java @@ -18,6 +18,7 @@ import com.github.copilot.generated.SessionEvent; import com.github.copilot.generated.rpc.SessionLimitsConfig; import com.github.copilot.rpc.AutoModeSwitchResponse; +import com.github.copilot.rpc.AskUserVariant; import com.github.copilot.rpc.CopilotClientOptions; import com.github.copilot.rpc.DefaultAgentConfig; import com.github.copilot.rpc.ExitPlanModeResult; @@ -119,7 +120,9 @@ void sessionConfigCloneBasic() { original.setModel("gpt-4o"); original.setReasoningSummary("detailed"); original.setContextTier("long_context"); + original.setAskUserVariant(AskUserVariant.ELICITATION); 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)); @@ -132,7 +135,9 @@ void sessionConfigCloneBasic() { assertEquals(original.getModel(), cloned.getModel()); assertEquals(original.getReasoningSummary(), cloned.getReasoningSummary()); assertEquals(original.getContextTier(), cloned.getContextTier()); + assertEquals(original.getAskUserVariant(), cloned.getAskUserVariant()); 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 +151,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 +162,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 +184,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()); } @@ -193,7 +201,9 @@ void resumeSessionConfigCloneBasic() { original.setModel("o1"); original.setReasoningSummary("none"); original.setContextTier("long_context"); + original.setAskUserVariant(AskUserVariant.LEGACY); 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)); @@ -204,7 +214,9 @@ void resumeSessionConfigCloneBasic() { assertEquals(original.getModel(), cloned.getModel()); assertEquals(original.getReasoningSummary(), cloned.getReasoningSummary()); assertEquals(original.getContextTier(), cloned.getContextTier()); + assertEquals(original.getAskUserVariant(), cloned.getAskUserVariant()); 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 +241,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 +390,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() { 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 92% rename from java/src/test/java/com/github/copilot/CopilotClientTest.java rename to java/sdk/src/test/java/com/github/copilot/CopilotClientTest.java index d977563aeb..db40d425f4 100644 --- a/java/src/test/java/com/github/copilot/CopilotClientTest.java +++ b/java/sdk/src/test/java/com/github/copilot/CopilotClientTest.java @@ -8,6 +8,8 @@ import org.junit.jupiter.api.Test; import com.github.copilot.rpc.CopilotClientOptions; +import com.github.copilot.rpc.DeleteSessionResponse; +import com.github.copilot.rpc.GitHubTokenProviderResult; import com.github.copilot.rpc.PermissionHandler; import com.github.copilot.rpc.PingResponse; import com.github.copilot.rpc.SessionConfig; @@ -111,6 +113,35 @@ void testForceStopAndExternalStopDoNotRequestRuntimeShutdown() throws Exception verify(externalRpc, never()).invoke(eq("runtime.shutdown"), any(), eq(Void.class)); } + @Test + @SuppressWarnings("unchecked") + void testDeleteSessionReleasesGitHubTokenProvider() throws Exception { + var client = new CopilotClient(new CopilotClientOptions().setAutoStart(false)); + var rpc = mock(JsonRpcClient.class); + when(rpc.invoke(eq("session.delete"), any(), eq(DeleteSessionResponse.class))) + .thenReturn(CompletableFuture.completedFuture(new DeleteSessionResponse(true, null))); + when(rpc.invoke(eq("session.destroy"), any(), eq(Void.class))) + .thenReturn(CompletableFuture.completedFuture(null)); + setConnectionFuture(client, rpc, null); + + var registry = new GitHubTokenProviderRegistry(); + var registration = registry + .register(args -> CompletableFuture.completedFuture(GitHubTokenProviderResult.cancelled())); + var session = new CopilotSession("delete-session", rpc); + session.setGitHubTokenProviderRegistration(registration); + Field sessionsField = CopilotClient.class.getDeclaredField("sessions"); + sessionsField.setAccessible(true); + ((Map) sessionsField.get(client)).put(session.getSessionId(), session); + + try { + client.deleteSession(session.getSessionId()).join(); + assertNull(registry.get(registration.id())); + } finally { + session.close(); + client.close(); + } + } + @Test void testClientConstruction() { var client = new CopilotClient(); @@ -148,6 +179,15 @@ void testCliUrlOnlyConstruction() { client.close(); } + @Test + void testBracketedIpv6CliUrlNormalizesHost() throws Exception { + try (var client = new CopilotClient(new CopilotClientOptions().setCliUrl("[::1]:4321"))) { + Field hostField = CopilotClient.class.getDeclaredField("optionsHost"); + hostField.setAccessible(true); + assertEquals("::1", hostField.get(client)); + } + } + @Test void testCliUrlMutualExclusionWithCliPath() { var options = new CopilotClientOptions().setCliUrl("localhost:3000").setCliPath("/path/to/cli"); @@ -610,9 +650,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 0000000000..46223d56d8 --- /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 96% rename from java/src/test/java/com/github/copilot/CopilotRequestSessionIdE2ETest.java rename to java/sdk/src/test/java/com/github/copilot/CopilotRequestSessionIdE2ETest.java index 3025c64c39..b4159d839c 100644 --- a/java/src/test/java/com/github/copilot/CopilotRequestSessionIdE2ETest.java +++ b/java/sdk/src/test/java/com/github/copilot/CopilotRequestSessionIdE2ETest.java @@ -77,11 +77,11 @@ void threadsSessionIdForCapiAndByok() throws Exception { // BYOK session. int before = handler.inferenceRequests().size(); ProviderConfig provider = new ProviderConfig().setType("openai").setWireApi("responses") - .setBaseUrl("https://byok.invalid/v1").setApiKey("byok-secret").setModelId("claude-sonnet-4.5") - .setWireModel("claude-sonnet-4.5"); + .setBaseUrl("https://byok.invalid/v1").setApiKey("byok-secret").setModelId("claude-sonnet-5") + .setWireModel("claude-sonnet-5"); CopilotSession byokSession = client .createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) - .setModel("claude-sonnet-4.5").setProvider(provider)) + .setModel("claude-sonnet-5").setProvider(provider)) .get(); String byokSessionId = byokSession.getSessionId(); 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 97% rename from java/src/test/java/com/github/copilot/CopilotRequestTestSupport.java rename to java/sdk/src/test/java/com/github/copilot/CopilotRequestTestSupport.java index ecbf92068f..fa2a6354be 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)); } /** @@ -133,7 +134,7 @@ static String anthropicMessageSseBody(String text) { startMessage.put("id", "msg_stub_1"); startMessage.put("type", "message"); startMessage.put("role", "assistant"); - startMessage.put("model", "claude-sonnet-4.5"); + startMessage.put("model", "claude-sonnet-5"); startMessage.put("content", List.of()); startMessage.put("stop_reason", null); startMessage.put("stop_sequence", null); @@ -250,7 +251,7 @@ static HttpResponse buildInferenceResponse(String url, String bodyT body.put("id", "msg_stub_1"); body.put("type", "message"); body.put("role", "assistant"); - body.put("model", "claude-sonnet-4.5"); + body.put("model", "claude-sonnet-5"); body.put("content", List.of(Map.of("type", "text", "text", text))); body.put("stop_reason", "end_turn"); body.put("stop_sequence", null); @@ -300,14 +301,14 @@ static String modelCatalog(List supportedEndpoints) { Map capabilities = new LinkedHashMap<>(); capabilities.put("type", "chat"); - capabilities.put("family", "claude-sonnet-4.5"); + capabilities.put("family", "claude-sonnet-5"); capabilities.put("tokenizer", "o200k_base"); capabilities.put("limits", limits); capabilities.put("supports", supports); Map model = new LinkedHashMap<>(); - model.put("id", "claude-sonnet-4.5"); - model.put("name", "Claude Sonnet 4.5"); + model.put("id", "claude-sonnet-5"); + model.put("name", "Claude Sonnet 5"); model.put("object", "model"); model.put("vendor", "Anthropic"); model.put("version", "1"); @@ -415,7 +416,7 @@ private static Map chatChunkBase() { base.put("id", "chatcmpl-stub-1"); base.put("object", "chat.completion.chunk"); base.put("created", 1); - base.put("model", "claude-sonnet-4.5"); + base.put("model", "claude-sonnet-5"); return base; } @@ -458,7 +459,7 @@ private static Map chatCompletion(String text) { root.put("id", "chatcmpl-stub-1"); root.put("object", "chat.completion"); root.put("created", 1); - root.put("model", "claude-sonnet-4.5"); + root.put("model", "claude-sonnet-5"); root.put("choices", List.of(choice)); root.put("usage", chatUsage()); return root; 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 156c968489..79e968cd39 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 89% rename from java/src/test/java/com/github/copilot/DataObjectCoverageTest.java rename to java/sdk/src/test/java/com/github/copilot/DataObjectCoverageTest.java index f38a038368..c0b0324e3c 100644 --- a/java/src/test/java/com/github/copilot/DataObjectCoverageTest.java +++ b/java/sdk/src/test/java/com/github/copilot/DataObjectCoverageTest.java @@ -8,6 +8,7 @@ import org.junit.jupiter.api.Test; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; import com.github.copilot.rpc.CustomAgentConfig; @@ -139,6 +140,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 @@ -303,6 +325,15 @@ void mcpStdioServerConfigCoversGettersAndFluentSetters() { assertEquals(30, cfg.getTimeout()); } + @Test + void mcpStdioServerConfigSerializesWorkingDirectoryAsCwd() { + var json = new ObjectMapper() + .valueToTree(new McpStdioServerConfig().setCommand("node").setWorkingDirectory("/workspace")); + + assertEquals("/workspace", json.path("cwd").asText()); + assertFalse(json.has("workingDirectory")); + } + @Test void modelCapabilitiesOverrideCoversNestedSupportsAndLimits() { var supports = new ModelCapabilitiesOverride.Supports().setVision(true).setReasoningEffort(false); 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 f7170f4fde..4e1396aa93 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 74% rename from java/src/test/java/com/github/copilot/E2ETestContext.java rename to java/sdk/src/test/java/com/github/copilot/E2ETestContext.java index f524b33dab..a6bec65d2c 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, @@ -62,8 +65,8 @@ public class E2ETestContext implements AutoCloseable { */ private static final String DEFAULT_GITHUB_TOKEN = "fake-token-for-e2e-tests"; private static final Pattern SNAKE_CASE = Pattern.compile("[^a-zA-Z0-9]"); - private static final Pattern USER_CONTENT_PATTERN = Pattern - .compile("^\\s+-\\s+role:\\s+user\\s*$\\s+content:\\s*(.+?)$", Pattern.MULTILINE); + private static final Pattern USER_ROLE_PATTERN = Pattern.compile("^(\\s*)-\\s+role:\\s+user\\s*$"); + private static final Pattern CONTENT_PATTERN = Pattern.compile("^(\\s*)content:\\s*(.*)$"); private final String cliPath; private final Path homeDir; @@ -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, @@ -223,25 +227,109 @@ public List getExpectedUserPrompts() { return List.of(); } try { - String content = Files.readString(currentSnapshotFile); - List prompts = new ArrayList<>(); - Matcher matcher = USER_CONTENT_PATTERN.matcher(content); - while (matcher.find()) { - String prompt = matcher.group(1).trim(); - // Remove quotes if present - if ((prompt.startsWith("\"") && prompt.endsWith("\"")) - || (prompt.startsWith("'") && prompt.endsWith("'"))) { - prompt = prompt.substring(1, prompt.length() - 1); + return parseExpectedUserPrompts(Files.readString(currentSnapshotFile)); + } catch (IOException e) { + LOG.warning("Failed to read snapshot file: " + e.getMessage()); + return List.of(); + } + } + + static List parseExpectedUserPrompts(String yaml) { + String[] lines = yaml.split("\\R", -1); + List prompts = new ArrayList<>(); + + for (int i = 0; i < lines.length; i++) { + Matcher roleMatcher = USER_ROLE_PATTERN.matcher(lines[i]); + if (!roleMatcher.matches()) { + continue; + } + + int roleIndent = roleMatcher.group(1).length(); + for (i++; i < lines.length; i++) { + String line = lines[i]; + if (!line.isBlank() && leadingWhitespace(line) <= roleIndent) { + i--; + break; } - if (!prompts.contains(prompt)) { + + Matcher contentMatcher = CONTENT_PATTERN.matcher(line); + if (!contentMatcher.matches()) { + continue; + } + + int contentIndent = contentMatcher.group(1).length(); + String value = contentMatcher.group(2).trim(); + List continuation = new ArrayList<>(); + while (i + 1 < lines.length + && (lines[i + 1].isBlank() || leadingWhitespace(lines[i + 1]) > contentIndent)) { + continuation.add(lines[++i]); + } + + String prompt = isBlockScalar(value) + ? parseBlockScalar(value.charAt(0), continuation) + : parsePlainScalar(value, continuation); + if (!prompt.isEmpty() && !prompts.contains(prompt)) { prompts.add(prompt); } + break; } - return prompts; - } catch (IOException e) { - LOG.warning("Failed to read snapshot file: " + e.getMessage()); - return List.of(); } + + return prompts; + } + + private static String parsePlainScalar(String firstLine, List continuation) { + StringBuilder value = new StringBuilder(unquote(firstLine)); + for (String line : continuation) { + if (!line.isBlank()) { + if (!value.isEmpty()) { + value.append(' '); + } + value.append(line.trim()); + } + } + return value.toString(); + } + + private static String parseBlockScalar(char style, List lines) { + int contentIndent = lines.stream().filter(line -> !line.isBlank()).mapToInt(E2ETestContext::leadingWhitespace) + .min().orElse(0); + StringBuilder value = new StringBuilder(); + boolean previousWasContent = false; + for (String line : lines) { + String text = line.isBlank() ? "" : line.substring(Math.min(contentIndent, line.length())); + if (text.isEmpty()) { + value.append('\n'); + previousWasContent = false; + } else { + if (previousWasContent) { + value.append(style == '>' ? ' ' : '\n'); + } + value.append(text); + previousWasContent = true; + } + } + return value.toString().stripTrailing(); + } + + private static boolean isBlockScalar(String value) { + return value.matches("[>|][+-]?"); + } + + private static String unquote(String value) { + if (value.length() >= 2 && ((value.startsWith("\"") && value.endsWith("\"")) + || (value.startsWith("'") && value.endsWith("'")))) { + return value.substring(1, value.length() - 1); + } + return value; + } + + private static int leadingWhitespace(String value) { + int index = 0; + while (index < value.length() && Character.isWhitespace(value.charAt(index))) { + index++; + } + return index; } /** @@ -322,10 +410,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 +424,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 +458,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 +558,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) @@ -463,53 +596,15 @@ private static String getCliPath(Path repoRoot) throws IOException { return envPath; } - // Try test harness platform-specific binary (preferred as it has correct - // version) - String os = System.getProperty("os.name").toLowerCase(); - String arch = System.getProperty("os.arch").toLowerCase(); - String platform = os.contains("mac") ? "darwin" : os.contains("win") ? "win32" : "linux"; - String cpuArch = arch.contains("aarch64") || arch.contains("arm64") ? "arm64" : "x64"; - Path platformBinary = repoRoot - .resolve("test/harness/node_modules/@github/copilot-" + platform + "-" + cpuArch + "/copilot"); - if (os.contains("win")) { - platformBinary = repoRoot - .resolve("test/harness/node_modules/@github/copilot-" + platform + "-" + cpuArch + "/copilot.exe"); - } - if (Files.exists(platformBinary)) { - return platformBinary.toString(); - } - - // Try test harness npm-loader.js - Path harnessCliPath = repoRoot.resolve("test/harness/node_modules/@github/copilot/npm-loader.js"); - if (Files.exists(harnessCliPath)) { - return harnessCliPath.toString(); - } - - // Try nodejs installation. As of CLI 1.0.64-1 the @github/copilot package - // is a thin loader; the runnable index.js ships in the installed - // platform-specific package (e.g. @github/copilot-linux-x64). Exactly one - // is installed. Running index.js under Node.js is the documented preferred - // entry point and matches the Go, Python, Rust, and .NET test harnesses. - Path githubModules = repoRoot.resolve("nodejs/node_modules/@github"); - if (Files.isDirectory(githubModules)) { - try (var modules = Files.newDirectoryStream(githubModules, "copilot-*")) { - for (Path module : modules) { - Path indexJs = module.resolve("index.js"); - if (Files.exists(indexJs)) { - return indexJs.toString(); - } - } + try { + return TestUtil.preparePinnedCli(repoRoot); + } catch (Exception e) { + String copilotInPath = findCopilotInPath(); + if (copilotInPath != null) { + return copilotInPath; } + throw new IOException("The pinned CLI could not be prepared and no CLI was found on PATH.", e); } - - // Fallback: try to find 'copilot' in PATH - String copilotInPath = findCopilotInPath(); - if (copilotInPath != null) { - return copilotInPath; - } - - throw new IOException("CLI not found. Either install 'copilot' globally, set COPILOT_CLI_PATH, " - + "or run 'npm install' in the nodejs directory or test/harness directory."); } private static String findCopilotInPath() { diff --git a/java/sdk/src/test/java/com/github/copilot/E2ETestContextTest.java b/java/sdk/src/test/java/com/github/copilot/E2ETestContextTest.java new file mode 100644 index 0000000000..bf307f6547 --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/E2ETestContextTest.java @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +class E2ETestContextTest { + + @Test + void expectedUserPromptsParseFoldedBlockScalars() { + String snapshot = """ + conversations: + - messages: + - role: user + content: First prompt + continued here. + - role: assistant + content: response + - role: user + content: >- + + + Agent completed successfully. + + + """; + + assertEquals( + List.of("First prompt continued here.", + "\nAgent completed successfully.\n"), + E2ETestContext.parseExpectedUserPrompts(snapshot)); + } +} 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 32579ffc4e..46f6741a04 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 78764db0fb..a8319475c6 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 7af60d4d38..909cd14069 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 69% rename from java/src/test/java/com/github/copilot/GitHubTelemetryTest.java rename to java/sdk/src/test/java/com/github/copilot/GitHubTelemetryTest.java index 8e35bd9a92..5a6e378b2a 100644 --- a/java/src/test/java/com/github/copilot/GitHubTelemetryTest.java +++ b/java/sdk/src/test/java/com/github/copilot/GitHubTelemetryTest.java @@ -24,6 +24,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.github.copilot.generated.rpc.GitHubTelemetryNotification; +import com.github.copilot.rpc.ClientInfo; import com.github.copilot.rpc.CopilotClientOptions; import com.github.copilot.rpc.PermissionHandler; import com.github.copilot.rpc.ResumeSessionConfig; @@ -204,6 +205,96 @@ void clientOmitsForwardingWhenNoHandler() throws Exception { } } + @Test + void connectForwardsDeclaredClientInfo() throws Exception { + try (var server = new FakeRuntimeServer(); + var client = new CopilotClient(new CopilotClientOptions().setCliUrl(server.url()) + .setClientInfo(new ClientInfo().setApplicationName("acme-developer-portal") + .setApplicationVersion("2.4.0").setIntegrationName("copilot-assistant") + .setIntegrationVersion("1.5.0")))) { + + client.start().get(15, TimeUnit.SECONDS); + + JsonNode connectParams = server.awaitConnect(); + JsonNode clientInfo = connectParams.path("clientInfo"); + assertEquals(4, clientInfo.size(), "clientInfo should carry only the four declared fields"); + assertEquals("acme-developer-portal", clientInfo.path("editorName").asText()); + assertEquals("2.4.0", clientInfo.path("editorVersion").asText()); + assertEquals("copilot-assistant", clientInfo.path("extensionName").asText()); + assertEquals("1.5.0", clientInfo.path("extensionVersion").asText()); + } + } + + @Test + void connectOmitsClientInfoWhenUnset() throws Exception { + try (var server = new FakeRuntimeServer(); + var client = new CopilotClient(new CopilotClientOptions().setCliUrl(server.url()))) { + + client.start().get(15, TimeUnit.SECONDS); + + JsonNode connectParams = server.awaitConnect(); + assertFalse(connectParams.has("clientInfo"), + "connect request should omit clientInfo when none was declared"); + } + } + + @Test + void connectOmitsEmptyClientInfoFields() throws Exception { + try (var server = new FakeRuntimeServer(); + var client = new CopilotClient(new CopilotClientOptions().setCliUrl(server.url()) + .setClientInfo(new ClientInfo().setApplicationName("example-app")))) { + + client.start().get(15, TimeUnit.SECONDS); + + JsonNode connectParams = server.awaitConnect(); + JsonNode clientInfo = connectParams.path("clientInfo"); + assertEquals("example-app", clientInfo.path("editorName").asText()); + assertFalse(clientInfo.has("editorVersion"), "unset editorVersion should be omitted"); + assertFalse(clientInfo.has("extensionName"), "unset extensionName should be omitted"); + assertFalse(clientInfo.has("extensionVersion"), "unset extensionVersion should be omitted"); + } + } + + @Test + void connectDropsEmptyClientInfoFields() throws Exception { + try (var server = new FakeRuntimeServer(); + var client = new CopilotClient(new CopilotClientOptions().setCliUrl(server.url()) + .setClientInfo(new ClientInfo().setApplicationName("example-app").setApplicationVersion("")))) { + + client.start().get(15, TimeUnit.SECONDS); + + JsonNode connectParams = server.awaitConnect(); + JsonNode clientInfo = connectParams.path("clientInfo"); + assertEquals(1, clientInfo.size(), "clientInfo should carry only the non-empty field"); + assertEquals("example-app", clientInfo.path("editorName").asText()); + assertFalse(clientInfo.has("editorVersion"), "empty editorVersion should be dropped"); + } + } + + @Test + void connectOmitsAllEmptyClientInfo() throws Exception { + try (var server = new FakeRuntimeServer(); + var client = new CopilotClient(new CopilotClientOptions().setCliUrl(server.url()) + .setClientInfo(new ClientInfo().setApplicationName("").setApplicationVersion("") + .setIntegrationName("").setIntegrationVersion("")))) { + + client.start().get(15, TimeUnit.SECONDS); + + JsonNode connectParams = server.awaitConnect(); + assertFalse(connectParams.has("clientInfo"), "connect request should omit an all-empty clientInfo"); + } + } + + @Test + void optionsRetainAndCloneClientInfo() { + var info = new ClientInfo().setApplicationName("example-app"); + var options = new CopilotClientOptions().setClientInfo(info); + assertSame(info, options.getClientInfo()); + + var copy = options.clone(); + assertSame(info, copy.getClientInfo()); + } + @Test void optionsRetainAndCloneTelemetryHandler() { Function> handler = n -> CompletableFuture @@ -259,23 +350,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)); - }); - 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")); + 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.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/sdk/src/test/java/com/github/copilot/GitHubTokenProviderRegistryTest.java b/java/sdk/src/test/java/com/github/copilot/GitHubTokenProviderRegistryTest.java new file mode 100644 index 0000000000..8f2c7d3314 --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/GitHubTokenProviderRegistryTest.java @@ -0,0 +1,74 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import org.junit.jupiter.api.Test; + +import com.github.copilot.rpc.GitHubTokenProviderResult; +import com.github.copilot.rpc.PermissionHandler; +import com.github.copilot.rpc.ResumeSessionConfig; +import com.github.copilot.rpc.SessionConfig; + +class GitHubTokenProviderRegistryTest { + + @Test + void staticTokenAndProviderAreMutuallyExclusive() { + try (var client = new CopilotClient()) { + var create = new SessionConfig().setGitHubToken("static") + .setGitHubTokenProvider( + args -> CompletableFuture.completedFuture(GitHubTokenProviderResult.cancelled())) + .setOnPermissionRequest(PermissionHandler.APPROVE_ALL); + var createError = assertThrows(CompletionException.class, () -> client.createSession(create).join()); + assertInstanceOf(IllegalArgumentException.class, createError.getCause()); + + var resume = new ResumeSessionConfig().setGitHubToken("static") + .setGitHubTokenProvider( + args -> CompletableFuture.completedFuture(GitHubTokenProviderResult.cancelled())) + .setOnPermissionRequest(PermissionHandler.APPROVE_ALL); + var resumeError = assertThrows(CompletionException.class, + () -> client.resumeSession("session", resume).join()); + assertInstanceOf(IllegalArgumentException.class, resumeError.getCause()); + } + } + + @Test + void registrationRollbackAndResumeReplacementAreIsolated() { + var registry = new GitHubTokenProviderRegistry(); + var first = registry.register(args -> CompletableFuture.completedFuture(GitHubTokenProviderResult.cancelled())); + var second = registry + .register(args -> CompletableFuture.completedFuture(GitHubTokenProviderResult.cancelled())); + + assertNotNull(registry.get(first.id())); + assertNotNull(registry.get(second.id())); + first.claim("session-1"); + second.claim("session-1"); + assertNull(registry.get(first.id())); + assertNotNull(registry.get(second.id())); + + first.close(); + assertNotNull(registry.get(second.id())); + second.close(); + assertNull(registry.get(second.id())); + + var retired = registry + .register(args -> CompletableFuture.completedFuture(GitHubTokenProviderResult.cancelled())); + retired.claim("session-2"); + registry.retire("session-2"); + assertNull(registry.get(retired.id())); + + var failed = registry + .register(args -> CompletableFuture.completedFuture(GitHubTokenProviderResult.cancelled())); + failed.close(); + assertNull(registry.get(failed.id())); + } +} 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 100% rename from java/src/test/java/com/github/copilot/HooksTest.java rename to java/sdk/src/test/java/com/github/copilot/HooksTest.java 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 94% rename from java/src/test/java/com/github/copilot/JsonRpcClientTest.java rename to java/sdk/src/test/java/com/github/copilot/JsonRpcClientTest.java index 3491ac8ab4..009f15c200 100644 --- a/java/src/test/java/com/github/copilot/JsonRpcClientTest.java +++ b/java/sdk/src/test/java/com/github/copilot/JsonRpcClientTest.java @@ -115,6 +115,22 @@ void testNotify() throws Exception { } } + @Test + void testCredentialValuesAreRedactedOnlyFromDiagnosticRendering() throws Exception { + String json = """ + {"jsonrpc":"2.0","result":{"accessToken":"secret","nested":{"gitHubToken":"static"}}, + "metadata":{"tokenType":"Bearer"}} + """; + + String rendered = JsonRpcClient.redactCredentialsForLogging(json); + + assertFalse(rendered.contains("secret")); + assertFalse(rendered.contains("static")); + assertEquals("", MAPPER.readTree(rendered).at("/result/accessToken").asText()); + assertEquals("", MAPPER.readTree(rendered).at("/result/nested/gitHubToken").asText()); + assertEquals("Bearer", MAPPER.readTree(rendered).at("/metadata/tokenType").asText()); + } + // ---- isConnected() ---- @Test @@ -133,14 +149,9 @@ void testIsConnectedWithSocketClosed() throws Exception { pair.serverSocket.close(); } - 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(); - } - @Test void testIsConnectedWithProcess() throws Exception { - Process proc = startBlockingProcess(); + Process proc = new TestProcess(); try (var client = JsonRpcClient.fromProcess(proc)) { assertTrue(client.isConnected()); } @@ -148,7 +159,7 @@ void testIsConnectedWithProcess() throws Exception { @Test void testIsConnectedWithProcessDead() throws Exception { - Process proc = startBlockingProcess(); + Process proc = new TestProcess(); var client = JsonRpcClient.fromProcess(proc); proc.destroy(); proc.waitFor(5, TimeUnit.SECONDS); @@ -160,7 +171,7 @@ void testIsConnectedWithProcessDead() throws Exception { @Test void testGetProcessReturnsProcess() throws Exception { - Process proc = startBlockingProcess(); + Process proc = new TestProcess(); try (var client = JsonRpcClient.fromProcess(proc)) { assertSame(proc, client.getProcess()); } 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 0000000000..d6341b26c5 --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/ManagedSettingsTest.java @@ -0,0 +1,98 @@ +/*--------------------------------------------------------------------------------------------- + * 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.rpc.DisableBypassPermissionsModes; +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(DisableBypassPermissionsModes.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 acceptsFutureBypassPermissionsModes() throws Exception { + var permissions = new ManagedSettingsPermissions().setDisableBypassPermissionsMode("future-fail-closed-mode"); + var json = new ObjectMapper().writeValueAsString(permissions); + + assertTrue(json.contains("\"disableBypassPermissionsMode\":\"future-fail-closed-mode\"")); + } + + @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 99% rename from java/src/test/java/com/github/copilot/McpAndAgentsTest.java rename to java/sdk/src/test/java/com/github/copilot/McpAndAgentsTest.java index f39e56eab3..06d9dca39e 100644 --- a/java/src/test/java/com/github/copilot/McpAndAgentsTest.java +++ b/java/sdk/src/test/java/com/github/copilot/McpAndAgentsTest.java @@ -131,7 +131,6 @@ void testShouldAcceptMcpServerConfigurationOnSessionResume() throws Exception { CopilotSession session1 = client .createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get(); String sessionId = session1.getSessionId(); - session1.sendAndWait(new MessageOptions().setPrompt("What is 1+1?")).get(60, TimeUnit.SECONDS); // Resume with MCP servers var mcpServers = createTestMcpServers("test-server"); 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 100% rename from java/src/test/java/com/github/copilot/ModeHandlersTest.java rename to java/sdk/src/test/java/com/github/copilot/ModeHandlersTest.java 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/src/test/java/com/github/copilot/PermissionRequestResultTest.java b/java/sdk/src/test/java/com/github/copilot/PermissionRequestResultTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/PermissionRequestResultTest.java rename to java/sdk/src/test/java/com/github/copilot/PermissionRequestResultTest.java 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 88% rename from java/src/test/java/com/github/copilot/RpcHandlerDispatcherTest.java rename to java/sdk/src/test/java/com/github/copilot/RpcHandlerDispatcherTest.java index 76c2d41b29..1cb3ae82ec 100644 --- a/java/src/test/java/com/github/copilot/RpcHandlerDispatcherTest.java +++ b/java/sdk/src/test/java/com/github/copilot/RpcHandlerDispatcherTest.java @@ -15,6 +15,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; import org.junit.jupiter.api.AfterEach; @@ -32,6 +33,9 @@ import com.github.copilot.rpc.ToolDefinition; import com.github.copilot.rpc.ToolResultObject; import com.github.copilot.rpc.UserInputResponse; +import com.github.copilot.generated.rpc.GitHubTokenAcquireReason; +import com.github.copilot.rpc.GitHubTokenProviderArgs; +import com.github.copilot.rpc.GitHubTokenProviderResult; /** * Unit tests for {@link RpcHandlerDispatcher} focusing on coverage gaps @@ -51,6 +55,7 @@ class RpcHandlerDispatcherTest { private RpcHandlerDispatcher dispatcher; private InputStream responseStream; private Map> handlers; + private GitHubTokenProviderRegistry gitHubTokenProviders; @BeforeEach void setup() throws Exception { @@ -66,7 +71,8 @@ void setup() throws Exception { sessions = new ConcurrentHashMap<>(); lifecycleEvents = new CopyOnWriteArrayList<>(); - dispatcher = new RpcHandlerDispatcher(sessions, lifecycleEvents::add, null); + gitHubTokenProviders = new GitHubTokenProviderRegistry(); + dispatcher = new RpcHandlerDispatcher(sessions, lifecycleEvents::add, null, gitHubTokenProviders); dispatcher.registerHandlers(rpc); // Extract the registered handlers via reflection so we can invoke them directly @@ -119,6 +125,57 @@ private CopilotSession createSession(String sessionId) { return session; } + @Test + void gitHubTokenCallbackMapsRequestAndTokenResult() throws Exception { + AtomicReference received = new AtomicReference<>(); + var registration = gitHubTokenProviders.register(args -> { + received.set(args); + return CompletableFuture.completedFuture(GitHubTokenProviderResult.token("secret", 28_800, "bearer")); + }); + ObjectNode params = MAPPER.createObjectNode(); + params.put("registrationId", registration.id()); + params.put("host", "github.example"); + params.put("sessionId", "session-1"); + params.put("reason", "initial"); + + invokeHandler("gitHubToken.getToken", "80", params); + + JsonNode response = readResponse(); + assertEquals("token", response.at("/result/kind").asText()); + assertEquals("secret", response.at("/result/accessToken").asText()); + assertEquals(28_800, response.at("/result/expiresIn").asLong()); + assertEquals("github.example", received.get().host()); + assertEquals("session-1", received.get().sessionId()); + assertEquals(GitHubTokenAcquireReason.INITIAL, received.get().reason()); + } + + @Test + void gitHubTokenCallbackPreservesCancellationAndErrors() throws Exception { + var cancelled = gitHubTokenProviders + .register(args -> CompletableFuture.completedFuture(GitHubTokenProviderResult.cancelled())); + ObjectNode cancelledParams = MAPPER.createObjectNode(); + cancelledParams.put("registrationId", cancelled.id()); + cancelledParams.put("host", "github.com"); + cancelledParams.put("reason", "refresh"); + invokeHandler("gitHubToken.getToken", "81", cancelledParams); + assertEquals("cancelled", readResponse().at("/result/kind").asText()); + + var failed = gitHubTokenProviders.register( + args -> CompletableFuture.failedFuture(new IllegalStateException("credential service unavailable"))); + ObjectNode failedParams = cancelledParams.deepCopy(); + failedParams.put("registrationId", failed.id()); + invokeHandler("gitHubToken.getToken", "82", failedParams); + JsonNode error = readResponse(); + assertEquals(-32603, error.at("/error/code").asInt()); + assertTrue(error.at("/error/message").asText().contains("credential service unavailable")); + + failedParams.put("registrationId", "unknown"); + invokeHandler("gitHubToken.getToken", "83", failedParams); + JsonNode unknown = readResponse(); + assertEquals(-32603, unknown.at("/error/code").asInt()); + assertTrue(unknown.at("/error/message").asText().contains("Unknown GitHub token provider registration")); + } + // ===== session.event tests ===== @Test 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 99% rename from java/src/test/java/com/github/copilot/RpcServerE2ETest.java rename to java/sdk/src/test/java/com/github/copilot/RpcServerE2ETest.java index 2393f334b2..6c9753025a 100644 --- a/java/src/test/java/com/github/copilot/RpcServerE2ETest.java +++ b/java/sdk/src/test/java/com/github/copilot/RpcServerE2ETest.java @@ -135,7 +135,7 @@ void testShouldCallRpcModelsListWithTypedResult() throws Exception { var result = client.getRpc().models.list().get(TIMEOUT_SECONDS, TimeUnit.SECONDS); assertNotNull(result.models()); - assertTrue(result.models().stream().anyMatch(model -> "claude-sonnet-4.5".equals(model.id()))); + assertTrue(result.models().stream().anyMatch(model -> "claude-sonnet-5".equals(model.id()))); result.models().forEach(model -> { assertFalse(model.id().isBlank()); assertFalse(model.name().isBlank()); 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 98% rename from java/src/test/java/com/github/copilot/RpcServerMiscE2ETest.java rename to java/sdk/src/test/java/com/github/copilot/RpcServerMiscE2ETest.java index 1db801d841..3b06e4d1f7 100644 --- a/java/src/test/java/com/github/copilot/RpcServerMiscE2ETest.java +++ b/java/sdk/src/test/java/com/github/copilot/RpcServerMiscE2ETest.java @@ -106,7 +106,8 @@ void testShouldLoginListGetCurrentAuthAndLogoutAccount() throws Exception { users.stream().filter(user -> accountLogin(user).equals(login)).findFirst() .ifPresent(user -> assertEquals(token, user.token())); - var logout = client.getRpc().account.logout(new AccountLogoutParams(authInfo)).get(30, TimeUnit.SECONDS); + var logout = client.getRpc().account.logout(new AccountLogoutParams(null, authInfo)).get(30, + TimeUnit.SECONDS); assertFalse(logout.hasMoreUsers()); assertNull(client.getRpc().account.getCurrentAuth().get(30, TimeUnit.SECONDS).authInfo()); } 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 95% rename from java/src/test/java/com/github/copilot/RpcSessionStateExtrasE2ETest.java rename to java/sdk/src/test/java/com/github/copilot/RpcSessionStateExtrasE2ETest.java index 5022d2a563..f7fc58d429 100644 --- a/java/src/test/java/com/github/copilot/RpcSessionStateExtrasE2ETest.java +++ b/java/sdk/src/test/java/com/github/copilot/RpcSessionStateExtrasE2ETest.java @@ -59,14 +59,13 @@ void testShouldAddByokProviderAndModelAtRuntime() throws Exception { ProviderConfigWireApi.COMPLETIONS, null, "https://models.example.test/v1", "provider-key", null, null, Map.of("x-provider", "java"), null)), List.of(new ProviderModelConfig("small", "java-e2e-provider", null, null, "Java Added Model", - 4096.0, null, null, null)))) + 4096L, null, null, null)))) .get(30, TimeUnit.SECONDS); assertEquals(1, result.models().size()); var selectionId = "java-e2e-provider/small"; - session.getRpc().model - .switchTo(new SessionModelSwitchToParams(null, selectionId, null, null, null, null, null, null)) - .get(30, TimeUnit.SECONDS); + session.getRpc().model.switchTo(new SessionModelSwitchToParams(null, selectionId, null, null, null, + null, null, null, null, null, null, null, null, null, null)).get(30, TimeUnit.SECONDS); var current = session.getRpc().model.getCurrent().get(30, TimeUnit.SECONDS); assertEquals(selectionId, current.modelId()); } @@ -143,7 +142,7 @@ void testShouldUpdateAndClearLiveSubagentSettings() throws Exception { session.getRpc().tools.updateSubagentSettings(new SessionToolsUpdateSubagentSettingsParams(null, new SessionToolsUpdateSubagentSettingsParams.SessionToolsUpdateSubagentSettingsParamsSubagents( Map.of("general-purpose", - new SubagentSettingsEntry("gpt-5-mini", "low", + new SubagentSettingsEntry("gpt-5-mini", null, "low", SubagentSettingsEntryContextTier.LONG_CONTEXT)), List.of("legacy-agent"), null, null))) .get(30, TimeUnit.SECONDS); 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 99% rename from java/src/test/java/com/github/copilot/RpcWrappersTest.java rename to java/sdk/src/test/java/com/github/copilot/RpcWrappersTest.java index 1f1785cba7..3bb9001f35 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,8 @@ 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, null); + var switchParams = new SessionModelSwitchToParams(null, "gpt-5", null, null, null, null, null, null, null, null, + null, null, null, null, null); session.model.switchTo(switchParams); assertEquals(1, stub.calls.size()); 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/sdk/src/test/java/com/github/copilot/SessionAutoTierEventTest.java b/java/sdk/src/test/java/com/github/copilot/SessionAutoTierEventTest.java new file mode 100644 index 0000000000..5213cdbb8d --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/SessionAutoTierEventTest.java @@ -0,0 +1,67 @@ +/*--------------------------------------------------------------------------------------------- + * 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.assertNull; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import com.github.copilot.generated.AutoTier; +import com.github.copilot.generated.SessionEvent; +import com.github.copilot.generated.SessionResumeEvent; +import com.github.copilot.generated.SessionStartEvent; + +/** + * Verifies auto routing preferences on generated session lifecycle events. + */ +class SessionAutoTierEventTest { + + private static final ObjectMapper MAPPER = JsonRpcClient.getObjectMapper(); + + @ParameterizedTest + @CsvSource({"session.start,EFFICIENCY,efficiency", "session.start,BALANCE,balance", + "session.start,INTELLIGENCE,intelligence", "session.resume,EFFICIENCY,efficiency", + "session.resume,BALANCE,balance", "session.resume,INTELLIGENCE,intelligence"}) + void canonicalAutoTierRoundTrips(String type, AutoTier tier, String value) throws Exception { + String json = """ + {"type":"%s","data":{"selectedModel":"auto","autoTier":"%s"}} + """.formatted(type, value); + + var event = MAPPER.readValue(json, SessionEvent.class); + assertEquals(tier, autoTier(event, type)); + String serialized = MAPPER.writeValueAsString(event); + assertEquals(value, MAPPER.readTree(serialized).path("data").path("autoTier").asText()); + assertEquals(tier, autoTier(MAPPER.readValue(serialized, SessionEvent.class), type)); + } + + @ParameterizedTest + @ValueSource(strings = {"session.start", "session.resume"}) + void missingOrNullAutoTierRemainsOptional(String type) throws Exception { + for (String data : new String[]{"{}", "{\"autoTier\":null}"}) { + String json = """ + {"type":"%s","data":%s} + """.formatted(type, data); + + var event = MAPPER.readValue(json, SessionEvent.class); + assertNull(autoTier(event, type)); + var serialized = MAPPER.readTree(MAPPER.writeValueAsString(event)); + assertFalse(serialized.path("data").has("autoTier")); + } + } + + private static AutoTier autoTier(SessionEvent event, String type) { + if ("session.start".equals(type)) { + return assertInstanceOf(SessionStartEvent.class, event).getData().autoTier(); + } + return assertInstanceOf(SessionResumeEvent.class, event).getData().autoTier(); + } +} 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 98% rename from java/src/test/java/com/github/copilot/SessionConfigE2ETest.java rename to java/sdk/src/test/java/com/github/copilot/SessionConfigE2ETest.java index 925fd6d873..e786bda994 100644 --- a/java/src/test/java/com/github/copilot/SessionConfigE2ETest.java +++ b/java/sdk/src/test/java/com/github/copilot/SessionConfigE2ETest.java @@ -125,7 +125,7 @@ void testShouldForwardProviderWireModel() throws Exception { try (CopilotClient client = ctx.createClient()) { CopilotSession session = client - .createSession(new SessionConfig().setModel("claude-sonnet-4.5") + .createSession(new SessionConfig().setModel("claude-sonnet-5") .setProvider(new ProviderConfig().setType("openai").setBaseUrl(ctx.getProxyUrl()) .setApiKey("test-provider-key").setWireModel("test-wire-model") .setMaxOutputTokens(1024)) @@ -149,7 +149,7 @@ void testShouldUseProviderModelIdAsWireModel() throws Exception { try (CopilotClient client = ctx.createClient()) { CopilotSession session = client.createSession(new SessionConfig() .setProvider(new ProviderConfig().setType("openai").setBaseUrl(ctx.getProxyUrl()) - .setApiKey("test-provider-key").setModelId("claude-sonnet-4.5")) + .setApiKey("test-provider-key").setModelId("claude-sonnet-5")) .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get(); session.sendAndWait(new MessageOptions().setPrompt("What is 1+1?")).get(30, TimeUnit.SECONDS); @@ -158,7 +158,7 @@ void testShouldUseProviderModelIdAsWireModel() throws Exception { assertFalse(exchanges.isEmpty(), "Should have at least one exchange"); @SuppressWarnings("unchecked") Map request = (Map) exchanges.get(0).get("request"); - assertEquals("claude-sonnet-4.5", request.get("model")); + assertEquals("claude-sonnet-5", request.get("model")); } } @@ -272,7 +272,7 @@ void testShouldEnableCitationsForAnthropicFileAttachmentsOnCreate() throws Excep var handler = new CopilotRequestTestSupport.RecordingRequestHandler(SYNTHETIC_TEXT); try (CopilotClient client = newLlmClient(ctx, handler)) { - CopilotSession session = client.createSession(new SessionConfig().setModel("claude-sonnet-4.5") + CopilotSession session = client.createSession(new SessionConfig().setModel("claude-sonnet-5") .setEnableCitations(true).setProvider(createAnthropicProvider()) .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get(); @@ -296,7 +296,7 @@ void testShouldEnableCitationsForAnthropicFileAttachmentsOnResume() throws Excep CopilotSession session1 = client .createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get(); CopilotSession session2 = client.resumeSession(session1.getSessionId(), - new ResumeSessionConfig().setModel("claude-sonnet-4.5").setEnableCitations(true) + new ResumeSessionConfig().setModel("claude-sonnet-5").setEnableCitations(true) .setProvider(createAnthropicProvider()) .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)) .get(); @@ -388,7 +388,7 @@ private static BlobAttachment createPdfAttachment() { private static ProviderConfig createAnthropicProvider() { return new ProviderConfig().setType("anthropic").setBaseUrl("https://anthropic-citations.invalid/v1") - .setApiKey("test-provider-key").setModelId("claude-sonnet-4.5").setWireModel("claude-sonnet-4.5"); + .setApiKey("test-provider-key").setModelId("claude-sonnet-5").setWireModel("claude-sonnet-5"); } private static String singleInferenceRequestBody(CopilotRequestTestSupport.RecordingRequestHandler handler) { 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 fc978ed65b..8d9b70a348 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 = """ 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 94% rename from java/src/test/java/com/github/copilot/SessionEventHandlingTest.java rename to java/sdk/src/test/java/com/github/copilot/SessionEventHandlingTest.java index bd38d4962e..b75e710720 100644 --- a/java/src/test/java/com/github/copilot/SessionEventHandlingTest.java +++ b/java/sdk/src/test/java/com/github/copilot/SessionEventHandlingTest.java @@ -5,11 +5,14 @@ package com.github.copilot; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; import java.io.Closeable; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -24,7 +27,10 @@ import com.github.copilot.generated.SessionEvent; import com.github.copilot.generated.AssistantMessageEvent; import com.github.copilot.generated.SessionIdleEvent; +import com.github.copilot.generated.SessionMode; import com.github.copilot.generated.SessionStartEvent; +import com.github.copilot.rpc.MessageOptions; +import com.github.copilot.rpc.SendMessageResponse; /** * Unit tests for session event handling API. @@ -85,6 +91,33 @@ void testTypedEventHandler() { assertEquals("Second message", receivedMessages.get(1).getData().content()); } + @Test + void testSendAndWaitSkipsAutopilotContinuationIdle() throws Exception { + var rpc = mock(JsonRpcClient.class); + when(rpc.invoke(eq("session.send"), any(), eq(SendMessageResponse.class))) + .thenReturn(CompletableFuture.completedFuture(new SendMessageResponse("message-1"))); + when(rpc.invoke(eq("session.destroy"), any(), eq(Void.class))) + .thenReturn(CompletableFuture.completedFuture(null)); + session = new CopilotSession("test-session-id", rpc); + + try { + var pending = session.sendAndWait(new MessageOptions().setPrompt("keep going")); + + dispatchEvent(createAssistantMessageEvent("intermediate")); + dispatchEvent(createSessionIdleEvent(SessionMode.AUTOPILOT)); + assertFalse(pending.isDone(), "Autopilot continuation idle must not complete sendAndWait"); + + dispatchEvent(createAssistantMessageEvent("final")); + dispatchEvent(createSessionIdleEvent(SessionMode.INTERACTIVE)); + + var result = pending.get(5, TimeUnit.SECONDS); + assertNotNull(result); + assertEquals("final", result.getData().content()); + } finally { + session.close(); + } + } + @Test void testMultipleTypedHandlers() { var messages = new ArrayList(); @@ -180,7 +213,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, null, null)); dispatchEvent(startEvent); AssistantMessageEvent msgEvent = createAssistantMessageEvent("Test content"); @@ -857,7 +890,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, null, null); event.setData(data); return event; } @@ -865,7 +898,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, null, null, null, null, null); event.setData(data); return event; } @@ -873,4 +906,10 @@ private AssistantMessageEvent createAssistantMessageEvent(String content) { private SessionIdleEvent createSessionIdleEvent() { return new SessionIdleEvent(); } + + private SessionIdleEvent createSessionIdleEvent(SessionMode mode) { + var event = new SessionIdleEvent(); + event.setData(new SessionIdleEvent.SessionIdleEventData(null, mode)); + 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 161839a533..dad75db528 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 100% rename from java/src/test/java/com/github/copilot/SessionHandlerTest.java rename to java/sdk/src/test/java/com/github/copilot/SessionHandlerTest.java 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 89% rename from java/src/test/java/com/github/copilot/SessionRequestBuilderTest.java rename to java/sdk/src/test/java/com/github/copilot/SessionRequestBuilderTest.java index 54773662c5..edc40175d0 100644 --- a/java/src/test/java/com/github/copilot/SessionRequestBuilderTest.java +++ b/java/sdk/src/test/java/com/github/copilot/SessionRequestBuilderTest.java @@ -13,6 +13,7 @@ import org.junit.jupiter.api.Test; import com.github.copilot.generated.rpc.SessionLimitsConfig; +import com.github.copilot.rpc.AskUserVariant; import com.github.copilot.rpc.AutoModeSwitchResponse; import com.github.copilot.rpc.CloudSessionOptions; import com.github.copilot.rpc.CloudSessionRepository; @@ -26,6 +27,7 @@ import com.github.copilot.rpc.ExitPlanModeResult; import com.github.copilot.rpc.ExpConfigEntry; import com.github.copilot.rpc.GitHubMcpToolConfig; +import com.github.copilot.rpc.GitHubTokenProviderResult; import com.github.copilot.rpc.LargeToolOutputConfig; import com.github.copilot.rpc.MemoryConfiguration; import com.github.copilot.rpc.ResumeSessionConfig; @@ -56,6 +58,63 @@ void testBuildCreateRequestNullConfig() { assertEquals("direct", request.getEnvValueMode(), "envValueMode should be 'direct' even for null config"); } + @Test + void testGitHubTokenProviderRegistrationWireFieldHasExactCasing() throws Exception { + var create = SessionRequestBuilder.buildCreateRequest(new SessionConfig(), "create-session"); + create.setGitHubTokenProviderRegistrationId("create-registration"); + var resume = SessionRequestBuilder.buildResumeRequest("resume-session", new ResumeSessionConfig()); + resume.setGitHubTokenProviderRegistrationId("resume-registration"); + var mapper = JsonRpcClient.getObjectMapper(); + + assertEquals("create-registration", + mapper.readTree(mapper.writeValueAsBytes(create)).path("gitHubTokenProviderRegistrationId").asText()); + assertEquals("resume-registration", + mapper.readTree(mapper.writeValueAsBytes(resume)).path("gitHubTokenProviderRegistrationId").asText()); + assertFalse(mapper.readTree(mapper.writeValueAsBytes(create)).has("gitHubToken")); + } + + @Test + void testGitHubTokenProviderResultRedactsToken() { + var result = GitHubTokenProviderResult.token("do-not-print", 28_800); + assertFalse(result.toString().contains("do-not-print")); + } + + @Test + void askUserVariantIsForwardedAndSerializedForCreateAndColdResume() throws Exception { + var createRequest = SessionRequestBuilder.buildCreateRequest( + new SessionConfig().setAskUserVariant(AskUserVariant.ELICITATION), "create-session"); + var resumeRequest = SessionRequestBuilder.buildResumeRequest("resume-session", + new ResumeSessionConfig().setAskUserVariant(AskUserVariant.LEGACY)); + var mapper = JsonRpcClient.getObjectMapper(); + + assertEquals(AskUserVariant.ELICITATION, createRequest.getAskUserVariant()); + assertEquals("elicitation", + mapper.readTree(mapper.writeValueAsBytes(createRequest)).path("askUserVariant").asText()); + assertEquals(AskUserVariant.LEGACY, resumeRequest.getAskUserVariant()); + assertEquals("legacy", + mapper.readTree(mapper.writeValueAsBytes(resumeRequest)).path("askUserVariant").asText()); + } + + @Test + void askUserVariantDefaultsToOmittedLegacyBehavior() throws Exception { + var mapper = JsonRpcClient.getObjectMapper(); + var createRequest = SessionRequestBuilder.buildCreateRequest(new SessionConfig(), "create-session"); + var resumeRequest = SessionRequestBuilder.buildResumeRequest("resume-session", new ResumeSessionConfig()); + + assertNull(createRequest.getAskUserVariant()); + assertFalse(mapper.readTree(mapper.writeValueAsBytes(createRequest)).has("askUserVariant")); + assertNull(resumeRequest.getAskUserVariant()); + assertFalse(mapper.readTree(mapper.writeValueAsBytes(resumeRequest)).has("askUserVariant")); + } + + @Test + void askUserVariantAcceptsOnlySupportedWireValues() { + assertEquals(AskUserVariant.LEGACY, AskUserVariant.fromValue("legacy")); + assertEquals(AskUserVariant.ELICITATION, AskUserVariant.fromValue("elicitation")); + assertThrows(IllegalArgumentException.class, () -> AskUserVariant.fromValue("ELICITATION")); + assertThrows(IllegalArgumentException.class, () -> AskUserVariant.fromValue("unsupported")); + } + @Test void testBuildCreateRequestHooksNonNullButEmpty() { // Hooks object exists but hasHooks() returns false @@ -169,13 +228,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 @@ -225,12 +288,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()); } @@ -430,12 +494,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()); } @@ -460,13 +525,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 @@ -1023,6 +1092,26 @@ void testBuildRequestsOmitExpAssignmentsWhenUnset() throws Exception { assertFalse(resumeJson.contains("\"expAssignments\""), "expAssignments should be omitted when null"); } + @Test + void testBuildRequestsPropagateAndSerializeFeatureFlags() throws Exception { + var mapper = JsonRpcClient.getObjectMapper(); + var flags = Map.of("ENABLED_TEST_FLAG", true, "DISABLED_TEST_FLAG", false); + + var createConfig = new SessionConfig().setFeatureFlags(flags); + CreateSessionRequest createRequest = SessionRequestBuilder.buildCreateRequest(createConfig, "session-1"); + assertEquals(flags, createRequest.getFeatureFlags()); + var createJson = mapper.readTree(mapper.writeValueAsString(createRequest)); + assertTrue(createJson.path("featureFlags").path("ENABLED_TEST_FLAG").asBoolean()); + assertFalse(createJson.path("featureFlags").path("DISABLED_TEST_FLAG").asBoolean()); + + var resumeConfig = new ResumeSessionConfig().setFeatureFlags(flags); + ResumeSessionRequest resumeRequest = SessionRequestBuilder.buildResumeRequest("session-1", resumeConfig); + assertEquals(flags, resumeRequest.getFeatureFlags()); + var resumeJson = mapper.readTree(mapper.writeValueAsString(resumeRequest)); + assertTrue(resumeJson.path("featureFlags").path("ENABLED_TEST_FLAG").asBoolean()); + assertFalse(resumeJson.path("featureFlags").path("DISABLED_TEST_FLAG").asBoolean()); + } + @Test void testClonePreservesAndForwardsExpAssignments() throws Exception { var mapper = JsonRpcClient.getObjectMapper(); 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 94% rename from java/src/test/java/com/github/copilot/SlashCommandsIT.java rename to java/sdk/src/test/java/com/github/copilot/SlashCommandsIT.java index 634c0bad9b..998d533bb3 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; @@ -90,9 +93,9 @@ void listCommandsReturnsAtLeast20() throws Exception { @Test void autoPilotToggle() throws Exception { SlashCommandInvocationResult first = session.getRpc().commands - .invoke(new SessionCommandsInvokeParams(null, "autopilot", null)).get(15, TimeUnit.SECONDS); + .invoke(new SessionCommandsInvokeParams(null, "autopilot", null, null)).get(15, TimeUnit.SECONDS); SlashCommandInvocationResult second = session.getRpc().commands - .invoke(new SessionCommandsInvokeParams(null, "autopilot", null)).get(15, TimeUnit.SECONDS); + .invoke(new SessionCommandsInvokeParams(null, "autopilot", null, null)).get(15, TimeUnit.SECONDS); String firstOutput = extractDisplayText(first); String secondOutput = extractDisplayText(second); @@ -125,7 +128,7 @@ void autoPilotToggle() throws Exception { @Test void listDirs() throws Exception { SlashCommandInvocationResult result = session.getRpc().commands - .invoke(new SessionCommandsInvokeParams(null, "list-dirs", null)).get(15, TimeUnit.SECONDS); + .invoke(new SessionCommandsInvokeParams(null, "list-dirs", null, null)).get(15, TimeUnit.SECONDS); String output = extractDisplayText(result); assertTrue(Pattern.compile("(?s)^.*Total: [0-9]+ directories.*$").matcher(output).matches(), @@ -144,19 +147,20 @@ void addDir() throws Exception { String addDirPathString = addDirPath.toString(); SlashCommandInvocationResult beforeListResult = session.getRpc().commands - .invoke(new SessionCommandsInvokeParams(null, "list-dirs", null)).get(15, TimeUnit.SECONDS); + .invoke(new SessionCommandsInvokeParams(null, "list-dirs", null, null)).get(15, TimeUnit.SECONDS); String beforeListOutput = extractDisplayText(beforeListResult); System.out.println("/list-dirs (before /add-dir) result:"); System.out.println(beforeListOutput); SlashCommandInvocationResult addDirResult = session.getRpc().commands - .invoke(new SessionCommandsInvokeParams(null, "add-dir", addDirPathString)).get(15, TimeUnit.SECONDS); + .invoke(new SessionCommandsInvokeParams(null, "add-dir", addDirPathString, null)) + .get(15, TimeUnit.SECONDS); String addDirOutput = extractDisplayText(addDirResult); System.out.println("/add-dir result:"); System.out.println(addDirOutput); SlashCommandInvocationResult afterListResult = session.getRpc().commands - .invoke(new SessionCommandsInvokeParams(null, "list-dirs", null)).get(15, TimeUnit.SECONDS); + .invoke(new SessionCommandsInvokeParams(null, "list-dirs", null, null)).get(15, TimeUnit.SECONDS); String afterListOutput = extractDisplayText(afterListResult); System.out.println("/list-dirs (after /add-dir) result:"); System.out.println(afterListOutput); @@ -168,7 +172,7 @@ void addDir() throws Exception { @Test void usage() throws Exception { SlashCommandInvocationResult result = session.getRpc().commands - .invoke(new SessionCommandsInvokeParams(null, "usage", null)).get(15, TimeUnit.SECONDS); + .invoke(new SessionCommandsInvokeParams(null, "usage", null, null)).get(15, TimeUnit.SECONDS); String output = extractDisplayText(result); assertTrue(Pattern.compile("(?s)^.*Changes:.*$").matcher(output).matches(), 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 bc4999a16f..3701cf9c13 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) - .setModel("gpt-5.4").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/sdk/src/test/java/com/github/copilot/TestProcess.java b/java/sdk/src/test/java/com/github/copilot/TestProcess.java new file mode 100644 index 0000000000..f7187ae676 --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/TestProcess.java @@ -0,0 +1,65 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +final class TestProcess extends Process { + + private final CountDownLatch terminated = new CountDownLatch(1); + + @Override + public OutputStream getOutputStream() { + return OutputStream.nullOutputStream(); + } + + @Override + public InputStream getInputStream() { + return InputStream.nullInputStream(); + } + + @Override + public InputStream getErrorStream() { + return InputStream.nullInputStream(); + } + + @Override + public int waitFor() throws InterruptedException { + terminated.await(); + return 0; + } + + @Override + public boolean waitFor(long timeout, TimeUnit unit) throws InterruptedException { + return terminated.await(timeout, unit); + } + + @Override + public int exitValue() { + if (isAlive()) { + throw new IllegalThreadStateException("Process has not exited"); + } + return 0; + } + + @Override + public void destroy() { + terminated.countDown(); + } + + @Override + public Process destroyForcibly() { + destroy(); + return this; + } + + @Override + public boolean isAlive() { + return terminated.getCount() > 0; + } +} 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 66% rename from java/src/test/java/com/github/copilot/TestUtil.java rename to java/sdk/src/test/java/com/github/copilot/TestUtil.java index 23bb53e493..8e90c2f48f 100644 --- a/java/src/test/java/com/github/copilot/TestUtil.java +++ b/java/sdk/src/test/java/com/github/copilot/TestUtil.java @@ -5,9 +5,12 @@ package com.github.copilot; import java.io.BufferedReader; +import java.io.File; import java.io.InputStreamReader; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.regex.Pattern; /** * Shared test utilities for locating the Copilot CLI binary and other @@ -37,10 +40,9 @@ public static String tempPath(String filename) { * Resolution order: *

    *
  1. Use the {@code COPILOT_CLI_PATH} environment variable when set.
  2. + *
  3. Prepare the release pinned by {@code nodejs/package.json}.
  4. *
  5. Otherwise search the system PATH using {@code where.exe} (Windows) or * {@code which} (Linux/macOS).
  6. - *
  7. Walk parent directories looking for - * {@code nodejs/node_modules/@github/copilot/npm-loader.js}.
  8. *
* *

@@ -60,43 +62,54 @@ static String findCliPath() { return envPath; } - String copilotInPath = findCopilotInPath(); - if (copilotInPath != null) { - return copilotInPath; - } - - // Walk parent directories looking for the CLI in the test harness or nodejs - // installation. Mirrors the resolution order in E2ETestContext.getCliPath(). - String os = System.getProperty("os.name").toLowerCase(); - String arch = System.getProperty("os.arch").toLowerCase(); - String platform = os.contains("mac") ? "darwin" : os.contains("win") ? "win32" : "linux"; - String cpuArch = arch.contains("aarch64") || arch.contains("arm64") ? "arm64" : "x64"; - String binaryName = os.contains("win") ? "copilot.exe" : "copilot"; - Path current = Paths.get(System.getProperty("user.dir")); while (current != null) { - // Test harness platform-specific binary - Path platformBinary = current.resolve( - "test/harness/node_modules/@github/copilot-" + platform + "-" + cpuArch + "/" + binaryName); - if (platformBinary.toFile().exists()) { - return platformBinary.toString(); + if (current.resolve("nodejs/package.json").toFile().exists()) { + try { + return preparePinnedCli(current); + } catch (Exception preparationFailed) { + break; + } } + current = current.getParent(); + } - // Test harness npm-loader.js - Path npmLoader = current.resolve("test/harness/node_modules/@github/copilot/npm-loader.js"); - if (npmLoader.toFile().exists()) { - return npmLoader.toString(); - } + return findCopilotInPath(); + } - // nodejs installation (thin loader; resolves the platform-specific - // CLI package internally) - Path cliPath = current.resolve("nodejs/node_modules/@github/copilot/npm-loader.js"); - if (cliPath.toFile().exists()) { - return cliPath.toString(); - } - current = current.getParent(); + static String preparePinnedCli(Path repoRoot) throws Exception { + var nodePath = findExecutableInPath("node"); + if (nodePath == null) { + throw new IllegalStateException("Node.js was not found in PATH"); + } + var process = new ProcessBuilder(nodePath, "node_modules/tsx/dist/cli.mjs", "scripts/prepare-runtime.ts", + "--print-path").directory(repoRoot.resolve("nodejs").toFile()).redirectErrorStream(true).start(); + String output; + try (var reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + output = reader.lines().reduce((first, second) -> second).orElse("").trim(); + } + int exitCode = process.waitFor(); + if (exitCode != 0 || output.isEmpty()) { + throw new IllegalStateException("Failed to prepare the pinned Copilot CLI: " + output); + } + return output; + } + + private static String findExecutableInPath(String name) { + var pathValue = System.getenv("PATH"); + if (pathValue == null || pathValue.isEmpty()) { + return null; } + var windows = System.getProperty("os.name").toLowerCase().contains("win"); + var fileName = windows ? name + ".exe" : name; + for (var directory : pathValue.split(Pattern.quote(File.pathSeparator))) { + var unquotedDirectory = directory.replaceAll("^\"|\"$", ""); + var candidate = Paths.get(unquotedDirectory, fileName).toAbsolutePath().normalize(); + if (Files.isRegularFile(candidate) && (windows || Files.isExecutable(candidate))) { + return candidate.toString(); + } + } return null; } 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 88% rename from java/src/test/java/com/github/copilot/UpdateSessionOptionsForModeTest.java rename to java/sdk/src/test/java/com/github/copilot/UpdateSessionOptionsForModeTest.java index d02ea3097d..24b4257a52 100644 --- a/java/src/test/java/com/github/copilot/UpdateSessionOptionsForModeTest.java +++ b/java/sdk/src/test/java/com/github/copilot/UpdateSessionOptionsForModeTest.java @@ -10,6 +10,7 @@ import java.net.ServerSocket; import java.net.Socket; import java.nio.charset.StandardCharsets; +import java.util.List; import org.junit.jupiter.api.Test; @@ -144,6 +145,8 @@ void copilotCliMode_skipCustomInstructionsSet_patchContainsOnlyThatField() throw assertTrue(pair.lastParams.path("manageScheduleEnabled").isMissingNode(), "manageScheduleEnabled should be absent"); assertTrue(pair.lastParams.path("installedPlugins").isMissingNode(), "installedPlugins should be absent"); + assertTrue(pair.lastParams.path("includedBuiltinSkills").isMissingNode(), + "includedBuiltinSkills should be absent in COPILOT_CLI mode"); client.close(); } } @@ -154,13 +157,14 @@ void copilotCliMode_allFieldsSet_allPropagated() throws Exception { var session = new CopilotSession("sess-1", pair.rpcClient); var client = new CopilotClient(new CopilotClientOptions().setAutoStart(false)); - client.updateSessionOptionsForMode(session, false, true, true, false).get(); + client.updateSessionOptionsForMode(session, false, true, true, false, List.of("code-review")).get(); assertEquals("session.options.update", pair.lastMethod); assertFalse(pair.lastParams.get("skipCustomInstructions").asBoolean()); assertTrue(pair.lastParams.get("customAgentsLocalOnly").asBoolean()); assertTrue(pair.lastParams.get("coauthorEnabled").asBoolean()); assertFalse(pair.lastParams.get("manageScheduleEnabled").asBoolean()); + assertEquals("code-review", pair.lastParams.get("includedBuiltinSkills").get(0).asText()); client.close(); } } @@ -197,6 +201,9 @@ void emptyMode_noFieldsSet_safeDefaultsSent() throws Exception { assertFalse(pair.lastParams.get("manageScheduleEnabled").asBoolean(), "default: schedule disabled"); assertTrue(pair.lastParams.get("installedPlugins").isArray(), "installedPlugins should be empty array"); assertEquals(0, pair.lastParams.get("installedPlugins").size()); + assertTrue(pair.lastParams.get("includedBuiltinSkills").isArray(), + "includedBuiltinSkills should be empty array"); + assertEquals(0, pair.lastParams.get("includedBuiltinSkills").size()); client.close(); } } @@ -218,6 +225,25 @@ void emptyMode_callerOverridesWin() throws Exception { assertTrue(pair.lastParams.get("installedPlugins").isArray(), "installedPlugins always empty in EMPTY mode"); assertEquals(0, pair.lastParams.get("installedPlugins").size()); + assertTrue(pair.lastParams.get("includedBuiltinSkills").isArray(), + "includedBuiltinSkills always empty in EMPTY mode"); + assertEquals(0, pair.lastParams.get("includedBuiltinSkills").size()); + client.close(); + } + } + + @Test + void emptyMode_explicitBuiltinSkillAllowlistWins() throws Exception { + try (var pair = new AutoReplyPair()) { + var session = new CopilotSession("sess-1", pair.rpcClient); + var client = new CopilotClient(new CopilotClientOptions().setMode(CopilotClientMode.EMPTY) + .setCopilotHome("/tmp/copilot-home").setAutoStart(false)); + + client.updateSessionOptionsForMode(session, null, null, null, null, List.of("code-review")).get(); + + assertEquals("session.options.update", pair.lastMethod); + assertEquals(1, pair.lastParams.get("includedBuiltinSkills").size()); + assertEquals("code-review", pair.lastParams.get("includedBuiltinSkills").get(0).asText()); client.close(); } } 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 0000000000..0294b511ec --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/e2e/InProcessTransportIT.java @@ -0,0 +1,122 @@ +/*--------------------------------------------------------------------------------------------- + * 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 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.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.ffi.InProcessEnvGuard; +import com.github.copilot.generated.AssistantMessageEvent; +import com.github.copilot.generated.SessionIdleEvent; +import com.github.copilot.rpc.CopilotClientOptions; +import com.github.copilot.rpc.MessageOptions; +import com.github.copilot.rpc.PermissionHandler; +import com.github.copilot.rpc.PingResponse; +import com.github.copilot.rpc.RuntimeConnection; +import com.github.copilot.rpc.SessionConfig; + +/** + * 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}. Coverage includes both a + * purely local {@code ping} round-trip and a replay-backed session turn that + * exercises authentication, tool initialization, model traffic, and + * asynchronous session event delivery over the FFI transport. + * + *

+ * {@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 the + * classifier JAR containing {@code runtime.node}, 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. + 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(); + } + } + } + + @Test + void shouldCreateSessionAndCompleteTurnOverInProcessFfi() throws Exception { + ctx.configureForTest("session", "should_receive_session_events"); + + try (CopilotClient client = ctx.createClient(); + CopilotSession session = client + .createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)) + .get(30, TimeUnit.SECONDS)) { + var idleReceived = new CompletableFuture(); + session.on(SessionIdleEvent.class, idleReceived::complete); + + AssistantMessageEvent response = session.sendAndWait(new MessageOptions().setPrompt("What is 100+200?")) + .get(60, TimeUnit.SECONDS); + + assertNotNull(response); + assertEquals("100 + 200 = 300", response.getData().content()); + assertNotNull(idleReceived.get(5, TimeUnit.SECONDS)); + } + } +} diff --git a/java/sdk/src/test/java/com/github/copilot/e2e/OutOfProcessTransportIT.java b/java/sdk/src/test/java/com/github/copilot/e2e/OutOfProcessTransportIT.java new file mode 100644 index 0000000000..19c14a0644 --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/e2e/OutOfProcessTransportIT.java @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * 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 org.junit.jupiter.api.Test; + +import com.github.copilot.AllowCopilotExperimental; +import com.github.copilot.CopilotClient; +import com.github.copilot.rpc.CopilotClientOptions; +import com.github.copilot.rpc.PingResponse; +import com.github.copilot.rpc.RuntimeConnection; + +/** + * Failsafe smoke test for the managed out-of-process runtime wrapper. + */ +@AllowCopilotExperimental +@RequireInProcess +class OutOfProcessTransportIT { + + @Test + void shouldStartPingAndStopOverStdio() throws Exception { + CopilotClientOptions options = new CopilotClientOptions().setConnection(RuntimeConnection.forStdio()); + try (CopilotClient client = new CopilotClient(options)) { + client.start().get(); + + PingResponse pong = client.ping("wrapper message").get(); + assertEquals("pong: wrapper 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 0000000000..12de4e5b73 --- /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 0000000000..fd3f92100d --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/e2e/RewindIT.java @@ -0,0 +1,144 @@ +/*--------------------------------------------------------------------------------------------- + * 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 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 ORIGINAL_FILE_CONTENT = "Original rewind content"; + private static final String PREPARED_FILE_CONTENT = "Prepared rewind content"; + 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); + Files.writeString(filePath, ORIGINAL_FILE_CONTENT); + + try (CopilotClient client = ctx.createClient(); + CopilotSession session = client.createSession(new SessionConfig().setModel("claude-sonnet-5") + .setEnableFileChangeTracking(true).setOnPermissionRequest(PermissionHandler.APPROVE_ALL)) + .get(30, TimeUnit.SECONDS)) { + AssistantMessageEvent ready = session + .sendAndWait(new MessageOptions().setPrompt("Use the edit tool to replace the exact contents of " + + FILE_NAME + " from " + ORIGINAL_FILE_CONTENT + " to " + PREPARED_FILE_CONTENT + + ". After the tool succeeds, reply with exactly SDK_REWIND_READY."), 30_000) + .get(60, TimeUnit.SECONDS); + assertNotNull(ready); + assertEquals("SDK_REWIND_READY", ready.getData().content()); + assertEquals(PREPARED_FILE_CONTENT, Files.readString(filePath)); + + AssistantMessageEvent response = session + .sendAndWait(new MessageOptions().setPrompt("Use the edit tool to replace the exact contents of " + + FILE_NAME + " from " + PREPARED_FILE_CONTENT + " to " + 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(2, rewindPoints.points().size()); + var rewindPoint = rewindPoints.points().get(1); + assertTrue(Boolean.TRUE.equals(rewindPoint.turnChangedFiles())); + 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)); + assertEquals(PREPARED_FILE_CONTENT, Files.readString(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(30); + SessionHistoryListRewindPointsResult result; + do { + result = session.getRpc().history.listRewindPoints().get(10, TimeUnit.SECONDS); + if (result.unavailableReason() == null && result.points().size() == 2 + && Boolean.TRUE.equals(result.points().get(1).turnChangedFiles()) + && Boolean.TRUE.equals(result.points().get(1).canRestoreFiles())) { + return result; + } + TimeUnit.MILLISECONDS.sleep(100); + } while (System.nanoTime() < deadline); + + assertNull(result.unavailableReason(), "Timed out waiting for rewind points to become available"); + assertTrue(result.points().size() >= 2, "Timed out waiting for both rewind points"); + assertTrue(Boolean.TRUE.equals(result.points().get(1).turnChangedFiles()), + "Timed out waiting for the edit turn to capture file changes"); + assertTrue(Boolean.TRUE.equals(result.points().get(1).canRestoreFiles()), + "Timed out waiting for rewind file restoration 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 0000000000..3f626e133f --- /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 0000000000..545b316c8c --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/ffi/FfiRuntimeHostTest.java @@ -0,0 +1,403 @@ +/*--------------------------------------------------------------------------------------------- + * 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 startWithoutEntrypointPassesOnlyRuntimeOptions() throws Exception { + AtomicReference argvJson = new AtomicReference<>(); + NativeBinding binding = new NativeBinding() { + @Override + public int hostStart(byte[] argv, int argvLen, byte[] env, int envLen) { + argvJson.set(argv); + 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; + } + }; + + try (FfiRuntimeHost host = new FfiRuntimeHost(binding, "/tmp/runtime.node")) { + host.start(null, new CopilotClientOptions().setLogLevel("debug")); + } + + List argv = MAPPER.readValue(argvJson.get(), new TypeReference>() { + }); + assertEquals(List.of("--log-level", "debug"), argv); + } + + @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 0000000000..43df713715 --- /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 0000000000..e37a7cfc82 --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/ffi/JnaNativeBindingTest.java @@ -0,0 +1,346 @@ +/*--------------------------------------------------------------------------------------------- + * 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() throws Exception { + Path nativeLib = resolveNativeLib(); + assumeTrue(nativeLib != null, "Native runtime not available (run with -Pinprocess)"); + Path altDir = Path.of("target", "jna-test-runtime"); + Files.createDirectories(altDir); + Path altPath = altDir.resolve("runtime-copy-reset.node"); + Files.copy(nativeLib, altPath, java.nio.file.StandardCopyOption.REPLACE_EXISTING); + + 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 0000000000..84ef7d8de4 --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/ffi/NativeRuntimeLoaderTest.java @@ -0,0 +1,672 @@ +/*--------------------------------------------------------------------------------------------- + * 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[] FAKE_WRAPPER_CONTENT = "fake runtime wrapper 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() throws Exception { + Path workingDirectory = Path.of("").toAbsolutePath(); + Path fakeCliDir = Files.createTempDirectory(workingDirectory.resolve("target"), "relative-cli-"); + try { + Path fakeCliPath = Files.createFile(fakeCliDir.resolve("copilot")); + Path runtimeNode = Files.write(fakeCliDir.resolve(NativeRuntimeLoader.RUNTIME_FILENAME), + FAKE_BINARY_CONTENT); + Path relativeCliPath = workingDirectory.relativize(fakeCliPath); + + assertEquals(runtimeNode, NativeRuntimeLoader.resolveFromCliPath(relativeCliPath.toString())); + } finally { + Files.deleteIfExists(fakeCliDir.resolve(NativeRuntimeLoader.RUNTIME_FILENAME)); + Files.deleteIfExists(fakeCliDir.resolve("copilot")); + Files.deleteIfExists(fakeCliDir); + } + } + + @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 resolveRuntimeWrapperExtractsAdjacentPairFromAbsentCache(@TempDir Path tempDir) throws Exception { + Path cacheBase = tempDir.resolve("cache"); + assertFalse(Files.exists(cacheBase)); + ClassLoader loader = classLoaderWithRuntimeWrapperArtifacts(tempDir, TEST_CLASSIFIER, TEST_NATIVE_VERSION); + + Path wrapper = NativeRuntimeLoader.resolveRuntimeWrapper(cacheBase, loader, TEST_CLASSIFIER, TEST_VERSION); + + assertEquals(NativeRuntimeLoader.RUNTIME_WRAPPER_FILENAME, wrapper.getFileName().toString()); + assertTrue(Files.isRegularFile(wrapper)); + assertTrue(Files.isRegularFile(wrapper.resolveSibling(NativeRuntimeLoader.RUNTIME_FILENAME))); + assertFalse(Files.exists(wrapper.resolveSibling(NativeRuntimeLoader.CLI_FILENAME))); + } + + @Test + void resolveRuntimeWrapperExtractsRetainedRuntimeAssets(@TempDir Path tempDir) throws Exception { + Path resourceDir = tempDir.resolve("native").resolve(TEST_CLASSIFIER); + writeRuntimeResource(tempDir, TEST_CLASSIFIER, FAKE_BINARY_CONTENT); + Files.write(resourceDir.resolve(NativeRuntimeLoader.RUNTIME_WRAPPER_FILENAME), FAKE_WRAPPER_CONTENT); + Path ripgrep = resourceDir.resolve("ripgrep/bin/linux-x64/rg"); + Files.createDirectories(ripgrep.getParent()); + Files.writeString(ripgrep, "ripgrep"); + Files.writeString(resourceDir.resolve(NativeRuntimeLoader.RUNTIME_ASSETS_FILENAME), + "644\truntime.node\n" + "755\tcopilot-runtime\n" + "755\tripgrep/bin/linux-x64/rg\n"); + ClassLoader loader = new URLClassLoader(new URL[]{tempDir.toUri().toURL()}, null); + + Path wrapper = NativeRuntimeLoader.resolveRuntimeWrapper(tempDir.resolve("cache"), loader, TEST_CLASSIFIER, + TEST_VERSION); + + Path installedRipgrep = wrapper.getParent().resolve("ripgrep/bin/linux-x64/rg"); + assertEquals("ripgrep", Files.readString(installedRipgrep)); + assertTrue(Files.isExecutable(installedRipgrep)); + } + + @Test + void resolveRuntimeWrapperRejectsClassifierWithoutWrapper(@TempDir Path tempDir) throws Exception { + writeRuntimeResource(tempDir, TEST_CLASSIFIER, FAKE_BINARY_CONTENT); + ClassLoader loader = new URLClassLoader(new URL[]{tempDir.toUri().toURL()}, null); + + IOException error = assertThrows(IOException.class, () -> NativeRuntimeLoader + .resolveRuntimeWrapper(tempDir.resolve("cache"), loader, TEST_CLASSIFIER, TEST_VERSION)); + + assertTrue(error.getMessage().contains(NativeRuntimeLoader.RUNTIME_WRAPPER_FILENAME)); + } + + @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.write(resourceDir.resolve(NativeRuntimeLoader.RUNTIME_WRAPPER_FILENAME), FAKE_WRAPPER_CONTENT); + Files.writeString(resourceDir.resolve("platform.properties"), + "classifier=" + classifier + "\nversion=" + nativeVersion + "\n"); + return new URLClassLoader(new URL[]{tempDir.toUri().toURL()}, null); + } + + private static ClassLoader classLoaderWithRuntimeWrapperArtifacts(Path tempDir, String classifier, + String nativeVersion) throws IOException { + writeRuntimeResource(tempDir, classifier, FAKE_BINARY_CONTENT); + Path resourceDir = tempDir.resolve("native").resolve(classifier); + Files.write(resourceDir.resolve(NativeRuntimeLoader.RUNTIME_WRAPPER_FILENAME), FAKE_WRAPPER_CONTENT); + Files.write(resourceDir.resolve(NativeRuntimeLoader.CLI_FILENAME), FAKE_CLI_CONTENT); + 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 0000000000..82049ea6af --- /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 0000000000..6fd9616706 --- /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 98% 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 f3b3f98083..fd0fdaaea1 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 @@ -126,7 +126,8 @@ void sessionRpc_mode_set_merges_sessionId() { var stub = new StubCaller(); var session = new SessionRpc(stub, "sess-mode-set"); - var modeParams = new SessionModeSetParams(null, null); + var modeParams = new SessionModeSetParams(null, null, null, null, null, null, null, null, null, null, null, + null); session.mode.set(modeParams); assertEquals(1, stub.calls.size()); @@ -466,7 +467,7 @@ void sessionRpc_ui_elicitation_merges_sessionId() { var stub = new StubCaller(); var session = new SessionRpc(stub, "sess-ui"); - var uiParams = new SessionUiElicitationParams(null, "Please provide info", null); + var uiParams = new SessionUiElicitationParams(null, null, "Please provide info", null, null, null); session.ui.elicitation(uiParams); assertEquals(1, stub.calls.size()); @@ -498,7 +499,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()); 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 3b8a716c7d..f86b3dfbcb 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 @@ -301,7 +301,8 @@ void sessionModeGetParams_record() { @Test void sessionModeSetParams_record() { - var params = new SessionModeSetParams("sess-30", SessionMode.PLAN); + var params = new SessionModeSetParams("sess-30", SessionMode.PLAN, null, null, null, null, null, null, null, + null, null, null); assertEquals("sess-30", params.sessionId()); assertEquals(SessionMode.PLAN, params.mode()); } @@ -325,10 +326,10 @@ void sessionModelGetCurrentParams_record() { @Test void sessionModelSwitchToParams_record() { - var params = new SessionModelSwitchToParams("sess-32", "claude-sonnet-4.5", "high", null, null, null, null, - null); + var params = new SessionModelSwitchToParams("sess-32", "claude-sonnet-5", "high", null, null, null, null, null, + null, null, null, null, null, null, null); assertEquals("sess-32", params.sessionId()); - assertEquals("claude-sonnet-4.5", params.modelId()); + assertEquals("claude-sonnet-5", params.modelId()); assertEquals("high", params.reasoningEffort()); assertNull(params.reasoningSummary()); assertNull(params.verbosity()); @@ -338,10 +339,11 @@ void sessionModelSwitchToParams_record() { @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 @@ -434,7 +436,7 @@ void sessionToolsHandlePendingToolCallParams_record() { @Test void sessionUiElicitationParams_record() { - var params = new SessionUiElicitationParams("sess-45", "What is your name?", null); + var params = new SessionUiElicitationParams("sess-45", null, "What is your name?", null, null, null); assertEquals("sess-45", params.sessionId()); assertEquals("What is your name?", params.message()); assertNull(params.requestedSchema()); @@ -468,7 +470,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, null, null, null); var result = new SessionAgentListResult(List.of(item)); assertEquals(1, result.agents().size()); assertEquals("name1", result.agents().get(0).name()); @@ -480,7 +482,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, null, - null); + null, null, null); var result = new SessionAgentGetCurrentResult(agent); assertEquals("agent-1", result.agent().name()); assertEquals("Agent One", result.agent().displayName()); @@ -496,7 +498,8 @@ 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, null); + var item = new AgentInfo("a", "A", "Desc", "/path/to/a", null, null, 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()); @@ -505,7 +508,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, null, null, null); var result = new SessionAgentSelectResult(agent); assertEquals("selected", result.agent().name()); } @@ -654,13 +657,13 @@ void sessionMcpListResult_status_enum_all_values() { @Test void sessionModelGetCurrentResult_record() { - var result = new SessionModelGetCurrentResult("claude-sonnet-4.5", null, null); - assertEquals("claude-sonnet-4.5", result.modelId()); + var result = new SessionModelGetCurrentResult("claude-sonnet-5", null, null); + assertEquals("claude-sonnet-5", result.modelId()); } @Test void sessionModelSwitchToResult_record() { - var result = new SessionModelSwitchToResult("gpt-5", true); + var result = new SessionModelSwitchToResult("gpt-5", true, null, null, null, null, null, null); assertEquals("gpt-5", result.modelId()); assertEquals(true, result.deferred()); } @@ -706,11 +709,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()); } @@ -729,7 +733,7 @@ void sessionToolsHandlePendingToolCallResult_record() { @Test void sessionUiElicitationResult_accept() { - var result = new SessionUiElicitationResult(UIElicitationResponseAction.ACCEPT, Map.of("name", "Alice")); + var result = new SessionUiElicitationResult(UIElicitationResponseAction.ACCEPT, Map.of("name", "Alice"), null); assertEquals(UIElicitationResponseAction.ACCEPT, result.action()); assertEquals("Alice", result.content().get("name")); } @@ -754,8 +758,8 @@ void sessionUiHandlePendingElicitationResult_record() { @Test void sessionUsageGetMetricsResult_nested() { var changes = new UsageMetricsCodeChanges(100L, 50L, 5L, null); - var result = new SessionUsageGetMetricsResult(0.5, 10L, null, null, 2000L, null, changes, null, "gpt-5", 1000L, - 500L); + var result = new SessionUsageGetMetricsResult(0.5, 10L, null, null, 2000L, null, changes, null, null, "gpt-5", + 1000L, 500L); assertEquals(0.5, result.totalPremiumRequestCost()); assertEquals(10L, result.totalUserRequests()); assertNotNull(result.codeChanges()); @@ -813,9 +817,10 @@ void modelsListResult_nested() { var limits = new ModelCapabilitiesLimits(100000L, 8192L, 128000L, null); var capabilities = new ModelCapabilities(supports, limits); var policy = new ModelPolicy(ModelPolicyState.ENABLED, null); - var promo = new ModelBillingPromo("summer-2026", 25.0, "2026-08-01T00:00:00Z", "Summer discount"); + var promo = new ModelBillingPromo("summer-2026", 25.0, "2026-08-01T00:00:00Z", "Summer discount", true); var billing = new ModelBilling(1.0, null, null, promo); - var modelItem = new Model("gpt-5", "GPT-5", capabilities, policy, billing, null, null, null); + var modelItem = new Model("gpt-5", "GPT-5", capabilities, policy, billing, null, null, null, null, null, null, + null, null); var result = new ModelsListResult(List.of(modelItem)); assertEquals(1, result.models().size()); @@ -830,6 +835,7 @@ void modelsListResult_nested() { assertEquals(Double.valueOf(25.0), result.models().get(0).billing().promo().discountPercent()); assertEquals("2026-08-01T00:00:00Z", result.models().get(0).billing().promo().endsAt()); assertEquals("Summer discount", result.models().get(0).billing().promo().message()); + assertTrue(result.models().get(0).billing().promo().showBanner()); } @Test @@ -851,7 +857,8 @@ 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, null); + var params = new SessionModelSwitchToParams("sess-m", "gpt-5", null, null, null, capabilities, null, null, null, + null, null, null, null, null, null); assertEquals("gpt-5", params.modelId()); assertNotNull(params.modelCapabilities()); @@ -866,7 +873,7 @@ void sessionModelSwitchToParams_nested_records() { @Test void sessionUiElicitationParams_nested_schema() { var schema = new UIElicitationSchema("object", Map.of("name", Map.of("type", "string")), List.of("name")); - var params = new SessionUiElicitationParams("sess-elicit", "Please fill form", schema); + var params = new SessionUiElicitationParams("sess-elicit", null, "Please fill form", schema, null, null); assertEquals("sess-elicit", params.sessionId()); assertEquals("object", params.requestedSchema().type()); assertTrue(params.requestedSchema().required().contains("name")); 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 100% 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 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 0000000000..1097b9ea0e --- /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, null); + } + + @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, null); + + 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 0000000000..850dfa251f --- /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 100% 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 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 97% 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 f0623d92ed..e7012c644f 100644 --- a/java/src/test/java/com/github/copilot/tool/CopilotToolProcessorTest.java +++ b/java/sdk/src/test/java/com/github/copilot/tool/CopilotToolProcessorTest.java @@ -620,8 +620,8 @@ public String doSomething(@CopilotToolParam("Input") String input) { assertFalse(generated.contains("Map.of("), "Expected no metadata map for a tool without metadata, got:\n" + generated); String normalizedGenerated = generated.replace("\r\n", "\n").replace('\r', '\n'); - assertTrue(normalizedGenerated.contains(" null\n )"), - "Expected metadata constructor argument to be null when metadata is absent, got:\n" + generated); + assertTrue(normalizedGenerated.contains(" null,\n null\n )"), + "Expected metadata and isTerminal constructor arguments to be null when absent, got:\n" + generated); } @Test @@ -1243,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 @@ -1252,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"; } @@ -1268,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 100% 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 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/NamedProviderConfig.java b/java/src/generated/java/com/github/copilot/generated/rpc/NamedProviderConfig.java deleted file mode 100644 index 9ee1c5a956..0000000000 --- a/java/src/generated/java/com/github/copilot/generated/rpc/NamedProviderConfig.java +++ /dev/null @@ -1,46 +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 java.util.Map; -import javax.annotation.processing.Generated; - -/** - * A named BYOK provider connection (transport + credentials). - * - * @since 1.0.0 - */ -@javax.annotation.processing.Generated("copilot-sdk-codegen") -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonIgnoreProperties(ignoreUnknown = true) -public record NamedProviderConfig( - /** Stable identifier referenced by BYOK model definitions. Must not contain '/'. */ - @JsonProperty("name") String name, - /** Provider type. Defaults to "openai" for generic OpenAI-compatible APIs. */ - @JsonProperty("type") ProviderConfigType type, - /** Wire API format (openai/azure only). Defaults to "completions". */ - @JsonProperty("wireApi") ProviderConfigWireApi wireApi, - /** Provider transport. Defaults to "http". */ - @JsonProperty("transport") ProviderConfigTransport transport, - /** API endpoint URL. */ - @JsonProperty("baseUrl") String baseUrl, - /** API key. Optional for local providers like Ollama. */ - @JsonProperty("apiKey") String apiKey, - /** Bearer token for authentication. Sets the Authorization header directly. Takes precedence over apiKey when both are set. */ - @JsonProperty("bearerToken") String bearerToken, - /** Azure-specific provider options. */ - @JsonProperty("azure") ProviderConfigAzure azure, - /** Custom HTTP headers to include in all outbound requests to the provider. */ - @JsonProperty("headers") Map headers, - /** 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. */ - @JsonProperty("hasBearerTokenProvider") Boolean hasBearerTokenProvider -) { -} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsGetAllowAllResult.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsGetAllowAllResult.java deleted file mode 100644 index 28d9915df2..0000000000 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsGetAllowAllResult.java +++ /dev/null @@ -1,32 +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 javax.annotation.processing.Generated; - -/** - * Current allow-all permission mode. - * - * @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 SessionPermissionsGetAllowAllResult( - /** Whether full allow-all permissions are currently active */ - @JsonProperty("enabled") Boolean enabled, - /** Current allow-all mode */ - @JsonProperty("mode") PermissionsAllowAllMode mode -) { -} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetAllowAllParams.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetAllowAllParams.java deleted file mode 100644 index f31646f766..0000000000 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetAllowAllParams.java +++ /dev/null @@ -1,38 +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 javax.annotation.processing.Generated; - -/** - * Allow-all mode to apply 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 SessionPermissionsSetAllowAllParams( - /** Target session identifier */ - @JsonProperty("sessionId") String sessionId, - /** Allow-all mode to apply. `on` enables full allow-all; `auto` enables advisory LLM auto-approval; `off` disables both. */ - @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 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/justfile b/justfile index c84166862f..69666bc00a 100644 --- a/justfile +++ b/justfile @@ -9,7 +9,7 @@ format: format-go format-python format-nodejs format-dotnet format-rust lint: lint-go lint-python lint-nodejs lint-dotnet lint-rust # Run tests for all languages -test: test-go test-python test-nodejs test-dotnet test-rust test-corrections +test: test-go test-python test-nodejs test-dotnet test-rust test-harness test-corrections # Format Go code format-go: @@ -66,6 +66,11 @@ test-nodejs: @echo "=== Testing Node.js code ===" @cd nodejs && npm test +# Run test harness tests +test-harness: + @echo "=== Testing test harness ===" + @cd test/harness && npm test + # Test .NET code test-dotnet: @echo "=== Testing .NET code ===" @@ -168,4 +173,3 @@ validate-docs-go: validate-docs-cs: @echo "=== Validating C# documentation ===" @cd scripts/docs-validation && npm run validate:cs - diff --git a/nodejs/.npmrc b/nodejs/.npmrc new file mode 100644 index 0000000000..100721e01a --- /dev/null +++ b/nodejs/.npmrc @@ -0,0 +1 @@ +omit-lockfile-registry-resolved=true diff --git a/nodejs/README.md b/nodejs/README.md index eec674ce4e..3a2a536e65 100644 --- a/nodejs/README.md +++ b/nodejs/README.md @@ -8,6 +8,20 @@ To use the SDK, you'll need: - Node.js ^20.19.0 or >=22.12.0 +The SDK uses an optional `@github/copilot-sdk-` package containing the +Copilot CLI runtime for the host platform. These packages are built from +verified `github/copilot-cli` release assets when the SDK is published, so +starting the SDK performs no runtime download. Set `COPILOT_CLI_PATH` to use an +existing installation instead. + +The checked-in release pin is `copilotCliVersion` in `package.json`. Run +`npm run set:cli-version -- ` to update it and regenerate the compiled +metadata in `src/cliVersion.ts`. Packaging verifies release assets against the +release's `SHA256SUMS.txt`. + +`npm run pack:release` builds the main package and all platform packages. Set +`COPILOT_CLI_DOWNLOAD_BASE_URL` to use a release mirror while packaging. + ## Installation ```bash @@ -22,6 +36,7 @@ Try the interactive chat sample (from the repo root): cd nodejs npm ci npm run build +export COPILOT_CLI_PATH="$(npm run --silent prepare:runtime -- --print-path)" cd samples npm install npm start @@ -95,6 +110,7 @@ new CopilotClient(options?: CopilotClientOptions) - `RuntimeConnection.forUri(url, { connectionToken? })` — connect to an already-running runtime (mutually exclusive with `gitHubToken`/`useLoggedInUser`). There is no top-level `cliUrl` shortcut; use this factory for URL-based connections. - `RuntimeConnection.forInProcess()` — host the runtime in-process over its native C ABI (FFI). **Experimental.** Because the runtime shares this process, `env`, `telemetry`, and `workingDirectory` are rejected with this transport; set them on the host process instead. - The child-process transports (`forStdio`/`forTcp`) also accept a per-connection `env`. Set it there or via the top-level `env` option — not both (setting both throws). + - Managed child-process connections materialize the bundled `copilot-runtime` and adjacent `runtime.node`, then launch the wrapper by default. An explicit connection `path` or `COPILOT_CLI_PATH` overrides the bundled runtime. - `mode?: "empty" | "copilot-cli"` - Defaulting strategy. Use `"empty"` for multi-user server mode; defaults to `"copilot-cli"`. - `workingDirectory?: string` - Working directory for the runtime process (default: current process cwd). - `baseDirectory?: string` - Base directory for Copilot data (session state, config, etc.). Sets `COPILOT_HOME` on the spawned runtime. When not set, the runtime defaults to `~/.copilot`. Ignored when connecting via `RuntimeConnection.forUri`. @@ -131,18 +147,33 @@ 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.** +- `capi?: CapiSessionOptions` - Copilot API options. With `model: "auto"`, set `autoTier` to `"efficiency"`, `"balance"`, or `"intelligence"` to choose a routing preference. Requires a runtime with Auto tier support and V2 Auto routing. Omission preserves default behavior. See [Auto tier persistence](../docs/features/session-persistence.md#auto-tier-persistence) for resume semantics. - `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. +- `gitHubTokenProvider?: GitHubTokenProvider` - Acquires rotating, session-scoped GitHub tokens. Token results require a positive `expiresIn` value in seconds remaining when the callback completes; production tokens typically last eight hours. Cannot be combined with `gitHubToken`. - `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. `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. +- `onUserInputRequest?: UserInputHandler` - Handler for legacy question-and-answer requests from the agent. Enables the legacy `ask_user` tool. See [User Input Requests](#user-input-requests) section. +- `askUserVariant?: "legacy" | "elicitation"` - Selects the model-facing `ask_user` tool shape when creating or cold-resuming a session. Defaults to `"legacy"`; use `"elicitation"` with `onElicitationRequest`. - `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. +```typescript +const session = await client.createSession({ + gitHubTokenProvider: async ({ host }) => ({ + kind: "token", + accessToken: await acquireTokenForHost(host), + expiresIn: 8 * 60 * 60, + }), +}); +``` + +Initial acquisition runs during session creation or resume. Cancellation, provider errors, and invalid token responses reject that operation instead of falling back to ambient authentication. Idle sessions refresh only before their next credential-consuming operation; there is no background refresh timer. + ##### `resumeSession(sessionId: string, config?: ResumeSessionConfig): Promise` Resume an existing session. Returns the session with `workspacePath` populated if infinite sessions were enabled. @@ -920,15 +951,15 @@ const session = await client.createSession({ The handler must return one of the `PermissionDecision` shapes (or `{ kind: "no-result" }`). Approval scopes are present-tense — they describe the decision to apply, not the outcome reported back on session events: -| Kind | Meaning | Extra fields | -| ------------------------ | -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | -| `"approve-once"` | Allow this single request | — | -| `"approve-for-session"` | Allow this request and remember the approval for the rest of the session | `approval?` (rule to remember), `domain?` (for URL approvals) | -| `"approve-for-location"` | Allow this request and persist the approval for this project location (git root or cwd) | `approval` (rule to persist), `locationKey` (location to persist under) | -| `"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"` | Suppress this SDK client's response so another connected client can answer the pending request | — | +| Kind | Meaning | Extra fields | +| ------------------------ | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | +| `"approve-once"` | Allow this single request | — | +| `"approve-for-session"` | Allow this request and remember the approval for the rest of the session | `approval?` (rule to remember), `domain?` (for URL approvals) | +| `"approve-for-location"` | Allow this request and persist the approval for this project location (git root or cwd) | `approval` (rule to persist), `locationKey` (location to persist under) | +| `"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"` | Suppress this SDK client's response so another connected client can answer the pending request | — | ### Resuming Sessions @@ -946,7 +977,7 @@ To let a specific custom tool bypass the permission prompt entirely, set `skipPe ## User Input Requests -Enable the agent to ask questions to the user using the `ask_user` tool by providing an `onUserInputRequest` handler: +Enable the legacy question-and-answer `ask_user` tool by providing an `onUserInputRequest` handler: ```typescript const session = await client.createSession({ @@ -978,6 +1009,7 @@ Register an `onElicitationRequest` handler to let your client act as an elicitat const session = await client.createSession({ model: "gpt-5", onPermissionRequest: approveAll, + askUserVariant: "elicitation", onElicitationRequest: async (context) => { // context.sessionId - Session that triggered the request // context.message - Description of what information is needed @@ -999,6 +1031,9 @@ const session = await client.createSession({ console.log(session.capabilities.ui?.elicitation); // true ``` +Set `askUserVariant: "elicitation"` to expose the structured form as the model's +`ask_user` tool. Omit it to retain the legacy SDK behavior. + When `onElicitationRequest` is provided, the SDK sends `requestElicitation: true` during session create/resume, which enables `session.capabilities.ui.elicitation` on the session. In multi-client scenarios: diff --git a/nodejs/docs/extensions.md b/nodejs/docs/extensions.md index d33a733120..3b7161f055 100644 --- a/nodejs/docs/extensions.md +++ b/nodejs/docs/extensions.md @@ -53,6 +53,27 @@ const session = await joinSession({ The `session` object provides methods for sending messages, logging to the timeline, listening to events, and accessing the RPC API. See the `.d.ts` files in the SDK package for full type information. +## Requesting Sensitive Environment Variables + +The CLI strips sensitive environment variables (for example `GITHUB_TOKEN`) from every extension process before it starts. An extension that needs one asks for it by name: + +```js +import { joinSession } from "@github/copilot-sdk/extension"; + +const session = await joinSession({ + requestedEnvironmentVariables: ["GITHUB_TOKEN"], +}); + +// Granted values are in process.env once joinSession resolves. +const token = process.env.GITHUB_TOKEN; +``` + +The CLI prompts the user with the extension's name and the exact list of variables requested. If the user approves, only those variables reach this extension and their values are written into `process.env` before `joinSession()` resolves. If the user denies, `joinSession()` rejects, the extension does not load, and its tools never reach the model. + +An approval is remembered against the exact set of names the user saw, so an extension that later asks for an additional variable prompts again. Names that are unset, or that the CLI does not filter from extensions, are not prompted for. + +An approved extension can pass a granted value to anything it starts, so ask only for what the extension genuinely needs. + ## Further Reading - `examples.md` — Practical code examples for tools, hooks, events, and complete extensions diff --git a/nodejs/docs/factories.md b/nodejs/docs/factories.md index 0c1f0f09a0..23b9d0fed3 100644 --- a/nodejs/docs/factories.md +++ b/nodejs/docs/factories.md @@ -16,11 +16,12 @@ const reviewChanged = defineFactory({ "Review changed files and verify the findings. " + "args: { files: string[] } — the paths to review.", phases: [{ title: "Review" }, { title: "Verify" }], - limits: { - maxConcurrentSubagents: 3, - maxTotalSubagents: 10, - timeoutSeconds: 90.5, - maxAiCredits: 5, + argsSchema: { + type: "object", + required: ["files"], + properties: { + files: { type: "array", items: { type: "string" } }, + }, }, }, run: async (ctx) => { @@ -41,9 +42,19 @@ const reviewChanged = defineFactory({ const session = await joinSession({ factories: [reviewChanged] }); ``` -Factory metadata contains a stable `name`, a human-readable `description`, declared `phases`, and optional `limits`. Phase entries contain a `title` and optional `detail`. +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`. -There is no declared schema for `ctx.args`. The `run_factory` tool forwards `args` verbatim and its parameter is untyped, so **the `description` is the only thing telling an agent what arguments to supply** — state the expected shape there whenever a factory reads `ctx.args`, as the example above does. Arguments supplied by an extension calling `session.factory.run(...)` directly are typed through `defineFactory`, but that typing does not reach the model. A factory that reads `ctx.args` should validate it rather than assume a shape. +## 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. @@ -53,7 +64,7 @@ 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`, and `model`. See [Subagent calls](#subagent-calls). +- `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. @@ -61,7 +72,7 @@ The `run()` context provides: - `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 full session returned by `joinSession`. +- `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. @@ -104,7 +115,14 @@ See [factory-patterns.md](./factory-patterns.md) for composable orchestration pa ## Resource limits -Limits may be declared in `meta.limits` and overridden per invocation. All limits must be positive when present. +Limits may be declared in `meta.limits` and overridden per invocation. Every limit is optional and must be positive when present; an omitted limit leaves that dimension unbounded, except that an omitted `maxConcurrentSubagents` falls back to `maxTotalSubagents`, so a declared total cap also bounds concurrency. + +Set a ceiling only from real knowledge of what the factory costs, or because the user named one. A guessed ceiling does not make a run safer: it stops a healthy run partway with `factory_limit_reached`, after that run has already spent credits. An agent authoring or invoking a factory on the user's behalf has no basis for estimating a number, so it should leave `limits` unset and bound the work with the factory's own counters instead. Omitting limits does not remove oversight of a model-initiated run: `run_factory` requests permission first, and that prompt shows the effective limits. SDK-initiated `run` and `resume` do not request permission, so an SDK caller that wants a ceiling sets it deliberately, from the cost it already knows. + +```js +// Only when the cost profile is known, or the user asked for this ceiling. +limits: { maxTotalSubagents: 10 }, +``` - `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`. @@ -121,6 +139,8 @@ Run by registered name or handle: const run = await session.factory.run("review-changed", { args: { files: ["src/a.ts"] }, limits: { maxAiCredits: 3 }, + notifyOnComplete: true, + logPhaseNames: true, }); if (run.status === "completed") { @@ -135,7 +155,12 @@ The name overload is: ```ts session.factory.run( name: string, - options?: { args?: JsonValue; limits?: FactoryLimits }, + options?: { + args?: JsonValue; + limits?: FactoryLimits; + notifyOnComplete?: boolean; + logPhaseNames?: boolean; + }, ): Promise; ``` @@ -144,6 +169,8 @@ Resume by run ID without resending the name or arguments: ```ts const run = await session.factory.resume(runId, { limits: { maxAiCredits: 6 }, + notifyOnComplete: true, + logPhaseNames: true, }); ``` @@ -152,11 +179,17 @@ The signature is: ```ts session.factory.resume( runId: string, - options?: { limits?: FactoryLimits }, + options?: { + limits?: FactoryLimits; + notifyOnComplete?: boolean; + logPhaseNames?: boolean; + }, ): 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`. A declined fresh run is not a pre-execution failure: the run row already exists by the time the prompt is answered, so it resolves with a terminal `cancelled` envelope carrying the run ID. Only failures that occur *before* a run exists reject: an unknown factory name or an already-active session. Pre-execution resume failures, including a declined reapproval, throw `FactoryResumeError`, whose `code` is one of `not_found`, `non_resumable`, `already_active`, `reapproval_declined`, or `no_approval_provider`. +Set `notifyOnComplete` to `true` for factories that are likely to be invoked by an agent, so the originating session is notified when the factory completes. Set it to `false` for factories intended to be invoked programmatically, where the caller awaits the result directly. Set `logPhaseNames` to emit factory phase names to the session transcript. Both options apply to new and resumed runs. + +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. @@ -193,7 +226,7 @@ async ({ args, agent, phase }) => { }; ``` -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, and limits before running it. +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 @@ -201,8 +234,13 @@ The calling session can inspect its own factory runs: ```ts const runs = await session.factory.listRuns(); +const runsPage = await session.factory.listRuns({ + afterSeq, + beforeSeq, + limit, +}); const detail = await session.factory.getRunDetail(runId); -const page = await session.factory.getRunProgress(runId, { +const progressPage = await session.factory.getRunProgress(runId, { phaseId, afterSeq, beforeSeq, @@ -210,7 +248,8 @@ const page = await session.factory.getRunProgress(runId, { }); ``` -- `listRuns()` returns summaries in durable creation order. +- `listRuns()` returns only the runs array from the newest default page of this session's durable factory runs. This overload preserves the original convenience API. +- `listRuns({ afterSeq, beforeSeq, limit })` returns the full page. Its `oldestSeq`, `newestSeq`, `hasMoreNewer`, and `omittedOlder` fields let callers continue paging without raw RPC calls. - `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. diff --git a/nodejs/docs/factory-patterns.md b/nodejs/docs/factory-patterns.md index 66c6d13b1f..8ae403d55a 100644 --- a/nodejs/docs/factory-patterns.md +++ b/nodejs/docs/factory-patterns.md @@ -189,6 +189,6 @@ Compose these freely. 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. +There is no in-script budget object. Scale with your own counters, as in the loop patterns above, and treat any 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 bound a wide fan-out with the factory's own counters. Do not invent a ceiling to compensate, and see [Resource limits](./factories.md#resource-limits) for when declaring one is appropriate. `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 b3c43e9275..a10153a1d3 100644 --- a/nodejs/package-lock.json +++ b/nodejs/package-lock.json @@ -9,7 +9,6 @@ "version": "0.0.0-dev", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.78", "koffi": "^3.1.0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" @@ -29,6 +28,7 @@ "quicktype-core": "^23.2.6", "rimraf": "^6.1.2", "semver": "^7.7.3", + "tar": "^7.5.22", "tsx": "^4.20.6", "typescript": "^5.0.0", "vitest": "^4.0.18", @@ -40,7 +40,6 @@ }, "node_modules/@apidevtools/json-schema-ref-parser": { "version": "11.9.3", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.3.tgz", "integrity": "sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==", "dev": true, "license": "MIT", @@ -58,8 +57,7 @@ }, "node_modules/@emnapi/core": { "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "integrity": "sha1-OAzMjyQS6iLR2XLff47iOjucdGc=", "dev": true, "license": "MIT", "optional": true, @@ -70,8 +68,7 @@ }, "node_modules/@emnapi/runtime": { "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "integrity": "sha1-SyYMDTU0IE6YxhELjbGph9JuyHw=", "dev": true, "license": "MIT", "optional": true, @@ -81,8 +78,7 @@ }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "integrity": "sha1-KP7SGhuhznl8RKBwq8lNQvOuhUg=", "dev": true, "license": "MIT", "optional": true, @@ -92,8 +88,7 @@ }, "node_modules/@esbuild/aix-ppc64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", - "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "integrity": "sha1-egGo0uwvuy2seK2tCbD6eB5Agr4=", "cpu": [ "ppc64" ], @@ -109,8 +104,7 @@ }, "node_modules/@esbuild/android-arm": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", - "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "integrity": "sha1-cEvSl95tdi3lTqu+r79V9nVqvi8=", "cpu": [ "arm" ], @@ -126,8 +120,7 @@ }, "node_modules/@esbuild/android-arm64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", - "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "integrity": "sha1-tUCifRTkr9BYSWpNvsTT9BTbEQo=", "cpu": [ "arm64" ], @@ -143,8 +136,7 @@ }, "node_modules/@esbuild/android-x64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", - "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "integrity": "sha1-0csWbTSw+/D+irRgpVlPJKN4cB4=", "cpu": [ "x64" ], @@ -160,7 +152,6 @@ }, "node_modules/@esbuild/darwin-arm64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", "cpu": [ "arm64" @@ -177,8 +168,7 @@ }, "node_modules/@esbuild/darwin-x64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", - "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "integrity": "sha1-ZVVqQyoeTXIDLYIYwZMvzKGkl3I=", "cpu": [ "x64" ], @@ -194,8 +184,7 @@ }, "node_modules/@esbuild/freebsd-arm64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", - "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "integrity": "sha1-LmHgWS+QMNfj2uGO4l68U1kYrvY=", "cpu": [ "arm64" ], @@ -211,8 +200,7 @@ }, "node_modules/@esbuild/freebsd-x64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", - "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "integrity": "sha1-yV7CiZWe+AecTcqBeh4sS+Zrm9M=", "cpu": [ "x64" ], @@ -228,8 +216,7 @@ }, "node_modules/@esbuild/linux-arm": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", - "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "integrity": "sha1-wJoPZ5F1kqwN6JKpvk04FN69Kmw=", "cpu": [ "arm" ], @@ -245,8 +232,7 @@ }, "node_modules/@esbuild/linux-arm64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", - "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "integrity": "sha1-QLIhdd2gYYLz7oFBGGxf8wTEpxc=", "cpu": [ "arm64" ], @@ -262,8 +248,7 @@ }, "node_modules/@esbuild/linux-ia32": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", - "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "integrity": "sha1-pYD5xnZ5eDOJHlGfx6EzfIr9jbM=", "cpu": [ "ia32" ], @@ -279,8 +264,7 @@ }, "node_modules/@esbuild/linux-loong64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", - "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "integrity": "sha1-RkUs8yHcf56Rwvp4Cla7Vuec1os=", "cpu": [ "loong64" ], @@ -296,8 +280,7 @@ }, "node_modules/@esbuild/linux-mips64el": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", - "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "integrity": "sha1-QhGzGE3WYI9T3LIuOfXTTuCIUsg=", "cpu": [ "mips64el" ], @@ -313,8 +296,7 @@ }, "node_modules/@esbuild/linux-ppc64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", - "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "integrity": "sha1-aXhXwqYcubC2u2ZS5AwdxeHKjl0=", "cpu": [ "ppc64" ], @@ -330,8 +312,7 @@ }, "node_modules/@esbuild/linux-riscv64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", - "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "integrity": "sha1-0ZKUPrFGpArExkl9DPe+NbmGvwg=", "cpu": [ "riscv64" ], @@ -347,8 +328,7 @@ }, "node_modules/@esbuild/linux-s390x": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", - "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "integrity": "sha1-rOoDVtoODrwI+Xz3ucLkAeHmSNw=", "cpu": [ "s390x" ], @@ -364,8 +344,7 @@ }, "node_modules/@esbuild/linux-x64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", - "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "integrity": "sha1-bww84MtkxTS3DExF7LLBbTTjXf0=", "cpu": [ "x64" ], @@ -381,8 +360,7 @@ }, "node_modules/@esbuild/netbsd-arm64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", - "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "integrity": "sha1-i813B3oNzjN4tXT+2ybSolO3PTY=", "cpu": [ "arm64" ], @@ -398,8 +376,7 @@ }, "node_modules/@esbuild/netbsd-x64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", - "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "integrity": "sha1-5/sqAemcgwyU5mI82f77TI+1g0c=", "cpu": [ "x64" ], @@ -415,8 +392,7 @@ }, "node_modules/@esbuild/openbsd-arm64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", - "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "integrity": "sha1-xSkJNy24uG4sVeBaiUADO1Zgo7I=", "cpu": [ "arm64" ], @@ -432,8 +408,7 @@ }, "node_modules/@esbuild/openbsd-x64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", - "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "integrity": "sha1-xCe5vlpkwmL/mn63C1+7qt9EbGw=", "cpu": [ "x64" ], @@ -449,8 +424,7 @@ }, "node_modules/@esbuild/openharmony-arm64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", - "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "integrity": "sha1-3JsUe6yi5sSzyFVxdB70hgpIkJc=", "cpu": [ "arm64" ], @@ -466,8 +440,7 @@ }, "node_modules/@esbuild/sunos-x64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", - "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "integrity": "sha1-zoZtEt8TwV5MmfBzo9Rm9uBkmzo=", "cpu": [ "x64" ], @@ -483,8 +456,7 @@ }, "node_modules/@esbuild/win32-arm64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", - "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "integrity": "sha1-dGjjaS0B1inVlB5dg4F7uA+eObQ=", "cpu": [ "arm64" ], @@ -500,8 +472,7 @@ }, "node_modules/@esbuild/win32-ia32": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", - "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "integrity": "sha1-pbwAY/sryrbQ7WPyoVN5WLwmnsY=", "cpu": [ "ia32" ], @@ -517,8 +488,7 @@ }, "node_modules/@esbuild/win32-x64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", - "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "integrity": "sha1-EAZO5E9DR7kMmgK0Rrv4CpFjKxI=", "cpu": [ "x64" ], @@ -534,7 +504,6 @@ }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", @@ -553,7 +522,6 @@ }, "node_modules/@eslint-community/regexpp": { "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", @@ -563,7 +531,6 @@ }, "node_modules/@eslint/config-array": { "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", "dev": true, "license": "Apache-2.0", @@ -578,7 +545,6 @@ }, "node_modules/@eslint/config-array/node_modules/minimatch": { "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", @@ -591,7 +557,6 @@ }, "node_modules/@eslint/config-helpers": { "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "dev": true, "license": "Apache-2.0", @@ -604,7 +569,6 @@ }, "node_modules/@eslint/core": { "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "dev": true, "license": "Apache-2.0", @@ -617,7 +581,6 @@ }, "node_modules/@eslint/eslintrc": { "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", "dev": true, "license": "MIT", @@ -641,7 +604,6 @@ }, "node_modules/@eslint/eslintrc/node_modules/ignore": { "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", @@ -651,7 +613,6 @@ }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", @@ -664,7 +625,6 @@ }, "node_modules/@eslint/js": { "version": "9.39.2", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", "dev": true, "license": "MIT", @@ -677,7 +637,6 @@ }, "node_modules/@eslint/object-schema": { "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "dev": true, "license": "Apache-2.0", @@ -687,7 +646,6 @@ }, "node_modules/@eslint/plugin-kit": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, "license": "Apache-2.0", @@ -699,166 +657,14 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@github/copilot": { - "version": "1.0.78", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.78.tgz", - "integrity": "sha512-jn+8HLZC3R7d6K1/1g9L1iWNKzBVS3JdVcx40r3aWyS5r+MLV1OPNp0fo5OfRMCDIm3NmEaaoqypi9sQkCXuiQ==", - "license": "SEE LICENSE IN LICENSE.md", - "dependencies": { - "detect-libc": "^2.1.2" - }, - "bin": { - "copilot": "npm-loader.js" - }, - "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.78", - "@github/copilot-darwin-x64": "1.0.78", - "@github/copilot-linux-arm64": "1.0.78", - "@github/copilot-linux-x64": "1.0.78", - "@github/copilot-linuxmusl-arm64": "1.0.78", - "@github/copilot-linuxmusl-x64": "1.0.78", - "@github/copilot-win32-arm64": "1.0.78", - "@github/copilot-win32-x64": "1.0.78" - } - }, - "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.78", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.78.tgz", - "integrity": "sha512-P11+VyWg8ad0WlywGtO2d7AxqTLJv4hkUicFg6Ycth5lfk00aCu/74YOOZSPO6C2bBBJhAza7oAdmauM6KEojw==", - "cpu": [ - "arm64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "darwin" - ], - "bin": { - "copilot-darwin-arm64": "copilot" - } - }, - "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.78", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.78.tgz", - "integrity": "sha512-stimP3WDFs2GU8nJzTJbtRpZViV4bsf80yg7QrFq+G4RISQ3Nihg/3/H0U6UQF1+txMJ/Ohmb5RFYxSw1Hj2sw==", - "cpu": [ - "x64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "darwin" - ], - "bin": { - "copilot-darwin-x64": "copilot" - } - }, - "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.78", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.78.tgz", - "integrity": "sha512-K31PRKGTm252V1Lof7ypjg283R2QSm3BgoCvZfX2taos4wqC3SaTozSQKwW3dgrAx7A3G3SGEoilVCNqfigdZA==", - "cpu": [ - "arm64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "copilot-linux-arm64": "copilot" - } - }, - "node_modules/@github/copilot-linux-x64": { - "version": "1.0.78", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.78.tgz", - "integrity": "sha512-QK3oMtAn9dIv+1u1kx0xNpZNtZxdI+uZVIyLl7myp+Oh2Uj8BLagVv6a7uP0cDphO3TgfIdlvpepCe5MIcx0fw==", - "cpu": [ - "x64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "copilot-linux-x64": "copilot" - } - }, - "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.78", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.78.tgz", - "integrity": "sha512-F/0cTMsz6ug4yiXn3RKaCAMsLR261U5Njb6G9Y/HeAI7ES/tKEo2t5SHuvgXaIH4mYiZsRvfDKdX7c0WgBX/Jg==", - "cpu": [ - "arm64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "copilot-linuxmusl-arm64": "copilot" - } - }, - "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.78", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.78.tgz", - "integrity": "sha512-YMaJaeBGbArGAFYel+yFaFW/0rFgh0Oqki2f2mUtlonTX/xHr8EB4+mTnMJkHYMFy4gOTC3OtSEEe1NaW/cBXQ==", - "cpu": [ - "x64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "copilot-linuxmusl-x64": "copilot" - } - }, - "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.78", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.78.tgz", - "integrity": "sha512-ktDkFXaaecEKD3hpM6ydM9lKOdoCfsQsXCmzLzE7DCmSpbbMCdfPfWfZ7MOclmKmpZ5/MNfr4U2l8CUqGerzYA==", - "cpu": [ - "arm64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "win32" - ], - "bin": { - "copilot-win32-arm64": "copilot.exe" - } - }, - "node_modules/@github/copilot-win32-x64": { - "version": "1.0.78", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.78.tgz", - "integrity": "sha512-Gd8l2T4eqYEWlOEPd0SZznQ+YYgYrwOkE0QXodMkhCBbPdgu/uTzb7mnISWwnVAgqs7pONdF1GOpHkTo+ay8CQ==", - "cpu": [ - "x64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "win32" - ], - "bin": { - "copilot-win32-x64": "copilot.exe" - } - }, "node_modules/@glideapps/ts-necessities": { "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@glideapps/ts-necessities/-/ts-necessities-2.2.3.tgz", "integrity": "sha512-gXi0awOZLHk3TbW55GZLCPP6O+y/b5X1pBXKBVckFONSwF1z1E5ND2BGJsghQFah+pW7pkkyFb2VhUQI2qhL5w==", "dev": true, "license": "MIT" }, "node_modules/@humanfs/core": { "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "dev": true, "license": "Apache-2.0", @@ -868,7 +674,6 @@ }, "node_modules/@humanfs/node": { "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", "dev": true, "license": "Apache-2.0", @@ -882,7 +687,6 @@ }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, "license": "Apache-2.0", @@ -896,7 +700,6 @@ }, "node_modules/@humanwhocodes/retry": { "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, "license": "Apache-2.0", @@ -908,23 +711,32 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "integrity": "sha1-LVmuOrSzj7QnC/oj0w+OLobH/jI=", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, "node_modules/@jsdevtools/ono": { "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", "dev": true, "license": "MIT" }, "node_modules/@koromix/koffi-darwin-arm64": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@koromix/koffi-darwin-arm64/-/koffi-darwin-arm64-3.1.0.tgz", "integrity": "sha512-VEt5r3fXTfbejr83PnuOP0H7s9Zmazcs+lofu96DOcRkistlMsn59wYyWiKpyAjs9PCgm0Ykh62ChZ3CGMmIOg==", "cpu": [ "arm64" @@ -940,8 +752,7 @@ }, "node_modules/@koromix/koffi-darwin-x64": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@koromix/koffi-darwin-x64/-/koffi-darwin-x64-3.1.0.tgz", - "integrity": "sha512-n/tVRB9xIzdXT5H3zZt8ueThgWTSDL+yU7PWnU8wbZPBSawP/otx3swQyd6nMOqj1bmHgSHopiKSBXRS9pllmg==", + "integrity": "sha1-r1z9mNiPAynDUcq8q6sPqKFY+xs=", "cpu": [ "x64" ], @@ -956,8 +767,7 @@ }, "node_modules/@koromix/koffi-freebsd-arm64": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@koromix/koffi-freebsd-arm64/-/koffi-freebsd-arm64-3.1.0.tgz", - "integrity": "sha512-vazoPYIhOAlXZksVIqDRMIID4VeUZKx8F3dR90hOobT2ATyOkqNS5dv5UCV7Q7DSq22lQTrdbvENBAhROzCp0w==", + "integrity": "sha1-rOu888/eXKu5jj4KgZb0gCzt+xE=", "cpu": [ "arm64" ], @@ -972,8 +782,7 @@ }, "node_modules/@koromix/koffi-freebsd-ia32": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@koromix/koffi-freebsd-ia32/-/koffi-freebsd-ia32-3.1.0.tgz", - "integrity": "sha512-Vm7Uc97ru6RTSVmae2zCZZQeaizqVZ8WoU4+gG4H03Qe+WOj7kbKt/MxT7VBzdbPYIU5ZJeG/ZED1YlZyab6eQ==", + "integrity": "sha1-0mky/r//V0JaXd+q9IRmdsZrkPI=", "cpu": [ "ia32" ], @@ -988,8 +797,7 @@ }, "node_modules/@koromix/koffi-freebsd-x64": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@koromix/koffi-freebsd-x64/-/koffi-freebsd-x64-3.1.0.tgz", - "integrity": "sha512-N+VuVWjoiYPy1Go5mRadZ3B6RM5Qz+eCLhj2LXrMlefbUJ+O4gg7teCUGvPGfBEHDgmSN4yYUrfQmdJC10vOYw==", + "integrity": "sha1-2INR65Jz3bqyIPXHyqZ+PRGVkCs=", "cpu": [ "x64" ], @@ -1004,8 +812,7 @@ }, "node_modules/@koromix/koffi-linux-arm64": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@koromix/koffi-linux-arm64/-/koffi-linux-arm64-3.1.0.tgz", - "integrity": "sha512-Wx5iOkeALe2ympLdiYwRpIg5qUkyQIv8N2foZ9rRker0uE7ZtXew2RRkbEgMir4b0yDYR1zyXd6B62GUzLtZ/g==", + "integrity": "sha1-9h8pjdDbtKufG4JXoxDFRNzAbnI=", "cpu": [ "arm64" ], @@ -1020,8 +827,7 @@ }, "node_modules/@koromix/koffi-linux-ia32": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@koromix/koffi-linux-ia32/-/koffi-linux-ia32-3.1.0.tgz", - "integrity": "sha512-1DjYm1QehXU0dgn0uE+FGYOb3Of7GiTMqLS+ZI2gbl1b+h76sz4LRBvDVrQyAmSMVVU8/7696S21YgE/iBhBVg==", + "integrity": "sha1-8AbgHpYQoyx0uSoDgGpHwc1sGvM=", "cpu": [ "ia32" ], @@ -1036,8 +842,7 @@ }, "node_modules/@koromix/koffi-linux-loong64": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@koromix/koffi-linux-loong64/-/koffi-linux-loong64-3.1.0.tgz", - "integrity": "sha512-NOa0LdyltdESz3oeTqUH6MErHVoJOHoeXIsEp6xIMTUh4eKXEtlDQeoK6EYqo0DnBt83Xud95qLvi4Aw12pG4Q==", + "integrity": "sha1-bQvI/dvGGdARiMeDkZg0R+POkPI=", "cpu": [ "loong64" ], @@ -1052,8 +857,7 @@ }, "node_modules/@koromix/koffi-linux-riscv64": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@koromix/koffi-linux-riscv64/-/koffi-linux-riscv64-3.1.0.tgz", - "integrity": "sha512-Ye6kiXZCGxGtAIXSly6XuOP5tJZNYOZ2eVg33k1MilKrzimAy9Mpw4d6e9+Sfsc1jesgeNYs1sb5iaI8HS3ncA==", + "integrity": "sha1-V5aXxWe4DH2j2r1dnJcMH9w44co=", "cpu": [ "riscv64" ], @@ -1068,8 +872,7 @@ }, "node_modules/@koromix/koffi-linux-x64": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@koromix/koffi-linux-x64/-/koffi-linux-x64-3.1.0.tgz", - "integrity": "sha512-3yQTOkQrMna4VX+yeyfYImBjLlGrItMpsWyfaW1uSiz/A6GRydqdwYH7DWnp4Z+RSGYZpsewkf7byMc8pOOQKA==", + "integrity": "sha1-eJX/wAVe0HJASLRTgubjJrHppuo=", "cpu": [ "x64" ], @@ -1084,8 +887,7 @@ }, "node_modules/@koromix/koffi-openbsd-ia32": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@koromix/koffi-openbsd-ia32/-/koffi-openbsd-ia32-3.1.0.tgz", - "integrity": "sha512-/cDoFHb9yx4+yoT3GUpnKnfi3W2drG+/Ewo0TTZaQHb4PsxnYYyT6V8+t4cL5XXbQcTTcOsZxpmBRrn0NBa3dA==", + "integrity": "sha1-LwhKcVohSKoVCgY945O/4/gyoSk=", "cpu": [ "ia32" ], @@ -1100,8 +902,7 @@ }, "node_modules/@koromix/koffi-openbsd-x64": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@koromix/koffi-openbsd-x64/-/koffi-openbsd-x64-3.1.0.tgz", - "integrity": "sha512-CoQdqgnKvWgTXXZlUst8cBRQEov7QsxlTN2WAsu9wez01Xe6gEcH/zYePANualzzCbnaELfe5P0rA80QkoDuPA==", + "integrity": "sha1-wWm472ijVY45okrdH/8EIAVtzAo=", "cpu": [ "x64" ], @@ -1116,8 +917,7 @@ }, "node_modules/@koromix/koffi-win32-ia32": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@koromix/koffi-win32-ia32/-/koffi-win32-ia32-3.1.0.tgz", - "integrity": "sha512-WjrA+DEkpy0xEHu48+NSOboHhTnzkIfsFuq3d/WrSs+T9WflWRng3jC7mdJxmR4eHb6i6BqjW3k/U0mNUTjFPA==", + "integrity": "sha1-nDF+wS4sK934tD6A6e4I036PANM=", "cpu": [ "ia32" ], @@ -1132,8 +932,7 @@ }, "node_modules/@koromix/koffi-win32-x64": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@koromix/koffi-win32-x64/-/koffi-win32-x64-3.1.0.tgz", - "integrity": "sha512-tnK5+IkzQBauQAQSzuyjso8OOIQRlaTZS39xIWpfqVYDLVDIuLDQk/WwHcOrR5yxlDrZq9ygiebBTOfcJFia7w==", + "integrity": "sha1-FyJ7F9SaNAIYddhe6gJ533eKOI4=", "cpu": [ "x64" ], @@ -1147,27 +946,28 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "version": "1.2.3", + "integrity": "sha1-l+PUXXQk3F2h1OMvO/OykvbBtEw=", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@tybys/wasm-util": "^0.10.1" + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/Brooooooklyn" }, "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" } }, "node_modules/@oxc-project/types": { "version": "0.133.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", "dev": true, "license": "MIT", @@ -1177,7 +977,6 @@ }, "node_modules/@platformatic/vfs": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@platformatic/vfs/-/vfs-0.3.0.tgz", "integrity": "sha512-BGXVOAz59HYPZCgI9v/MtiTF/ng8YAWtkooxVwOPR3TatNgGy0WZ/t15ScqytiZi5NdSRqWNRfuAbXKeAlKDdQ==", "dev": true, "license": "MIT", @@ -1187,8 +986,7 @@ }, "node_modules/@rolldown/binding-android-arm64": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", - "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", + "integrity": "sha1-VM6Pg4IhP0oxSgwve6g/gf/q5ZI=", "cpu": [ "arm64" ], @@ -1204,7 +1002,6 @@ }, "node_modules/@rolldown/binding-darwin-arm64": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", "cpu": [ "arm64" @@ -1221,8 +1018,7 @@ }, "node_modules/@rolldown/binding-darwin-x64": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", - "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", + "integrity": "sha1-U/V94fWZ7PHbE4I8/IjBj7gJVK0=", "cpu": [ "x64" ], @@ -1238,8 +1034,7 @@ }, "node_modules/@rolldown/binding-freebsd-x64": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", - "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", + "integrity": "sha1-bz/dobeuqsnSaKUmgEtPuW5ONfE=", "cpu": [ "x64" ], @@ -1255,8 +1050,7 @@ }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", - "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", + "integrity": "sha1-2HpFS/WFzJZ2hJN36R1uN1KXMm8=", "cpu": [ "arm" ], @@ -1272,8 +1066,7 @@ }, "node_modules/@rolldown/binding-linux-arm64-gnu": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", - "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", + "integrity": "sha1-QZ/Wv2Es80jxBSjLzZTrq5YH2NE=", "cpu": [ "arm64" ], @@ -1289,8 +1082,7 @@ }, "node_modules/@rolldown/binding-linux-arm64-musl": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", - "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", + "integrity": "sha1-/MaRhpa7doRId+HkkwoY/Q03QGk=", "cpu": [ "arm64" ], @@ -1306,8 +1098,7 @@ }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", - "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", + "integrity": "sha1-Mq7LfI2uXU8qjN5XoFjshpkVQvg=", "cpu": [ "ppc64" ], @@ -1323,8 +1114,7 @@ }, "node_modules/@rolldown/binding-linux-s390x-gnu": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", - "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", + "integrity": "sha1-vtk0bqgea7i5PPEfXYi3fbiQt2M=", "cpu": [ "s390x" ], @@ -1340,8 +1130,7 @@ }, "node_modules/@rolldown/binding-linux-x64-gnu": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", - "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", + "integrity": "sha1-ZMLSb3Xf/ZtaH5dVegCudyUMjLc=", "cpu": [ "x64" ], @@ -1357,8 +1146,7 @@ }, "node_modules/@rolldown/binding-linux-x64-musl": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", - "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", + "integrity": "sha1-WkUTLopHZZ7qrztUDClUqXyGD/M=", "cpu": [ "x64" ], @@ -1374,8 +1162,7 @@ }, "node_modules/@rolldown/binding-openharmony-arm64": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", - "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", + "integrity": "sha1-KQUTBoxV6EnchFejKv7h17Csswk=", "cpu": [ "arm64" ], @@ -1391,8 +1178,7 @@ }, "node_modules/@rolldown/binding-wasm32-wasi": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", - "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", + "integrity": "sha1-PZly2/GpU9PHr6pKDyDvKy458xs=", "cpu": [ "wasm32" ], @@ -1410,8 +1196,7 @@ }, "node_modules/@rolldown/binding-win32-arm64-msvc": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", - "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", + "integrity": "sha1-oASrYHoW1vA7y1VXKP+IivdXc60=", "cpu": [ "arm64" ], @@ -1427,8 +1212,7 @@ }, "node_modules/@rolldown/binding-win32-x64-msvc": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", - "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", + "integrity": "sha1-4qJbNGkaHMihIJ195wkGMCbdDNs=", "cpu": [ "x64" ], @@ -1444,22 +1228,19 @@ }, "node_modules/@rolldown/pluginutils": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, "license": "MIT" }, "node_modules/@standard-schema/spec": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "dev": true, "license": "MIT" }, "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "version": "0.10.3", + "integrity": "sha1-AVy6np3UfOFNA9KoxdVHv7FpZl0=", "dev": true, "license": "MIT", "optional": true, @@ -1469,7 +1250,6 @@ }, "node_modules/@types/chai": { "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "license": "MIT", @@ -1480,35 +1260,30 @@ }, "node_modules/@types/deep-eql": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", "dev": true, "license": "MIT" }, "node_modules/@types/estree": { "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, "license": "MIT" }, "node_modules/@types/json-schema": { "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true, "license": "MIT" }, "node_modules/@types/lodash": { "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==", "dev": true, "license": "MIT" }, "node_modules/@types/node": { "version": "25.3.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.3.tgz", "integrity": "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==", "dev": true, "license": "MIT", @@ -1518,7 +1293,6 @@ }, "node_modules/@types/ws": { "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "dev": true, "license": "MIT", @@ -1528,7 +1302,6 @@ }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz", "integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==", "dev": true, "license": "MIT", @@ -1557,7 +1330,6 @@ }, "node_modules/@typescript-eslint/parser": { "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", "dev": true, "license": "MIT", @@ -1582,7 +1354,6 @@ }, "node_modules/@typescript-eslint/project-service": { "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz", "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==", "dev": true, "license": "MIT", @@ -1604,7 +1375,6 @@ }, "node_modules/@typescript-eslint/scope-manager": { "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz", "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==", "dev": true, "license": "MIT", @@ -1622,7 +1392,6 @@ }, "node_modules/@typescript-eslint/tsconfig-utils": { "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz", "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==", "dev": true, "license": "MIT", @@ -1639,7 +1408,6 @@ }, "node_modules/@typescript-eslint/type-utils": { "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz", "integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==", "dev": true, "license": "MIT", @@ -1664,7 +1432,6 @@ }, "node_modules/@typescript-eslint/types": { "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", "dev": true, "license": "MIT", @@ -1678,7 +1445,6 @@ }, "node_modules/@typescript-eslint/typescript-estree": { "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz", "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==", "dev": true, "license": "MIT", @@ -1706,7 +1472,6 @@ }, "node_modules/@typescript-eslint/utils": { "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz", "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==", "dev": true, "license": "MIT", @@ -1730,7 +1495,6 @@ }, "node_modules/@typescript-eslint/visitor-keys": { "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz", "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==", "dev": true, "license": "MIT", @@ -1748,7 +1512,6 @@ }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, "license": "Apache-2.0", @@ -1761,7 +1524,6 @@ }, "node_modules/@vitest/expect": { "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.8.tgz", "integrity": "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==", "dev": true, "license": "MIT", @@ -1779,7 +1541,6 @@ }, "node_modules/@vitest/mocker": { "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.8.tgz", "integrity": "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==", "dev": true, "license": "MIT", @@ -1806,7 +1567,6 @@ }, "node_modules/@vitest/pretty-format": { "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.8.tgz", "integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==", "dev": true, "license": "MIT", @@ -1819,7 +1579,6 @@ }, "node_modules/@vitest/runner": { "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.8.tgz", "integrity": "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==", "dev": true, "license": "MIT", @@ -1833,7 +1592,6 @@ }, "node_modules/@vitest/snapshot": { "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.8.tgz", "integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==", "dev": true, "license": "MIT", @@ -1849,7 +1607,6 @@ }, "node_modules/@vitest/spy": { "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.8.tgz", "integrity": "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==", "dev": true, "license": "MIT", @@ -1859,7 +1616,6 @@ }, "node_modules/@vitest/utils": { "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.8.tgz", "integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==", "dev": true, "license": "MIT", @@ -1874,7 +1630,6 @@ }, "node_modules/abort-controller": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", "dev": true, "license": "MIT", @@ -1887,7 +1642,6 @@ }, "node_modules/acorn": { "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", @@ -1900,7 +1654,6 @@ }, "node_modules/acorn-jsx": { "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, "license": "MIT", @@ -1910,7 +1663,6 @@ }, "node_modules/ajv": { "version": "6.15.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", @@ -1927,14 +1679,12 @@ }, "node_modules/argparse": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, "license": "Python-2.0" }, "node_modules/assertion-error": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, "license": "MIT", @@ -1944,14 +1694,12 @@ }, "node_modules/balanced-match": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, "license": "MIT" }, "node_modules/base64-js": { "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true, "funding": [ @@ -1972,7 +1720,6 @@ }, "node_modules/brace-expansion": { "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", @@ -1983,14 +1730,12 @@ }, "node_modules/browser-or-node": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-3.0.0.tgz", "integrity": "sha512-iczIdVJzGEYhP5DqQxYM9Hh7Ztpqqi+CXZpSmX8ALFs9ecXkQIeqRyM6TfxEfMVpwhl3dSuDvxdzzo9sUOIVBQ==", "dev": true, "license": "MIT" }, "node_modules/buffer": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "dev": true, "funding": [ @@ -2015,7 +1760,6 @@ }, "node_modules/callsites": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, "license": "MIT", @@ -2025,7 +1769,6 @@ }, "node_modules/chai": { "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true, "license": "MIT", @@ -2035,7 +1778,6 @@ }, "node_modules/chalk": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", @@ -2052,7 +1794,6 @@ }, "node_modules/chalk/node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", @@ -2066,16 +1807,23 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/chownr": { + "version": "3.0.0", + "integrity": "sha1-mFXmTs0kCpzEJnzopKpdJKHaFeQ=", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/collection-utils": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collection-utils/-/collection-utils-1.0.1.tgz", "integrity": "sha512-LA2YTIlR7biSpXkKYwwuzGjwL5rjWEZVOSnvdUc7gObvWe4WkjxOpfrdhoP7Hs09YWDVfg0Mal9BpAqLfVEzQg==", "dev": true, "license": "Apache-2.0" }, "node_modules/color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", @@ -2088,28 +1836,24 @@ }, "node_modules/color-name": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, "license": "MIT" }, "node_modules/concat-map": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true, "license": "MIT" }, "node_modules/convert-source-map": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, "license": "MIT" }, "node_modules/cross-fetch": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz", "integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==", "dev": true, "license": "MIT", @@ -2119,7 +1863,6 @@ }, "node_modules/cross-spawn": { "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", @@ -2134,7 +1877,6 @@ }, "node_modules/debug": { "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", @@ -2152,15 +1894,14 @@ }, "node_modules/deep-is": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true, "license": "MIT" }, "node_modules/detect-libc": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -2168,14 +1909,12 @@ }, "node_modules/es-module-lexer": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", "dev": true, "license": "MIT" }, "node_modules/esbuild": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", "dev": true, "hasInstallScript": true, @@ -2217,7 +1956,6 @@ }, "node_modules/escape-string-regexp": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "license": "MIT", @@ -2230,7 +1968,6 @@ }, "node_modules/eslint": { "version": "9.39.2", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", @@ -2290,7 +2027,6 @@ }, "node_modules/eslint-scope": { "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", @@ -2307,7 +2043,6 @@ }, "node_modules/eslint-visitor-keys": { "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "license": "Apache-2.0", @@ -2320,7 +2055,6 @@ }, "node_modules/eslint/node_modules/eslint-visitor-keys": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", @@ -2333,7 +2067,6 @@ }, "node_modules/eslint/node_modules/ignore": { "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", @@ -2343,7 +2076,6 @@ }, "node_modules/eslint/node_modules/minimatch": { "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", @@ -2356,7 +2088,6 @@ }, "node_modules/espree": { "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", @@ -2374,7 +2105,6 @@ }, "node_modules/espree/node_modules/eslint-visitor-keys": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", @@ -2387,7 +2117,6 @@ }, "node_modules/esquery": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, "license": "BSD-3-Clause", @@ -2400,7 +2129,6 @@ }, "node_modules/esrecurse": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "license": "BSD-2-Clause", @@ -2413,7 +2141,6 @@ }, "node_modules/estraverse": { "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "license": "BSD-2-Clause", @@ -2423,7 +2150,6 @@ }, "node_modules/estree-walker": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, "license": "MIT", @@ -2433,7 +2159,6 @@ }, "node_modules/esutils": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, "license": "BSD-2-Clause", @@ -2443,7 +2168,6 @@ }, "node_modules/event-target-shim": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", "dev": true, "license": "MIT", @@ -2453,7 +2177,6 @@ }, "node_modules/events": { "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true, "license": "MIT", @@ -2463,7 +2186,6 @@ }, "node_modules/expect-type": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", "dev": true, "license": "Apache-2.0", @@ -2473,28 +2195,24 @@ }, "node_modules/fast-deep-equal": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true, "license": "MIT" }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true, "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, "license": "MIT" }, "node_modules/fdir": { "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", @@ -2512,7 +2230,6 @@ }, "node_modules/file-entry-cache": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", @@ -2525,7 +2242,6 @@ }, "node_modules/find-up": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", @@ -2542,7 +2258,6 @@ }, "node_modules/flat-cache": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", @@ -2556,14 +2271,12 @@ }, "node_modules/flatted": { "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, "node_modules/fsevents": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, @@ -2578,7 +2291,6 @@ }, "node_modules/glob": { "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "dev": true, "license": "BlueOak-1.0.0", @@ -2596,7 +2308,6 @@ }, "node_modules/glob-parent": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "license": "ISC", @@ -2609,7 +2320,6 @@ }, "node_modules/globals": { "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, "license": "MIT", @@ -2622,7 +2332,6 @@ }, "node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", @@ -2632,7 +2341,6 @@ }, "node_modules/ieee754": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "dev": true, "funding": [ @@ -2653,7 +2361,6 @@ }, "node_modules/ignore": { "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, "license": "MIT", @@ -2663,7 +2370,6 @@ }, "node_modules/import-fresh": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, "license": "MIT", @@ -2680,7 +2386,6 @@ }, "node_modules/imurmurhash": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "license": "MIT", @@ -2690,7 +2395,6 @@ }, "node_modules/is-extglob": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "license": "MIT", @@ -2700,7 +2404,6 @@ }, "node_modules/is-glob": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "license": "MIT", @@ -2713,29 +2416,25 @@ }, "node_modules/is-url": { "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", "dev": true, "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, "license": "ISC" }, "node_modules/js-base64": { "version": "3.7.8", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz", "integrity": "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==", "dev": true, "license": "BSD-3-Clause" }, "node_modules/js-yaml": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", - "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "version": "4.3.2", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", "dev": true, "funding": [ { @@ -2757,21 +2456,18 @@ }, "node_modules/json-buffer": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true, "license": "MIT" }, "node_modules/json-schema": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", "dev": true, "license": "(AFL-2.1 OR BSD-3-Clause)" }, "node_modules/json-schema-to-typescript": { "version": "15.0.4", - "resolved": "https://registry.npmjs.org/json-schema-to-typescript/-/json-schema-to-typescript-15.0.4.tgz", "integrity": "sha512-Su9oK8DR4xCmDsLlyvadkXzX6+GGXJpbhwoLtOGArAG61dvbW4YQmSEno2y66ahpIdmLMg6YUf/QHLgiwvkrHQ==", "dev": true, "license": "MIT", @@ -2795,21 +2491,18 @@ }, "node_modules/json-schema-traverse": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true, "license": "MIT" }, "node_modules/keyv": { "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "license": "MIT", @@ -2819,7 +2512,6 @@ }, "node_modules/koffi": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/koffi/-/koffi-3.1.0.tgz", "integrity": "sha512-0mCvdjTJBXioiaKNz0vajAEdWtfM5qyhVXSq+wQrrU3odzNvl/J7Cqna79QpNo9mfoKpQgGsyFFDRtDACCwGrQ==", "hasInstallScript": true, "license": "MIT", @@ -2845,7 +2537,6 @@ }, "node_modules/levn": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "license": "MIT", @@ -2859,7 +2550,6 @@ }, "node_modules/lightningcss": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "dev": true, "license": "MPL-2.0", @@ -2889,8 +2579,7 @@ }, "node_modules/lightningcss-android-arm64": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "integrity": "sha1-8DOIURbf79nG9UeHUj41FLYeGWg=", "cpu": [ "arm64" ], @@ -2910,7 +2599,6 @@ }, "node_modules/lightningcss-darwin-arm64": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", "cpu": [ "arm64" @@ -2931,8 +2619,7 @@ }, "node_modules/lightningcss-darwin-x64": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "integrity": "sha1-NfPpczLRMLnKGB4RtWje1q68bV4=", "cpu": [ "x64" ], @@ -2952,8 +2639,7 @@ }, "node_modules/lightningcss-freebsd-x64": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "integrity": "sha1-l3enZHK2Ttb/lDQq1kx7r9eUpXU=", "cpu": [ "x64" ], @@ -2973,8 +2659,7 @@ }, "node_modules/lightningcss-linux-arm-gnueabihf": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "integrity": "sha1-E65lLhq3O5E117faFy9mbEEK1T0=", "cpu": [ "arm" ], @@ -2994,8 +2679,7 @@ }, "node_modules/lightningcss-linux-arm64-gnu": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "integrity": "sha1-QXhYeVqUWS9oASOhsfnaig4e8zU=", "cpu": [ "arm64" ], @@ -3015,8 +2699,7 @@ }, "node_modules/lightningcss-linux-arm64-musl": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "integrity": "sha1-a+NmkugQtxgECAL9gJYjz/5zITM=", "cpu": [ "arm64" ], @@ -3036,8 +2719,7 @@ }, "node_modules/lightningcss-linux-x64-gnu": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "integrity": "sha1-C3gDr06yHP043Tn+Kru1PH3QkfY=", "cpu": [ "x64" ], @@ -3057,8 +2739,7 @@ }, "node_modules/lightningcss-linux-x64-musl": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "integrity": "sha1-iNyLqGXd3bGsXvBLDxYYBEGMFjs=", "cpu": [ "x64" ], @@ -3078,8 +2759,7 @@ }, "node_modules/lightningcss-win32-arm64-msvc": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "integrity": "sha1-TzC6P6XpJfW3n5RejMDRdsOxqzg=", "cpu": [ "arm64" ], @@ -3099,8 +2779,7 @@ }, "node_modules/lightningcss-win32-x64-msvc": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "integrity": "sha1-FBqlYFZFBkkokCu0rwRfp9n0Igo=", "cpu": [ "x64" ], @@ -3120,7 +2799,6 @@ }, "node_modules/locate-path": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", @@ -3136,21 +2814,18 @@ }, "node_modules/lodash": { "version": "4.18.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "dev": true, "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true, "license": "MIT" }, "node_modules/lru-cache": { "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", "dev": true, "license": "BlueOak-1.0.0", @@ -3160,7 +2835,6 @@ }, "node_modules/magic-string": { "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", @@ -3170,7 +2844,6 @@ }, "node_modules/minimatch": { "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dev": true, "license": "BlueOak-1.0.0", @@ -3186,7 +2859,6 @@ }, "node_modules/minimatch/node_modules/balanced-match": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, "license": "MIT", @@ -3196,7 +2868,6 @@ }, "node_modules/minimatch/node_modules/brace-expansion": { "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", @@ -3209,7 +2880,6 @@ }, "node_modules/minimist": { "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, "license": "MIT", @@ -3219,7 +2889,6 @@ }, "node_modules/minipass": { "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, "license": "BlueOak-1.0.0", @@ -3227,16 +2896,26 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/minizlib": { + "version": "3.1.0", + "integrity": "sha1-atdsOo8QInybUdHJrI4wsn9aJRw=", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/ms": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, "license": "MIT" }, "node_modules/nanoid": { "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": [ @@ -3255,14 +2934,12 @@ }, "node_modules/natural-compare": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true, "license": "MIT" }, "node_modules/node-fetch": { "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dev": true, "license": "MIT", @@ -3283,7 +2960,6 @@ }, "node_modules/obug": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", "dev": true, "funding": [ @@ -3294,7 +2970,6 @@ }, "node_modules/optionator": { "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "license": "MIT", @@ -3312,7 +2987,6 @@ }, "node_modules/p-limit": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", @@ -3328,7 +3002,6 @@ }, "node_modules/p-locate": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", @@ -3344,21 +3017,18 @@ }, "node_modules/package-json-from-dist": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/pako": { "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "dev": true, "license": "(MIT AND Zlib)" }, "node_modules/parent-module": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "license": "MIT", @@ -3371,7 +3041,6 @@ }, "node_modules/path-exists": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", @@ -3381,7 +3050,6 @@ }, "node_modules/path-key": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", @@ -3391,7 +3059,6 @@ }, "node_modules/path-scurry": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", "dev": true, "license": "BlueOak-1.0.0", @@ -3408,21 +3075,18 @@ }, "node_modules/pathe": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, "license": "MIT" }, "node_modules/picocolors": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", @@ -3435,7 +3099,6 @@ }, "node_modules/pluralize": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", "dev": true, "license": "MIT", @@ -3445,7 +3108,6 @@ }, "node_modules/postcss": { "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": [ @@ -3474,7 +3136,6 @@ }, "node_modules/prelude-ls": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, "license": "MIT", @@ -3484,7 +3145,6 @@ }, "node_modules/prettier": { "version": "3.8.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", @@ -3500,7 +3160,6 @@ }, "node_modules/process": { "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", "dev": true, "license": "MIT", @@ -3510,7 +3169,6 @@ }, "node_modules/punycode": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "license": "MIT", @@ -3520,7 +3178,6 @@ }, "node_modules/quicktype-core": { "version": "23.2.6", - "resolved": "https://registry.npmjs.org/quicktype-core/-/quicktype-core-23.2.6.tgz", "integrity": "sha512-asfeSv7BKBNVb9WiYhFRBvBZHcRutPRBwJMxW0pefluK4kkKu4lv0IvZBwFKvw2XygLcL1Rl90zxWDHYgkwCmA==", "dev": true, "license": "Apache-2.0", @@ -3543,7 +3200,6 @@ }, "node_modules/readable-stream": { "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, "license": "MIT", @@ -3560,7 +3216,6 @@ }, "node_modules/resolve-from": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, "license": "MIT", @@ -3570,7 +3225,6 @@ }, "node_modules/rimraf": { "version": "6.1.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.3.tgz", "integrity": "sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==", "dev": true, "license": "BlueOak-1.0.0", @@ -3590,7 +3244,6 @@ }, "node_modules/rolldown": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", "dev": true, "license": "MIT", @@ -3624,7 +3277,6 @@ }, "node_modules/safe-buffer": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true, "funding": [ @@ -3645,7 +3297,6 @@ }, "node_modules/semver": { "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", @@ -3658,7 +3309,6 @@ }, "node_modules/shebang-command": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "license": "MIT", @@ -3671,7 +3321,6 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", @@ -3681,14 +3330,12 @@ }, "node_modules/siginfo": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true, "license": "ISC" }, "node_modules/source-map-js": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "license": "BSD-3-Clause", @@ -3698,21 +3345,18 @@ }, "node_modules/stackback": { "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "dev": true, "license": "MIT" }, "node_modules/std-env": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", "dev": true, "license": "MIT" }, "node_modules/string_decoder": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "license": "MIT", @@ -3722,7 +3366,6 @@ }, "node_modules/strip-json-comments": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, "license": "MIT", @@ -3735,7 +3378,6 @@ }, "node_modules/supports-color": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", @@ -3746,23 +3388,36 @@ "node": ">=8" } }, + "node_modules/tar": { + "version": "7.5.22", + "integrity": "sha1-ppb5mBNucUh9w/hpqFu6LGeXG6k=", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/tiny-inflate": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", "dev": true, "license": "MIT" }, "node_modules/tinybench": { "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", "dev": true, "license": "MIT" }, "node_modules/tinyexec": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", "dev": true, "license": "MIT", @@ -3772,7 +3427,6 @@ }, "node_modules/tinyglobby": { "version": "0.2.17", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", @@ -3789,7 +3443,6 @@ }, "node_modules/tinyrainbow": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", "dev": true, "license": "MIT", @@ -3799,14 +3452,12 @@ }, "node_modules/tr46": { "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "dev": true, "license": "MIT" }, "node_modules/ts-api-utils": { "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", "dev": true, "license": "MIT", @@ -3819,15 +3470,13 @@ }, "node_modules/tslib": { "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "integrity": "sha1-YS7+TtI11Wfoq6Xypfq3AoCt6D8=", "dev": true, "license": "0BSD", "optional": true }, "node_modules/tsx": { "version": "4.22.4", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", "dev": true, "license": "MIT", @@ -3846,7 +3495,6 @@ }, "node_modules/type-check": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "license": "MIT", @@ -3859,7 +3507,6 @@ }, "node_modules/typescript": { "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", @@ -3873,14 +3520,12 @@ }, "node_modules/undici-types": { "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "dev": true, "license": "MIT" }, "node_modules/unicode-properties": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", "dev": true, "license": "MIT", @@ -3891,7 +3536,6 @@ }, "node_modules/unicode-trie": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", "dev": true, "license": "MIT", @@ -3902,14 +3546,12 @@ }, "node_modules/unicode-trie/node_modules/pako": { "version": "0.2.9", - "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", "dev": true, "license": "MIT" }, "node_modules/uri-js": { "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "license": "BSD-2-Clause", @@ -3919,14 +3561,12 @@ }, "node_modules/urijs": { "version": "1.19.11", - "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.11.tgz", "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==", "dev": true, "license": "MIT" }, "node_modules/vite": { "version": "8.0.16", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", "dev": true, "license": "MIT", @@ -4004,7 +3644,6 @@ }, "node_modules/vitest": { "version": "4.1.8", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.8.tgz", "integrity": "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==", "dev": true, "license": "MIT", @@ -4094,7 +3733,6 @@ }, "node_modules/vscode-jsonrpc": { "version": "8.2.1", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.1.tgz", "integrity": "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ==", "license": "MIT", "engines": { @@ -4103,14 +3741,12 @@ }, "node_modules/webidl-conversions": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "dev": true, "license": "BSD-2-Clause" }, "node_modules/whatwg-url": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "dev": true, "license": "MIT", @@ -4121,7 +3757,6 @@ }, "node_modules/which": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "license": "ISC", @@ -4137,7 +3772,6 @@ }, "node_modules/why-is-node-running": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, "license": "MIT", @@ -4154,7 +3788,6 @@ }, "node_modules/word-wrap": { "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "license": "MIT", @@ -4164,14 +3797,12 @@ }, "node_modules/wordwrap": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "dev": true, "license": "MIT" }, "node_modules/ws": { "version": "8.21.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "dev": true, "license": "MIT", @@ -4191,9 +3822,17 @@ } } }, + "node_modules/yallist": { + "version": "5.0.0", + "integrity": "sha1-AOLeRDY57Q14/YfeDSdGn7z/tTM=", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/yaml": { "version": "2.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", "dev": true, "license": "ISC", @@ -4209,7 +3848,6 @@ }, "node_modules/yocto-queue": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "license": "MIT", @@ -4222,7 +3860,6 @@ }, "node_modules/zod": { "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", "funding": { diff --git a/nodejs/package.json b/nodejs/package.json index ca259556ba..c937d83bf7 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -5,6 +5,7 @@ "url": "https://github.com/github/copilot-sdk.git" }, "version": "0.0.0-dev", + "copilotCliVersion": "1.0.83-3", "description": "TypeScript SDK for programmatic control of GitHub Copilot CLI via JSON-RPC", "main": "./dist/cjs/index.js", "types": "./dist/index.d.ts", @@ -34,6 +35,9 @@ "scripts": { "clean": "rimraf --glob dist *.tgz", "build": "tsx esbuild-copilotsdk-nodejs.ts", + "pack:release": "tsx scripts/package-sdk.ts", + "verify:release-packages": "tsx scripts/verify-release-packages.ts", + "prepare:runtime": "tsx scripts/prepare-runtime.ts", "test": "vitest run", "test:watch": "vitest", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" --ignore-path .prettierignore", @@ -42,9 +46,10 @@ "lint:fix": "eslint --fix \"src/**/*.ts\" \"test/**/*.ts\"", "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json", "generate": "cd ../scripts/codegen && npm run generate", + "set:cli-version": "node scripts/set-cli-version.js", "update:protocol-version": "tsx scripts/update-protocol-version.ts", "prepublishOnly": "npm run build", - "package": "npm run clean && npm run build && node scripts/set-version.js && npm pack && npm version 0.0.0-dev --no-git-tag-version --allow-same-version" + "package": "npm run clean && npm run build && node scripts/set-version.js && npm run pack:release && npm version 0.0.0-dev --no-git-tag-version --allow-same-version" }, "keywords": [ "github", @@ -56,7 +61,6 @@ "author": "GitHub", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.78", "koffi": "^3.1.0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" @@ -76,6 +80,7 @@ "quicktype-core": "^23.2.6", "rimraf": "^6.1.2", "semver": "^7.7.3", + "tar": "^7.5.22", "tsx": "^4.20.6", "typescript": "^5.0.0", "vitest": "^4.0.18", diff --git a/nodejs/samples/package-lock.json b/nodejs/samples/package-lock.json index aed2d4465e..e2a9ab8a00 100644 --- a/nodejs/samples/package-lock.json +++ b/nodejs/samples/package-lock.json @@ -18,7 +18,6 @@ "version": "0.0.0-dev", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.78", "koffi": "^3.1.0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" @@ -38,6 +37,7 @@ "quicktype-core": "^23.2.6", "rimraf": "^6.1.2", "semver": "^7.7.3", + "tar": "^7.5.22", "tsx": "^4.20.6", "typescript": "^5.0.0", "vitest": "^4.0.18", @@ -47,10 +47,2535 @@ "node": "^20.19.0 || >=22.12.0" } }, + "../node_modules/@apidevtools/json-schema-ref-parser": { + "version": "11.9.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.15", + "js-yaml": "^4.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/philsturgeon" + } + }, + "../node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "../node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "../node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "../node_modules/@eslint/config-array": { + "version": "0.21.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "../node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.5", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "../node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "../node_modules/@eslint/core": { + "version": "0.17.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "../node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "../node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "../node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "../node_modules/@eslint/js": { + "version": "9.39.2", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "../node_modules/@eslint/object-schema": { + "version": "2.1.7", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "../node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "../node_modules/@glideapps/ts-necessities": { + "version": "2.2.3", + "dev": true, + "license": "MIT" + }, + "../node_modules/@humanfs/core": { + "version": "0.19.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "../node_modules/@humanfs/node": { + "version": "0.16.7", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "../node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "../node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "../node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "../node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "dev": true, + "license": "MIT" + }, + "../node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "dev": true, + "license": "MIT" + }, + "../node_modules/@koromix/koffi-darwin-arm64": { + "version": "3.1.0", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://liberapay.com/Koromix" + } + }, + "../node_modules/@oxc-project/types": { + "version": "0.133.0", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "../node_modules/@platformatic/vfs": { + "version": "0.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 22" + } + }, + "../node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.3", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "../node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "../node_modules/@standard-schema/spec": { + "version": "1.1.0", + "dev": true, + "license": "MIT" + }, + "../node_modules/@types/chai": { + "version": "5.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "../node_modules/@types/deep-eql": { + "version": "4.0.2", + "dev": true, + "license": "MIT" + }, + "../node_modules/@types/estree": { + "version": "1.0.8", + "dev": true, + "license": "MIT" + }, + "../node_modules/@types/json-schema": { + "version": "7.0.15", + "dev": true, + "license": "MIT" + }, + "../node_modules/@types/lodash": { + "version": "4.17.21", + "dev": true, + "license": "MIT" + }, + "../node_modules/@types/node": { + "version": "25.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "../node_modules/@types/ws": { + "version": "8.18.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "../node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.56.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/type-utils": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.56.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "../node_modules/@typescript-eslint/parser": { + "version": "8.56.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "../node_modules/@typescript-eslint/project-service": { + "version": "8.56.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.56.1", + "@typescript-eslint/types": "^8.56.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "../node_modules/@typescript-eslint/scope-manager": { + "version": "8.56.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "../node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.56.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "../node_modules/@typescript-eslint/type-utils": { + "version": "8.56.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "../node_modules/@typescript-eslint/types": { + "version": "8.56.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "../node_modules/@typescript-eslint/typescript-estree": { + "version": "8.56.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.56.1", + "@typescript-eslint/tsconfig-utils": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "../node_modules/@typescript-eslint/utils": { + "version": "8.56.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "../node_modules/@typescript-eslint/visitor-keys": { + "version": "8.56.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "../node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "../node_modules/@vitest/expect": { + "version": "4.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.8", + "@vitest/utils": "4.1.8", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "../node_modules/@vitest/mocker": { + "version": "4.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.8", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "../node_modules/@vitest/pretty-format": { + "version": "4.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "../node_modules/@vitest/runner": { + "version": "4.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.8", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "../node_modules/@vitest/snapshot": { + "version": "4.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.8", + "@vitest/utils": "4.1.8", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "../node_modules/@vitest/spy": { + "version": "4.1.8", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "../node_modules/@vitest/utils": { + "version": "4.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.8", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "../node_modules/abort-controller": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "../node_modules/acorn": { + "version": "8.15.0", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "../node_modules/acorn-jsx": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "../node_modules/ajv": { + "version": "6.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "../node_modules/argparse": { + "version": "2.0.1", + "dev": true, + "license": "Python-2.0" + }, + "../node_modules/assertion-error": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "../node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "../node_modules/base64-js": { + "version": "1.5.1", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "../node_modules/brace-expansion": { + "version": "1.1.16", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "../node_modules/browser-or-node": { + "version": "3.0.0", + "dev": true, + "license": "MIT" + }, + "../node_modules/buffer": { + "version": "6.0.3", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "../node_modules/callsites": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "../node_modules/chai": { + "version": "6.2.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "../node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "../node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "../node_modules/chownr": { + "version": "3.0.0", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "../node_modules/collection-utils": { + "version": "1.0.1", + "dev": true, + "license": "Apache-2.0" + }, + "../node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "../node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "../node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "../node_modules/convert-source-map": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "../node_modules/cross-fetch": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "node-fetch": "^2.7.0" + } + }, + "../node_modules/cross-spawn": { + "version": "7.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "../node_modules/debug": { + "version": "4.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "../node_modules/deep-is": { + "version": "0.1.4", + "dev": true, + "license": "MIT" + }, + "../node_modules/detect-libc": { + "version": "2.1.2", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "../node_modules/es-module-lexer": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "../node_modules/esbuild": { + "version": "0.28.1", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "../node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "../node_modules/eslint": { + "version": "9.39.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "../node_modules/eslint-scope": { + "version": "8.4.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "../node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "../node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "../node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "../node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "../node_modules/espree": { + "version": "10.4.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "../node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "../node_modules/esquery": { + "version": "1.6.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "../node_modules/esrecurse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "../node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "../node_modules/estree-walker": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "../node_modules/esutils": { + "version": "2.0.3", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "../node_modules/event-target-shim": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "../node_modules/events": { + "version": "3.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "../node_modules/expect-type": { + "version": "1.3.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "../node_modules/fast-deep-equal": { + "version": "3.1.3", + "dev": true, + "license": "MIT" + }, + "../node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "../node_modules/fast-levenshtein": { + "version": "2.0.6", + "dev": true, + "license": "MIT" + }, + "../node_modules/fdir": { + "version": "6.5.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "../node_modules/file-entry-cache": { + "version": "8.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "../node_modules/find-up": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "../node_modules/flat-cache": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "../node_modules/flatted": { + "version": "3.4.2", + "dev": true, + "license": "ISC" + }, + "../node_modules/fsevents": { + "version": "2.3.3", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "../node_modules/glob": { + "version": "13.0.6", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "../node_modules/glob-parent": { + "version": "6.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "../node_modules/globals": { + "version": "14.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "../node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "../node_modules/ieee754": { + "version": "1.2.1", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "../node_modules/ignore": { + "version": "7.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "../node_modules/import-fresh": { + "version": "3.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "../node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "../node_modules/is-extglob": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "../node_modules/is-glob": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "../node_modules/is-url": { + "version": "1.2.4", + "dev": true, + "license": "MIT" + }, + "../node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "../node_modules/js-base64": { + "version": "3.7.8", + "dev": true, + "license": "BSD-3-Clause" + }, + "../node_modules/js-yaml": { + "version": "4.2.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "../node_modules/json-buffer": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "../node_modules/json-schema": { + "version": "0.4.0", + "dev": true, + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "../node_modules/json-schema-to-typescript": { + "version": "15.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^11.5.5", + "@types/json-schema": "^7.0.15", + "@types/lodash": "^4.17.7", + "is-glob": "^4.0.3", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "minimist": "^1.2.8", + "prettier": "^3.2.5", + "tinyglobby": "^0.2.9" + }, + "bin": { + "json2ts": "dist/src/cli.js" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "../node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "../node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "../node_modules/keyv": { + "version": "4.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "../node_modules/koffi": { + "version": "3.1.0", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "url": "https://liberapay.com/Koromix" + }, + "optionalDependencies": { + "@koromix/koffi-darwin-arm64": "3.1.0", + "@koromix/koffi-darwin-x64": "3.1.0", + "@koromix/koffi-freebsd-arm64": "3.1.0", + "@koromix/koffi-freebsd-ia32": "3.1.0", + "@koromix/koffi-freebsd-x64": "3.1.0", + "@koromix/koffi-linux-arm64": "3.1.0", + "@koromix/koffi-linux-ia32": "3.1.0", + "@koromix/koffi-linux-loong64": "3.1.0", + "@koromix/koffi-linux-riscv64": "3.1.0", + "@koromix/koffi-linux-x64": "3.1.0", + "@koromix/koffi-openbsd-ia32": "3.1.0", + "@koromix/koffi-openbsd-x64": "3.1.0", + "@koromix/koffi-win32-ia32": "3.1.0", + "@koromix/koffi-win32-x64": "3.1.0" + } + }, + "../node_modules/levn": { + "version": "0.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "../node_modules/lightningcss": { + "version": "1.32.0", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "../node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "../node_modules/locate-path": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "../node_modules/lodash": { + "version": "4.18.1", + "dev": true, + "license": "MIT" + }, + "../node_modules/lodash.merge": { + "version": "4.6.2", + "dev": true, + "license": "MIT" + }, + "../node_modules/lru-cache": { + "version": "11.2.6", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "../node_modules/magic-string": { + "version": "0.30.21", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "../node_modules/minimatch": { + "version": "10.2.4", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "../node_modules/minimatch/node_modules/balanced-match": { + "version": "4.0.4", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "../node_modules/minimatch/node_modules/brace-expansion": { + "version": "5.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "../node_modules/minimist": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "../node_modules/minipass": { + "version": "7.1.3", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "../node_modules/minizlib": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "../node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "../node_modules/nanoid": { + "version": "3.3.17", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "../node_modules/natural-compare": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "../node_modules/node-fetch": { + "version": "2.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "../node_modules/obug": { + "version": "2.1.1", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "../node_modules/optionator": { + "version": "0.9.4", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "../node_modules/p-limit": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "../node_modules/p-locate": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "../node_modules/package-json-from-dist": { + "version": "1.0.1", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "../node_modules/pako": { + "version": "1.0.11", + "dev": true, + "license": "(MIT AND Zlib)" + }, + "../node_modules/parent-module": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "../node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "../node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "../node_modules/path-scurry": { + "version": "2.0.2", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "../node_modules/pathe": { + "version": "2.0.3", + "dev": true, + "license": "MIT" + }, + "../node_modules/picocolors": { + "version": "1.1.1", + "dev": true, + "license": "ISC" + }, + "../node_modules/picomatch": { + "version": "4.0.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "../node_modules/pluralize": { + "version": "8.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "../node_modules/postcss": { + "version": "8.5.25", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "../node_modules/prelude-ls": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "../node_modules/prettier": { + "version": "3.8.1", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "../node_modules/process": { + "version": "0.11.10", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "../node_modules/punycode": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "../node_modules/quicktype-core": { + "version": "23.2.6", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@glideapps/ts-necessities": "2.2.3", + "browser-or-node": "^3.0.0", + "collection-utils": "^1.0.1", + "cross-fetch": "^4.0.0", + "is-url": "^1.2.4", + "js-base64": "^3.7.7", + "lodash": "^4.17.21", + "pako": "^1.0.6", + "pluralize": "^8.0.0", + "readable-stream": "4.5.2", + "unicode-properties": "^1.4.1", + "urijs": "^1.19.1", + "wordwrap": "^1.0.0", + "yaml": "^2.4.1" + } + }, + "../node_modules/readable-stream": { + "version": "4.5.2", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "../node_modules/resolve-from": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "../node_modules/rimraf": { + "version": "6.1.3", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "glob": "^13.0.3", + "package-json-from-dist": "^1.0.1" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "../node_modules/rolldown": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.133.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.3", + "@rolldown/binding-darwin-arm64": "1.0.3", + "@rolldown/binding-darwin-x64": "1.0.3", + "@rolldown/binding-freebsd-x64": "1.0.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", + "@rolldown/binding-linux-arm64-gnu": "1.0.3", + "@rolldown/binding-linux-arm64-musl": "1.0.3", + "@rolldown/binding-linux-ppc64-gnu": "1.0.3", + "@rolldown/binding-linux-s390x-gnu": "1.0.3", + "@rolldown/binding-linux-x64-gnu": "1.0.3", + "@rolldown/binding-linux-x64-musl": "1.0.3", + "@rolldown/binding-openharmony-arm64": "1.0.3", + "@rolldown/binding-wasm32-wasi": "1.0.3", + "@rolldown/binding-win32-arm64-msvc": "1.0.3", + "@rolldown/binding-win32-x64-msvc": "1.0.3" + } + }, + "../node_modules/safe-buffer": { + "version": "5.2.1", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "../node_modules/semver": { + "version": "7.7.3", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "../node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "../node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "../node_modules/siginfo": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "../node_modules/source-map-js": { + "version": "1.2.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "../node_modules/stackback": { + "version": "0.0.2", + "dev": true, + "license": "MIT" + }, + "../node_modules/std-env": { + "version": "4.1.0", + "dev": true, + "license": "MIT" + }, + "../node_modules/string_decoder": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "../node_modules/strip-json-comments": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "../node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "../node_modules/tar": { + "version": "7.5.22", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "../node_modules/tiny-inflate": { + "version": "1.0.3", + "dev": true, + "license": "MIT" + }, + "../node_modules/tinybench": { + "version": "2.9.0", + "dev": true, + "license": "MIT" + }, + "../node_modules/tinyexec": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "../node_modules/tinyglobby": { + "version": "0.2.17", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "../node_modules/tinyrainbow": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "../node_modules/tr46": { + "version": "0.0.3", + "dev": true, + "license": "MIT" + }, + "../node_modules/ts-api-utils": { + "version": "2.4.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "../node_modules/tsx": { + "version": "4.22.4", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "../node_modules/type-check": { + "version": "0.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "../node_modules/typescript": { + "version": "5.9.3", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "../node_modules/undici-types": { + "version": "7.18.2", + "dev": true, + "license": "MIT" + }, + "../node_modules/unicode-properties": { + "version": "1.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" + } + }, + "../node_modules/unicode-trie": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, + "../node_modules/unicode-trie/node_modules/pako": { + "version": "0.2.9", + "dev": true, + "license": "MIT" + }, + "../node_modules/uri-js": { + "version": "4.4.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "../node_modules/urijs": { + "version": "1.19.11", + "dev": true, + "license": "MIT" + }, + "../node_modules/vite": { + "version": "8.0.16", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "1.0.3", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "../node_modules/vitest": { + "version": "4.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.8", + "@vitest/mocker": "4.1.8", + "@vitest/pretty-format": "4.1.8", + "@vitest/runner": "4.1.8", + "@vitest/snapshot": "4.1.8", + "@vitest/spy": "4.1.8", + "@vitest/utils": "4.1.8", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.8", + "@vitest/browser-preview": "4.1.8", + "@vitest/browser-webdriverio": "4.1.8", + "@vitest/coverage-istanbul": "4.1.8", + "@vitest/coverage-v8": "4.1.8", + "@vitest/ui": "4.1.8", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "../node_modules/vscode-jsonrpc": { + "version": "8.2.1", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "../node_modules/webidl-conversions": { + "version": "3.0.1", + "dev": true, + "license": "BSD-2-Clause" + }, + "../node_modules/whatwg-url": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "../node_modules/which": { + "version": "2.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "../node_modules/why-is-node-running": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "../node_modules/word-wrap": { + "version": "1.2.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "../node_modules/wordwrap": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "../node_modules/ws": { + "version": "8.21.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "../node_modules/yallist": { + "version": "5.0.0", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "../node_modules/yaml": { + "version": "2.9.0", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "../node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "../node_modules/zod": { + "version": "4.3.6", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", - "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "version": "0.28.2", + "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha1-v24QMDvPLnxoaXX6Uvk37Cco2Lw=", "cpu": [ "ppc64" ], @@ -65,9 +2590,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", - "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "version": "0.28.2", + "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha1-LYTs5qTiaE2SvibuE9QnV9gxw4E=", "cpu": [ "arm" ], @@ -82,9 +2607,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", - "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "version": "0.28.2", + "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha1-DGJGvI0sTRcqrC2z+xGQ1yvWVQQ=", "cpu": [ "arm64" ], @@ -99,9 +2624,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", - "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "version": "0.28.2", + "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha1-/DjU1jWNjcHPU/CfdYn+Q262SAE=", "cpu": [ "x64" ], @@ -116,9 +2641,7 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", - "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "version": "0.28.2", "cpu": [ "arm64" ], @@ -133,9 +2656,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", - "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "version": "0.28.2", + "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha1-UQFHwFWnlViNu+FP1rG4rQovMN4=", "cpu": [ "x64" ], @@ -150,9 +2673,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", - "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "version": "0.28.2", + "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha1-CTuSAOzwsRW6Tl4kinSFycX4vV4=", "cpu": [ "arm64" ], @@ -167,9 +2690,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", - "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha1-C+Irbfkl0hPoQeqHEjr134Cw+vc=", "cpu": [ "x64" ], @@ -184,9 +2707,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", - "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "version": "0.28.2", + "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha1-vrEq1yuE9y0oSIzBuO6ffrFB11M=", "cpu": [ "arm" ], @@ -201,9 +2724,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", - "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha1-G9vGUc2pupmVxT7ZxxzqplCUdi0=", "cpu": [ "arm64" ], @@ -218,9 +2741,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", - "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "version": "0.28.2", + "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha1-uB+dVVKbRcIGpGoTghSxqmh5aWs=", "cpu": [ "ia32" ], @@ -235,9 +2758,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", - "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "version": "0.28.2", + "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha1-WYZnJBoEyZt27W75QKxQA4xBn5g=", "cpu": [ "loong64" ], @@ -252,9 +2775,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", - "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "version": "0.28.2", + "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha1-HFHrnOqQP1PZe1rzsYQdtw9Vlso=", "cpu": [ "mips64el" ], @@ -269,9 +2792,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", - "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "version": "0.28.2", + "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha1-Y91h8XzrMagSJ/QT/qyKcbwsUfI=", "cpu": [ "ppc64" ], @@ -286,9 +2809,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", - "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "version": "0.28.2", + "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha1-N2Owj95c8lqx+suOd1Lt/kX7/Cc=", "cpu": [ "riscv64" ], @@ -303,9 +2826,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", - "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "version": "0.28.2", + "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha1-GhN/8pOoKQbrMXY4W9fo4OXPt8s=", "cpu": [ "s390x" ], @@ -320,9 +2843,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", - "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "version": "0.28.2", + "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha1-Jos2IRwUbKVPj+EsV4qNbviXlIU=", "cpu": [ "x64" ], @@ -337,9 +2860,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", - "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha1-Ilca2VHWK7aszILY0frVyMGsC6E=", "cpu": [ "arm64" ], @@ -354,9 +2877,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", - "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "version": "0.28.2", + "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha1-QvzFcpfrCgyj9fxHUpH0waP3wN4=", "cpu": [ "x64" ], @@ -371,9 +2894,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", - "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha1-nrMq8QSsPaz07coB9ZZmSqsMc+8=", "cpu": [ "arm64" ], @@ -388,9 +2911,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", - "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "version": "0.28.2", + "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha1-/r7SQC1giCJekfIPtM4lIq0KTv0=", "cpu": [ "x64" ], @@ -405,9 +2928,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", - "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha1-hWQcPUZkKL+8zqXyHCaDZmP+9c4=", "cpu": [ "arm64" ], @@ -422,9 +2945,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", - "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "version": "0.28.2", + "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha1-pzb52JYkgQRfxMPlT1R58iyHD7Q=", "cpu": [ "x64" ], @@ -439,9 +2962,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", - "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "version": "0.28.2", + "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha1-7lq0D60YYgG2UqM/il6xSenkJTI=", "cpu": [ "arm64" ], @@ -456,9 +2979,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", - "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "version": "0.28.2", + "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha1-xA0optmaEn2mcR8q/XSxHLY7Bqc=", "cpu": [ "ia32" ], @@ -473,9 +2996,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", - "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha1-shr/uATMFnwTPZX0WzodwTI7moc=", "cpu": [ "x64" ], @@ -494,9 +3017,7 @@ "link": true }, "node_modules/@types/node": { - "version": "22.19.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.11.tgz", - "integrity": "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==", + "version": "22.20.1", "dev": true, "license": "MIT", "dependencies": { @@ -504,9 +3025,7 @@ } }, "node_modules/esbuild": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", - "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "version": "0.28.2", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -517,40 +3036,37 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.1", - "@esbuild/android-arm": "0.28.1", - "@esbuild/android-arm64": "0.28.1", - "@esbuild/android-x64": "0.28.1", - "@esbuild/darwin-arm64": "0.28.1", - "@esbuild/darwin-x64": "0.28.1", - "@esbuild/freebsd-arm64": "0.28.1", - "@esbuild/freebsd-x64": "0.28.1", - "@esbuild/linux-arm": "0.28.1", - "@esbuild/linux-arm64": "0.28.1", - "@esbuild/linux-ia32": "0.28.1", - "@esbuild/linux-loong64": "0.28.1", - "@esbuild/linux-mips64el": "0.28.1", - "@esbuild/linux-ppc64": "0.28.1", - "@esbuild/linux-riscv64": "0.28.1", - "@esbuild/linux-s390x": "0.28.1", - "@esbuild/linux-x64": "0.28.1", - "@esbuild/netbsd-arm64": "0.28.1", - "@esbuild/netbsd-x64": "0.28.1", - "@esbuild/openbsd-arm64": "0.28.1", - "@esbuild/openbsd-x64": "0.28.1", - "@esbuild/openharmony-arm64": "0.28.1", - "@esbuild/sunos-x64": "0.28.1", - "@esbuild/win32-arm64": "0.28.1", - "@esbuild/win32-ia32": "0.28.1", - "@esbuild/win32-x64": "0.28.1" + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" } }, "node_modules/fsevents": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, - "hasInstallScript": true, "license": "MIT", "optional": true, "os": [ @@ -561,9 +3077,7 @@ } }, "node_modules/tsx": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", - "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", + "version": "4.23.12", "dev": true, "license": "MIT", "dependencies": { @@ -581,8 +3095,6 @@ }, "node_modules/undici-types": { "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, "license": "MIT" } diff --git a/nodejs/scripts/package-sdk.ts b/nodejs/scripts/package-sdk.ts new file mode 100644 index 0000000000..fbdd7c6092 --- /dev/null +++ b/nodejs/scripts/package-sdk.ts @@ -0,0 +1,73 @@ +import { execFileSync } from "node:child_process"; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { COPILOT_CLI_VERSION } from "../src/cliVersion.js"; +import { + getRuntimePackageName, + materializeRuntimeBundle, + RUNTIME_PLATFORMS, +} from "../src/runtimeArtifacts.js"; +import { ensureCopilotPackage } from "./releaseArtifacts.js"; + +const nodeRoot = join(dirname(fileURLToPath(import.meta.url)), ".."); +const packagePath = join(nodeRoot, "package.json"); +const originalPackage = readFileSync(packagePath, "utf8"); +const packageJson = JSON.parse(originalPackage); +const sdkVersion = packageJson.version; +const npmCliPath = process.env.npm_execpath; +if (!npmCliPath) { + throw new Error("package-sdk.ts must be run through an npm script"); +} +const requestedPlatforms = process.env.COPILOT_SDK_RUNTIME_PLATFORMS?.split(",").filter(Boolean); +const platforms = requestedPlatforms ?? [...RUNTIME_PLATFORMS]; +const stagingRoot = mkdtempSync(join(tmpdir(), "copilot-sdk-platform-packages-")); + +try { + const optionalDependencies: Record = {}; + for (const platform of platforms) { + if (!(RUNTIME_PLATFORMS as readonly string[]).includes(platform)) { + throw new Error(`Unsupported runtime platform: ${platform}`); + } + const releasePackage = await ensureCopilotPackage(COPILOT_CLI_VERSION, { platform }); + const runtimeWrapper = materializeRuntimeBundle( + { packageRoot: releasePackage, platform }, + stagingRoot, + platform + ); + const runtimeRoot = resolve(dirname(runtimeWrapper), "..", ".."); + const packageName = getRuntimePackageName(platform); + const [osName, cpu] = platform.replace("linuxmusl", "linux").split("-"); + const runtimePackage = { + name: packageName, + version: sdkVersion, + description: `Platform runtime for @github/copilot-sdk (${platform})`, + license: "MIT", + os: [osName], + cpu: [cpu], + ...(platform.startsWith("linux") + ? { libc: [platform.startsWith("linuxmusl") ? "musl" : "glibc"] } + : {}), + }; + writeFileSync( + join(runtimeRoot, "package.json"), + `${JSON.stringify(runtimePackage, null, 4)}\n` + ); + execFileSync(process.execPath, [npmCliPath, "pack", runtimeRoot, "--pack-destination", nodeRoot], { + stdio: "inherit", + }); + optionalDependencies[packageName] = sdkVersion; + } + + packageJson.optionalDependencies = optionalDependencies; + writeFileSync(packagePath, `${JSON.stringify(packageJson, null, 4)}\n`); + execFileSync( + process.execPath, + [npmCliPath, "pack", nodeRoot, "--pack-destination", nodeRoot], + { stdio: "inherit" } + ); +} finally { + writeFileSync(packagePath, originalPackage); + rmSync(stagingRoot, { recursive: true, force: true }); +} diff --git a/nodejs/scripts/prepare-runtime.ts b/nodejs/scripts/prepare-runtime.ts new file mode 100644 index 0000000000..1cdfc0a6e7 --- /dev/null +++ b/nodejs/scripts/prepare-runtime.ts @@ -0,0 +1,16 @@ +import { join } from "node:path"; +import { getRuntimePlatform, materializeRuntimeBundle } from "../src/runtimeArtifacts.js"; +import { COPILOT_CLI_VERSION } from "../src/cliVersion.js"; +import { ensureCopilotPackage } from "./releaseArtifacts.js"; + +const [option] = process.argv.slice(2); +const platform = getRuntimePlatform(); +const packageRoot = await ensureCopilotPackage(COPILOT_CLI_VERSION, { platform }); +if (option === "--print-legacy-path") { + process.stdout.write(`${join(packageRoot, "app.js")}\n`); +} else if (option === "--print-path" || option === undefined) { + const runtimePath = materializeRuntimeBundle({ packageRoot, platform }); + process.stdout.write(`${runtimePath}\n`); +} else { + throw new Error(`Unknown option: ${option}`); +} diff --git a/nodejs/scripts/releaseArtifacts.ts b/nodejs/scripts/releaseArtifacts.ts new file mode 100644 index 0000000000..2731d878c5 --- /dev/null +++ b/nodejs/scripts/releaseArtifacts.ts @@ -0,0 +1,233 @@ +import { createHash } from "node:crypto"; +import { existsSync, mkdirSync, mkdtempSync, renameSync, rmSync, writeFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { x as extractTar } from "tar"; +import { + defaultRuntimeCacheRoot, + getRuntimePlatform, + getRuntimeReleaseAssetName, + resolvePackageRoot, + validateFile, +} from "../src/runtimeArtifacts.js"; +import { COPILOT_CLI_USE_NPM_PACKAGE, COPILOT_CLI_VERSION } from "../src/cliVersion.js"; + +export interface EnsureCopilotPackageOptions { + cacheRoot?: string; + environment?: NodeJS.ProcessEnv; + fetch?: typeof globalThis.fetch; + fetchTimeoutMs?: number; + platform?: string; +} + +const packageDownloads = new Map>(); +const checksumDownloads = new Map>>(); +const DEFAULT_FETCH_TIMEOUT_MS = 60_000; + +async function fetchWithRetry( + fetcher: typeof globalThis.fetch, + url: string, + readResponse: (response: Response) => Promise, + timeoutMs: number +): Promise { + let lastError: unknown; + for (let attempt = 0; attempt < 3; attempt++) { + try { + const response = await fetcher(url, { + signal: AbortSignal.timeout(timeoutMs), + }); + if (response.ok) { + return await readResponse(response); + } + await response.body?.cancel(); + lastError = new Error(`${response.status} ${response.statusText}`); + if ( + response.status >= 400 && + response.status < 500 && + response.status !== 408 && + response.status !== 429 + ) { + break; + } + } catch (error) { + lastError = error; + } + if (attempt < 2) { + await new Promise((resolve) => setTimeout(resolve, 2 ** attempt * 1000)); + } + } + throw new Error(`Failed to download ${url}: ${String(lastError)}`); +} + +async function getReleaseChecksum( + version: string, + assetName: string, + baseUrl: string, + fetcher: typeof globalThis.fetch, + fetchTimeoutMs: number +): Promise { + const key = `${baseUrl}\0${version}`; + let checksums = checksumDownloads.get(key); + if (!checksums) { + checksums = downloadReleaseChecksums(version, baseUrl, fetcher, fetchTimeoutMs); + checksumDownloads.set(key, checksums); + try { + return (await checksums).get(assetName); + } catch (error) { + checksumDownloads.delete(key); + throw error; + } + } + return (await checksums).get(assetName); +} + +async function downloadReleaseChecksums( + version: string, + baseUrl: string, + fetcher: typeof globalThis.fetch, + fetchTimeoutMs: number +): Promise> { + const contents = await fetchWithRetry( + fetcher, + `${baseUrl}/v${version}/SHA256SUMS.txt`, + (response) => response.text(), + fetchTimeoutMs + ); + const checksums = new Map(); + for (const line of contents.split(/\r?\n/)) { + const [hash, name] = line.trim().split(/\s+/, 2); + if (/^[a-fA-F0-9]{64}$/.test(hash) && name) { + checksums.set(name.replace(/^\*/, ""), hash.toLowerCase()); + } + } + return checksums; +} + +export async function ensureCopilotPackage( + version = COPILOT_CLI_VERSION, + options: EnsureCopilotPackageOptions = {} +): Promise { + const platform = options.platform ?? getRuntimePlatform(); + // lgtm[js/trivial-conditional] This generated constant is true for internal canary builds. + if (version === COPILOT_CLI_VERSION && COPILOT_CLI_USE_NPM_PACKAGE) { + const packageName = `@github/copilot-${platform}`; + const packageRoot = resolvePackageRoot(packageName); + if (!packageRoot) { + throw new Error(`Could not resolve ${packageName} for Copilot CLI ${version}.`); + } + validateFile( + join(packageRoot, "prebuilds", platform, "runtime.node"), + "Copilot runtime.node" + ); + return packageRoot; + } + + const cacheRoot = options.cacheRoot ?? defaultRuntimeCacheRoot(); + const cachedPackageRoot = join(cacheRoot, version, "packages", platform); + const cachedRuntimeNode = join(cachedPackageRoot, "prebuilds", platform, "runtime.node"); + if (existsSync(cachedRuntimeNode)) { + validateFile(cachedRuntimeNode, "Copilot runtime.node"); + return cachedPackageRoot; + } + + const baseUrl = ( + (options.environment ?? process.env).COPILOT_CLI_DOWNLOAD_BASE_URL ?? + "https://github.com/github/copilot-cli/releases/download" + ).replace(/\/+$/, ""); + const fetchTimeoutMs = options.fetchTimeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS; + const key = `${cacheRoot}\0${version}\0${platform}\0${baseUrl}`; + if (!options.fetch) { + const existing = packageDownloads.get(key); + if (existing) { + return existing; + } + const download = downloadCopilotPackage( + version, + platform, + cacheRoot, + baseUrl, + globalThis.fetch, + fetchTimeoutMs + ); + packageDownloads.set(key, download); + try { + return await download; + } finally { + packageDownloads.delete(key); + } + } + return downloadCopilotPackage( + version, + platform, + cacheRoot, + baseUrl, + options.fetch, + fetchTimeoutMs + ); +} + +async function downloadCopilotPackage( + version: string, + platform: string, + cacheRoot: string, + baseUrl: string, + fetcher: typeof globalThis.fetch, + fetchTimeoutMs: number +): Promise { + if (!fetcher) { + throw new Error("This Node.js runtime does not provide fetch()."); + } + const assetName = getRuntimeReleaseAssetName(version, platform); + const expectedChecksum = await getReleaseChecksum( + version, + assetName, + baseUrl, + fetcher, + fetchTimeoutMs + ); + if (!expectedChecksum) { + throw new Error(`SHA256SUMS.txt does not contain ${assetName}.`); + } + const archive = await fetchWithRetry( + fetcher, + `${baseUrl}/v${version}/${assetName}`, + async (response) => Buffer.from(await response.arrayBuffer()), + fetchTimeoutMs + ); + const actualChecksum = createHash("sha256").update(archive).digest("hex"); + if (actualChecksum !== expectedChecksum) { + throw new Error( + `Checksum mismatch for ${assetName}: expected ${expectedChecksum}, got ${actualChecksum}.` + ); + } + + mkdirSync(cacheRoot, { recursive: true }); + const stagingRoot = mkdtempSync(join(cacheRoot, ".download-")); + const archivePath = join(stagingRoot, assetName); + const packageRoot = join(stagingRoot, "package"); + const cachedPackageRoot = join(cacheRoot, version, "packages", platform); + writeFileSync(archivePath, archive); + try { + await extractTar({ + cwd: stagingRoot, + file: archivePath, + gzip: true, + preservePaths: false, + strict: true, + }); + validateFile( + join(packageRoot, "prebuilds", platform, "runtime.node"), + "Copilot runtime.node" + ); + mkdirSync(dirname(cachedPackageRoot), { recursive: true }); + try { + renameSync(packageRoot, cachedPackageRoot); + } catch (error) { + if (!existsSync(cachedPackageRoot)) { + throw error; + } + } + return cachedPackageRoot; + } finally { + rmSync(stagingRoot, { recursive: true, force: true }); + } +} diff --git a/nodejs/scripts/set-cli-version.js b/nodejs/scripts/set-cli-version.js new file mode 100644 index 0000000000..ea45f90ada --- /dev/null +++ b/nodejs/scripts/set-cli-version.js @@ -0,0 +1,71 @@ +import { readFileSync, writeFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const [version, mode] = process.argv.slice(2); +if (!version || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z._-]+)?$/.test(version)) { + throw new Error("Usage: set-cli-version.js [--npm-package]"); +} +if (mode !== undefined && mode !== "--npm-package") { + throw new Error(`Unknown option: ${mode}`); +} + +const nodeRoot = join(dirname(fileURLToPath(import.meta.url)), ".."); +const runtimePlatforms = [ + "darwin-arm64", + "darwin-x64", + "linux-arm64", + "linux-x64", + "linuxmusl-arm64", + "linuxmusl-x64", + "win32-arm64", + "win32-x64", +]; +const cliAssets = [ + "copilot-darwin-arm64.tar.gz", + "copilot-darwin-x64.tar.gz", + "copilot-linux-arm64.tar.gz", + "copilot-linux-x64.tar.gz", + "copilot-win32-arm64.zip", + "copilot-win32-x64.zip", +]; +const useNpmPackage = mode === "--npm-package"; +if (!useNpmPackage) { + const checksumsUrl = `https://github.com/github/copilot-cli/releases/download/v${version}/SHA256SUMS.txt`; + const response = await fetch(checksumsUrl); + if (!response.ok) { + throw new Error( + `Failed to download ${checksumsUrl}: ${response.status} ${response.statusText}` + ); + } + const checksums = new Map( + (await response.text()) + .split(/\r?\n/) + .map((line) => line.trim().split(/\s+/, 2)) + .filter(([hash, name]) => /^[a-fA-F0-9]{64}$/.test(hash) && name) + .map(([hash, name]) => [name.replace(/^\*/, ""), hash.toLowerCase()]) + ); + for (const assetName of [ + ...runtimePlatforms.map((platform) => `github-copilot-${version}-${platform}.tgz`), + ...cliAssets, + ]) { + if (!checksums.has(assetName)) { + throw new Error(`SHA256SUMS.txt does not contain ${assetName}`); + } + } +} +const packagePath = join(nodeRoot, "package.json"); +const packageJson = JSON.parse(readFileSync(packagePath, "utf8")); +packageJson.copilotCliVersion = version; +writeFileSync(packagePath, `${JSON.stringify(packageJson, null, 4)}\n`); + +const sourcePath = join(nodeRoot, "src", "cliVersion.ts"); +writeFileSync( + sourcePath, + [ + `export const COPILOT_CLI_VERSION = ${JSON.stringify(version)};`, + "", + `export const COPILOT_CLI_USE_NPM_PACKAGE = ${useNpmPackage};`, + "", + ].join("\n") +); diff --git a/nodejs/scripts/verify-release-packages.ts b/nodejs/scripts/verify-release-packages.ts new file mode 100644 index 0000000000..e1b13a3074 --- /dev/null +++ b/nodejs/scripts/verify-release-packages.ts @@ -0,0 +1,100 @@ +import assert from "node:assert/strict"; +import { mkdtempSync, readFileSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { globSync } from "glob"; +import { t as listTar, x as extractTar } from "tar"; +import { getRuntimePackageName, RUNTIME_PLATFORMS } from "../src/runtimeArtifacts.js"; + +interface PackedPackage { + manifest: { + name: string; + version: string; + optionalDependencies?: Record; + }; + entries: Set; +} + +const nodeRoot = join(dirname(fileURLToPath(import.meta.url)), ".."); +const sourceManifest = JSON.parse( + readFileSync(join(nodeRoot, "package.json"), "utf8") +) as PackedPackage["manifest"]; +const expectedRuntimePackages = Object.fromEntries( + RUNTIME_PLATFORMS.map((platform) => [getRuntimePackageName(platform), sourceManifest.version]) +); +const expectedPackageNames = new Set([ + sourceManifest.name, + ...Object.keys(expectedRuntimePackages), +]); +const packages = new Map(); + +for (const archive of globSync("*.tgz", { cwd: nodeRoot, absolute: true })) { + const entries = new Set(); + await listTar({ + file: archive, + onReadEntry(entry) { + entries.add(entry.path); + entry.resume(); + }, + }); + + const manifestRoot = mkdtempSync(join(tmpdir(), "copilot-sdk-package-manifest-")); + let manifest: PackedPackage["manifest"]; + try { + await extractTar({ + cwd: manifestRoot, + file: archive, + strict: true, + filter: (entryPath) => entryPath === "package/package.json", + }); + manifest = JSON.parse( + readFileSync(join(manifestRoot, "package", "package.json"), "utf8") + ) as PackedPackage["manifest"]; + } finally { + rmSync(manifestRoot, { recursive: true, force: true }); + } + + if (manifest.version !== sourceManifest.version || !expectedPackageNames.has(manifest.name)) { + continue; + } + assert(!packages.has(manifest.name), `Duplicate tarball for ${manifest.name}`); + packages.set(manifest.name, { manifest, entries }); +} + +assert.deepEqual( + [...packages.keys()].sort(), + [...expectedPackageNames].sort(), + "Release packaging did not produce the expected main and platform packages" +); + +const mainPackage = packages.get(sourceManifest.name); +assert(mainPackage, `Missing ${sourceManifest.name} tarball`); +assert.deepEqual( + mainPackage.manifest.optionalDependencies, + expectedRuntimePackages, + "Main package optional dependencies do not match the platform packages" +); +assert(mainPackage.entries.has("package/dist/index.js"), "Main package is missing dist/index.js"); +assert( + mainPackage.entries.has("package/dist/cjs/index.js"), + "Main package is missing dist/cjs/index.js" +); + +for (const platform of RUNTIME_PLATFORMS) { + const packageName = getRuntimePackageName(platform); + const packed = packages.get(packageName); + assert(packed, `Missing ${packageName} tarball`); + const runtimeName = platform.startsWith("win32") ? "copilot-runtime.exe" : "copilot-runtime"; + for (const requiredPath of [ + `package/prebuilds/${platform}/${runtimeName}`, + `package/prebuilds/${platform}/runtime.node`, + "package/copilot-sdk/extension.js", + "package/preloads/extension_bootstrap.mjs", + "package/sdk/index.js", + ]) { + assert(packed.entries.has(requiredPath), `${packageName} is missing ${requiredPath}`); + } +} + +console.log(`Verified ${packages.size} release package tarballs.`); diff --git a/nodejs/src/cliVersion.ts b/nodejs/src/cliVersion.ts new file mode 100644 index 0000000000..a0c4eb8558 --- /dev/null +++ b/nodejs/src/cliVersion.ts @@ -0,0 +1,3 @@ +export const COPILOT_CLI_VERSION = "1.0.83-3"; + +export const COPILOT_CLI_USE_NPM_PACKAGE = false; diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index 3e100edddd..a942ed6776 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -14,10 +14,8 @@ import { spawn, type ChildProcess } from "node:child_process"; 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 { fileURLToPath } from "node:url"; +import { isIPv6, Socket } from "node:net"; +import { dirname, isAbsolute, join, resolve } from "node:path"; import { createMessageConnection, ErrorCodes, @@ -34,13 +32,18 @@ import { registerClientSessionApiHandlers, } from "./generated/rpc.js"; import type { + ConnectClientInfo, GitHubTelemetryNotification, + GitHubTokenAcquireRequest, + GitHubTokenAcquireResult, OpenCanvasInstance, SessionUpdateOptionsParams, } from "./generated/rpc.js"; import { getSdkProtocolVersion } from "./sdkProtocolVersion.js"; import { CopilotSession } from "./session.js"; import type { FfiRuntimeHost } from "./ffiRuntimeHost.js"; +import { ensureRuntimeBundle } from "./runtimeArtifacts.js"; +import { COPILOT_CLI_VERSION } from "./cliVersion.js"; import { createSessionFsAdapter, type SessionFsProvider } from "./sessionFsProvider.js"; import { createCopilotRequestAdapter } from "./copilotRequestHandler.js"; import type { CopilotRequestHandler } from "./copilotRequestHandler.js"; @@ -49,14 +52,17 @@ import { ToolSet } from "./toolSet.js"; import type { AutoModeSwitchRequest, AutoModeSwitchResponse, + CopilotClientInfo, CopilotClientMode, CopilotClientOptions, CustomAgentConfig, ExitPlanModeRequest, ExitPlanModeResult, + ExtensionJoinOptions, ForegroundSessionInfo, GetAuthStatusResponse, BearerTokenProvider, + GitHubTokenProvider, GetStatusResponse, InternalRuntimeConnection, RuntimeConnection, @@ -254,6 +260,22 @@ function toWireCustomAgents(agents: CustomAgentConfig[] | undefined): unknown[] }); } +/** + * Map the public {@link CopilotClientInfo} onto the generated connect wire + * shape, dropping empty fields. Returns `undefined` when no field carries a + * non-empty value so the caller omits `clientInfo` from the handshake and keeps + * the runtime's default attribution. + */ +function clientInfoToWire(info: CopilotClientInfo | undefined): ConnectClientInfo | undefined { + if (info == null) return undefined; + const wire: ConnectClientInfo = {}; + if (info.applicationName) wire.editorName = info.applicationName; + if (info.applicationVersion) wire.editorVersion = info.applicationVersion; + if (info.integrationName) wire.extensionName = info.integrationName; + if (info.integrationVersion) wire.extensionVersion = info.integrationVersion; + return Object.keys(wire).length > 0 ? wire : undefined; +} + /** * Convert a {@link LargeToolOutputConfig} from the public API shape * (`outputDirectory`) to the wire shape (`outputDir`). @@ -348,67 +370,8 @@ function getNodeExecPath(): string { return process.execPath; } -/** - * Computes the candidate platform-specific CLI package names for the current - * platform/arch, mirroring @github/copilot's npm-loader. As of CLI 1.0.64-1 the - * @github/copilot package is a thin loader and the actual CLI ships in a - * platform package (e.g. @github/copilot-darwin-arm64). For Linux we try both - * the glibc and musl variants since only the matching one is installed. - */ -function getCliPlatformPackageNames(): string[] { - const arch = process.arch; - const variants = process.platform === "linux" ? ["linux", "linuxmusl"] : [process.platform]; - return variants.map((variant) => `@github/copilot-${variant}-${arch}`); -} - -/** - * Gets the path to the bundled CLI from the platform-specific @github/copilot-* - * package. Uses index.js directly rather than the native binary so the CLI runs - * under the current Node.js runtime. - * - * In ESM, uses import.meta.resolve directly. In CJS (e.g., VS Code extensions - * bundled with esbuild format:"cjs"), import.meta is empty so we fall back to - * walking node_modules to find the package. - */ -function getBundledCliPath(): string { - const packageNames = getCliPlatformPackageNames(); - - if (typeof import.meta.resolve === "function") { - // ESM: resolve via import.meta.resolve - for (const packageName of packageNames) { - try { - const sdkUrl = import.meta.resolve(`${packageName}/sdk`); - const sdkPath = fileURLToPath(sdkUrl); - // sdkPath is like .../node_modules/@github/copilot-/sdk/index.js - // Go up two levels to get the package root, then append index.js - return join(dirname(dirname(sdkPath)), "index.js"); - } catch { - // Try the next candidate platform package. - } - } - throw new Error( - `Could not resolve a @github/copilot platform package (tried ${packageNames.join(", ")}). ` + - `Ensure @github/copilot is installed, or pass cliPath/cliUrl to CopilotClient.` - ); - } - - // CJS fallback: the platform packages have ESM-only exports so - // require.resolve cannot reach them. Walk the module search paths instead. - const req = createRequire(__filename); - const searchPaths = req.resolve.paths("@github/copilot") ?? []; - for (const base of searchPaths) { - for (const packageName of packageNames) { - const candidate = join(base, ...packageName.split("/"), "index.js"); - if (existsSync(candidate)) { - return candidate; - } - } - } - throw new Error( - `Could not find a @github/copilot platform package (tried ${packageNames.join(", ")}). ` + - `Searched ${searchPaths.length} paths. ` + - `Ensure @github/copilot is installed, or pass cliPath/cliUrl to CopilotClient.` - ); +function getBundledRuntimePath(): Promise { + return ensureRuntimeBundle(COPILOT_CLI_VERSION); } /** @@ -501,6 +464,7 @@ export class CopilotClient { sessionIdleTimeoutSeconds: number; enableRemoteSessions: boolean; mode: CopilotClientMode; + clientInfo?: CopilotClientInfo; }; private isExternalServer: boolean = false; private forceStopping: boolean = false; @@ -518,12 +482,18 @@ export class CopilotClient { private _rpc: ReturnType | null = null; private _internalRpc: ReturnType | null = null; private processExitPromise: Promise | null = null; // Rejects when CLI process exits + private processTransportError: Error | null = null; private negotiatedProtocolVersion: number | null = null; /** 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 = {}; + private githubTokenProviders = new Map< + string, + { provider: GitHubTokenProvider; sessionId?: string; committed: boolean } + >(); /** * Typed server-scoped RPC methods. @@ -682,6 +652,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") { @@ -716,10 +696,12 @@ export class CopilotClient { conn.kind === "stdio" || conn.kind === "tcp" ? conn.env : undefined; const effectiveEnv = connEnv ?? options.env ?? process.env; this.resolvedEnv = effectiveEnv; - this.resolvedCliPath = - conn.kind === "stdio" || conn.kind === "tcp" - ? (conn.path ?? effectiveEnv.COPILOT_CLI_PATH ?? getBundledCliPath()) - : undefined; + if (conn.kind === "stdio" || conn.kind === "tcp") { + const explicitCliPath = conn.path ?? effectiveEnv.COPILOT_CLI_PATH; + if (explicitCliPath) { + this.resolvedCliPath = explicitCliPath; + } + } // Collect extra CLI args from the connection variant (if any). const connArgs: readonly string[] = @@ -737,6 +719,7 @@ export class CopilotClient { sessionIdleTimeoutSeconds: options.sessionIdleTimeoutSeconds ?? 0, enableRemoteSessions: options.enableRemoteSessions ?? false, mode: options.mode ?? "copilot-cli", + clientInfo: options.clientInfo, }; // Empty mode: validate at construction time that the app supplied a @@ -765,22 +748,38 @@ export class CopilotClient { /** * Parse CLI URL into host and port - * Supports formats: "host:port", "http://host:port", "https://host:port", or just "port" + * Supports formats: "host:port", "[ipv6]:port", "http://host:port", "https://host:port", or just "port" */ private parseCliUrl(url: string): { host: string; port: number } { // Remove protocol if present - let cleanUrl = url.replace(/^https?:\/\//, ""); + const cleanUrl = url.replace(/^https?:\/\//, ""); // Check if it's just a port number if (/^\d+$/.test(cleanUrl)) { return { host: "localhost", port: parseInt(cleanUrl, 10) }; } + // Handle the canonical bracketed IPv6 host:port form without changing + // the existing parser behavior for other inputs. + const ipv6Match = cleanUrl.match(/^\[([^\]]+)\]:(\d+)$/); + if (ipv6Match) { + const host = ipv6Match[1]; + if (!isIPv6(host)) { + throw new Error(`Invalid cliUrl format: ${url}`); + } + + const port = parseInt(ipv6Match[2], 10); + if (isNaN(port) || port <= 0 || port > 65535) { + throw new Error(`Invalid port in cliUrl: ${url}`); + } + return { host, port }; + } + // Parse host:port format const parts = cleanUrl.split(":"); if (parts.length !== 2) { throw new Error( - `Invalid cliUrl format: ${url}. Expected "host:port", "http://host:port", or "port"` + `Invalid cliUrl format: ${url}. Expected "host:port", "[ipv6]:port", "http://host:port", or "port"` ); } @@ -852,9 +851,65 @@ export class CopilotClient { }, }; } + handlers.gitHubToken = { + getToken: (params) => this.acquireGitHubToken(params), + }; this.clientGlobalHandlers = handlers; } + private async acquireGitHubToken( + params: GitHubTokenAcquireRequest + ): Promise { + const registration = this.githubTokenProviders.get(params.registrationId); + if (!registration) { + throw new Error( + `No GitHub token provider registered for registration ID "${params.registrationId}"` + ); + } + return await registration.provider({ + host: params.host, + sessionId: params.sessionId ?? registration.sessionId, + reason: params.reason, + }); + } + + private registerGitHubTokenProvider( + provider: GitHubTokenProvider | undefined, + sessionId?: string + ): string | undefined { + if (!provider) { + return undefined; + } + const registrationId = randomUUID(); + this.githubTokenProviders.set(registrationId, { provider, sessionId, committed: false }); + return registrationId; + } + + private assignGitHubTokenProvider(registrationId: string | undefined, sessionId: string): void { + if (!registrationId) { + return; + } + const registration = this.githubTokenProviders.get(registrationId); + if (registration) { + registration.sessionId = sessionId; + } + } + + private commitGitHubTokenProvider(sessionId: string, registrationId?: string): void { + for (const [candidateId, registration] of this.githubTokenProviders) { + if (registration.sessionId === sessionId && registration.committed) { + this.githubTokenProviders.delete(candidateId); + } + } + const registration = registrationId + ? this.githubTokenProviders.get(registrationId) + : undefined; + if (registration) { + registration.sessionId = sessionId; + registration.committed = true; + } + } + /** * Starts the CLI server and establishes a connection. * @@ -878,6 +933,8 @@ export class CopilotClient { return; } + this.forceStopping = false; + this.processTransportError = null; this.state = "connecting"; try { @@ -894,6 +951,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", { @@ -913,8 +981,10 @@ export class CopilotClient { this.state = "connected"; } catch (error) { + const startupError = this.processTransportError ?? error; + await this.forceStop(); this.state = "error"; - throw error; + throw startupError; } } @@ -994,6 +1064,7 @@ export class CopilotClient { session._markDisconnected(); } this.sessions.clear(); + this.githubTokenProviders.clear(); // Ask SDK-owned runtimes to flush and clean up before we tear down // their transport/process. External runtimes may be shared, so only @@ -1176,6 +1247,7 @@ export class CopilotClient { session._markDisconnected(); } this.sessions.clear(); + this.githubTokenProviders.clear(); // Force close connection. Suppress writer failures first so teardown // write rejections don't surface as unhandled rejections. @@ -1380,7 +1452,8 @@ export class CopilotClient { * * In empty mode, defaults the four overridable feature flags to safe values * (caller values from `config` win). `installedPlugins=[]` is unconditional - * in empty mode — apps that need custom plugins should switch modes. + * in empty mode. `includedBuiltinSkills` defaults to `[]`, but callers can + * explicitly allow selected runtime-bundled skills. */ private async updateSessionOptionsForMode( session: CopilotSession, @@ -1393,6 +1466,7 @@ export class CopilotClient { patch.coauthorEnabled = config.coauthorEnabled ?? false; patch.manageScheduleEnabled = config.manageScheduleEnabled ?? false; patch.installedPlugins = []; + patch.includedBuiltinSkills = config.includedBuiltinSkills ?? []; } else { if (config.skipCustomInstructions !== undefined) patch.skipCustomInstructions = config.skipCustomInstructions; @@ -1402,6 +1476,8 @@ export class CopilotClient { patch.coauthorEnabled = config.coauthorEnabled; if (config.manageScheduleEnabled !== undefined) patch.manageScheduleEnabled = config.manageScheduleEnabled; + if (config.includedBuiltinSkills !== undefined) + patch.includedBuiltinSkills = config.includedBuiltinSkills; } if (Object.keys(patch).length === 0) { return; @@ -1423,6 +1499,9 @@ export class CopilotClient { } async createSession(config: SessionConfig): Promise { + if (config.gitHubToken !== undefined && config.gitHubTokenProvider !== undefined) { + throw new Error("gitHubToken and gitHubTokenProvider are mutually exclusive"); + } if (!this.connection) { await this.start(); } @@ -1442,6 +1521,11 @@ export class CopilotClient { const callerSessionId = config.sessionId; const useServerGeneratedId = config.cloud != null && callerSessionId == null; const localSessionId = useServerGeneratedId ? undefined : (callerSessionId ?? randomUUID()); + const toolFilterOptions = this.resolveToolFilterOptions(config); + const gitHubTokenProviderRegistrationId = this.registerGitHubTokenProvider( + config.gitHubTokenProvider, + localSessionId + ); // Strip non-serializable bearerTokenProvider callbacks from provider configs, // replacing them with a wire flag; keep the callbacks for session-side @@ -1467,7 +1551,16 @@ export class CopilotClient { this.onGetTraceContext, { mcpAuthHandler: config.onMcpAuthRequest, - managedSettingsEnabled: config.enableManagedSettings, + managedSettingsEnabled: + config.enableManagedSettings === true || + config.managedSettings !== undefined, + onDisconnected: + gitHubTokenProviderRegistrationId === undefined + ? undefined + : () => + this.githubTokenProviders.delete( + gitHubTokenProviderRegistrationId + ), } ); s.registerTools(config.tools); @@ -1511,12 +1604,17 @@ export class CopilotClient { // processing (e.g. sessionFs.writeFile for workspace metadata) can be // routed to the correct handlers. if (localSessionId !== undefined) { - session = initializeSession(localSessionId); - registeredId = localSessionId; + try { + session = initializeSession(localSessionId); + registeredId = localSessionId; + } catch (error) { + if (gitHubTokenProviderRegistrationId !== undefined) { + this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId); + } + throw error; + } } - const toolFilterOptions = this.resolveToolFilterOptions(config); - try { const response = await this.connection!.sendRequest("session.create", { ...(await getTraceContext(this.onGetTraceContext)), @@ -1535,6 +1633,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), @@ -1545,7 +1644,7 @@ export class CopilotClient { canvasProvider: config.canvasProvider, commands: config.commands?.map((cmd) => ({ name: cmd.name, - description: cmd.description, + description: cmd.description ?? "", })), systemMessage: wireSystemMessage, availableTools: toolFilterOptions.availableTools, @@ -1558,12 +1657,14 @@ 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), requestPermission: !!config.onPermissionRequest, requestUserInput: !!config.onUserInputRequest, requestElicitation: !!config.onElicitationRequest, + askUserVariant: config.askUserVariant, ...(config.enableMcpApps ? { requestMcpApps: true } : {}), ...(config.githubMcpToolConfig != null ? { githubMcpToolConfig: config.githubMcpToolConfig } @@ -1599,13 +1700,17 @@ export class CopilotClient { pluginDirectories: config.pluginDirectories, instructionDirectories: config.instructionDirectories, disabledSkills: config.disabledSkills, + disabledMcpServers: config.disabledMcpServers, infiniteSessions: config.infiniteSessions, memory: config.memory, gitHubToken: config.gitHubToken, + gitHubTokenProviderRegistrationId, remoteSession: config.remoteSession, cloud: config.cloud, + featureFlags: config.featureFlags, expAssignments: config.expAssignments, enableManagedSettings: config.enableManagedSettings, + managedSettings: config.managedSettings, }); const { @@ -1631,6 +1736,7 @@ export class CopilotClient { session = initializeSession(returnedSessionId); registeredId = returnedSessionId; } + this.assignGitHubTokenProvider(gitHubTokenProviderRegistrationId, returnedSessionId); if (config.onMcpAuthRequest) { await this.connection!.sendRequest("session.eventLog.registerInterest", { sessionId: returnedSessionId, @@ -1641,10 +1747,14 @@ export class CopilotClient { session.setCapabilities(capabilities); await this.updateSessionOptionsForMode(session, config); + this.commitGitHubTokenProvider(returnedSessionId, gitHubTokenProviderRegistrationId); } catch (e) { if (registeredId !== undefined) { this.sessions.delete(registeredId); } + if (gitHubTokenProviderRegistrationId !== undefined) { + this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId); + } throw e; } @@ -1683,16 +1793,21 @@ export class CopilotClient { async resumeSessionForExtension( sessionId: string, config: ResumeSessionConfig, - factories?: FactoryHandle[] + factories?: FactoryHandle[], + extensionOptions?: ExtensionJoinOptions ): Promise { - return this.resumeSessionInternal(sessionId, config, factories); + return this.resumeSessionInternal(sessionId, config, factories, extensionOptions); } private async resumeSessionInternal( sessionId: string, config: ResumeSessionConfig, - factories?: FactoryHandle[] + factories?: FactoryHandle[], + extensionOptions?: ExtensionJoinOptions ): Promise { + if (config.gitHubToken !== undefined && config.gitHubTokenProvider !== undefined) { + throw new Error("gitHubToken and gitHubTokenProvider are mutually exclusive"); + } if (!this.connection) { await this.start(); } @@ -1706,7 +1821,8 @@ export class CopilotClient { this.onGetTraceContext, { mcpAuthHandler: config.onMcpAuthRequest, - managedSettingsEnabled: config.enableManagedSettings, + managedSettingsEnabled: + config.enableManagedSettings === true || config.managedSettings !== undefined, } ); session.registerTools(config.tools); @@ -1757,6 +1873,15 @@ export class CopilotClient { this.setupSessionFs(session, config); const toolFilterOptions = this.resolveToolFilterOptions(config); + const gitHubTokenProviderRegistrationId = this.registerGitHubTokenProvider( + config.gitHubTokenProvider, + sessionId + ); + if (gitHubTokenProviderRegistrationId !== undefined) { + session._setOnDisconnected(() => + this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId) + ); + } try { const response = await this.connection!.sendRequest("session.resume", { @@ -1775,6 +1900,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, @@ -1784,6 +1910,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), @@ -1795,7 +1922,7 @@ export class CopilotClient { canvasProvider: config.canvasProvider, commands: config.commands?.map((cmd) => ({ name: cmd.name, - description: cmd.description, + description: cmd.description ?? "", })), provider: bearerWireProvider, capi: config.capi, @@ -1807,6 +1934,7 @@ export class CopilotClient { config.onPermissionRequest !== defaultJoinSessionPermissionHandler, requestUserInput: !!config.onUserInputRequest, requestElicitation: !!config.onElicitationRequest, + askUserVariant: config.askUserVariant, ...(config.enableMcpApps ? { requestMcpApps: true } : {}), ...(config.githubMcpToolConfig != null ? { githubMcpToolConfig: config.githubMcpToolConfig } @@ -1842,17 +1970,44 @@ export class CopilotClient { pluginDirectories: config.pluginDirectories, instructionDirectories: config.instructionDirectories, disabledSkills: config.disabledSkills, + disabledMcpServers: config.disabledMcpServers, infiniteSessions: config.infiniteSessions, memory: config.memory, disableResume: config.suppressResumeEvent, continuePendingWork: config.continuePendingWork, gitHubToken: config.gitHubToken, + gitHubTokenProviderRegistrationId, remoteSession: config.remoteSession, openCanvases: config.openCanvases, + featureFlags: config.featureFlags, expAssignments: config.expAssignments, enableManagedSettings: config.enableManagedSettings, + managedSettings: config.managedSettings, + ...(extensionOptions?.requestedEnvironmentVariables + ? { + requestedEnvironmentVariables: + extensionOptions.requestedEnvironmentVariables, + } + : {}), }); + // The host answers an approved environment request with the resolved + // values, and this method consumes the response, so the grant has to be + // applied here — no caller ever sees it. Only the names the user + // approved may reach the extension, so a host that answers with + // anything extra cannot widen the grant. + if (extensionOptions?.requestedEnvironmentVariables) { + const requested = new Set(extensionOptions.requestedEnvironmentVariables); + const { grantedEnvironmentVariables } = response as { + grantedEnvironmentVariables?: Record; + }; + for (const [name, value] of Object.entries(grantedEnvironmentVariables ?? {})) { + if (requested.has(name)) { + process.env[name] = value; + } + } + } + const { workspacePath, capabilities, openCanvases } = response as { sessionId: string; workspacePath?: string; @@ -1870,8 +2025,12 @@ export class CopilotClient { } await this.updateSessionOptionsForMode(session, config); + this.commitGitHubTokenProvider(sessionId, gitHubTokenProviderRegistrationId); } catch (e) { this.sessions.delete(sessionId); + if (gitHubTokenProviderRegistrationId !== undefined) { + this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId); + } throw e; } @@ -2017,6 +2176,7 @@ export class CopilotClient { const connectParams: { token?: string; enableGitHubTelemetryForwarding?: boolean; + clientInfo?: ConnectClientInfo; } = { token: this.effectiveConnectionToken }; // Opt in to GitHub telemetry forwarding at the connection level when a // handler is registered (mirrors the runtime, which reads this flag on the @@ -2025,6 +2185,14 @@ export class CopilotClient { if (this.onGitHubTelemetry != null) { connectParams.enableGitHubTelemetryForwarding = true; } + // Declare the integrating application's identity so the runtime attributes + // the telemetry it emits on this connection to a consistent surface + // instead of its own build. Empty fields are dropped, and an + // all-empty identity is omitted entirely. + const clientInfo = clientInfoToWire(this.options.clientInfo); + if (clientInfo != null) { + connectParams.clientInfo = clientInfo; + } const result = await raceAgainstExit(this.internalRpc.connect(connectParams)); serverVersion = result.protocolVersion; } catch (err) { @@ -2115,8 +2283,9 @@ export class CopilotClient { throw new Error(`Failed to delete session ${sessionId}: ${error || "Unknown error"}`); } - // Remove from local sessions map if present + const session = this.sessions.get(sessionId); this.sessions.delete(sessionId); + session?._runOnDisconnected(); } /** @@ -2408,6 +2577,7 @@ export class CopilotClient { * Start the CLI server process */ private async startCLIServer(): Promise { + this.resolvedCliPath ??= await getBundledRuntimePath(); return new Promise((resolve, reject) => { // Clear stderr buffer for fresh capture this.stderrBuffer = ""; @@ -2467,7 +2637,7 @@ export class CopilotClient { // Verify CLI exists before attempting to spawn if (!existsSync(this.resolvedCliPath)) { throw new Error( - `Copilot CLI not found at ${this.resolvedCliPath}. Ensure @github/copilot is installed.` + `Copilot CLI not found at ${this.resolvedCliPath}. Set COPILOT_CLI_PATH to use a custom installation.` ); } @@ -2545,27 +2715,27 @@ export class CopilotClient { // Set up a promise that rejects when the process exits (used to race against RPC calls) this.processExitPromise = new Promise((_, rejectProcessExit) => { this.cliProcess!.on("exit", (code) => { - // Give a small delay for stderr to be fully captured - setTimeout(() => { - const stderrOutput = this.stderrBuffer.trim(); - if (stderrOutput) { - rejectProcessExit( - new Error( - `CLI server exited with code ${code}\nstderr: ${stderrOutput}` - ) - ); - } else { - rejectProcessExit( - new Error(`CLI server exited unexpectedly with code ${code}`) - ); - } - }, 50); + if (this.messageWriter) { + this.messageWriter.suppressWriteErrors = true; + } + const stderrOutput = this.stderrBuffer.trim(); + if (stderrOutput) { + rejectProcessExit( + new Error( + `CLI server exited with code ${code}\nstderr: ${stderrOutput}` + ) + ); + } else { + rejectProcessExit( + new Error(`CLI server exited unexpectedly with code ${code}`) + ); + } }); }); // Prevent unhandled rejection when process exits normally (we only use this in Promise.race) this.processExitPromise.catch(() => {}); - this.cliProcess.on("exit", (code) => { + this.cliProcess.on("close", (code) => { if (!resolved) { resolved = true; const stderrOutput = this.stderrBuffer.trim(); @@ -2610,7 +2780,22 @@ export class CopilotClient { /** Starts the in-process FFI runtime with SDK-managed typed options. */ private async startInProcessFfi(): Promise { - const entrypoint = this.resolveCliPathForFfi(); + const explicitEntrypoint = this.resolvedEnv.COPILOT_CLI_PATH; + let runtimeLibrary: string; + if (explicitEntrypoint) { + const entrypointDirectory = dirname(resolve(explicitEntrypoint)); + const adjacentRuntime = join(entrypointDirectory, "runtime.node"); + runtimeLibrary = existsSync(adjacentRuntime) + ? adjacentRuntime + : join( + entrypointDirectory, + "prebuilds", + CopilotClient.getNapiPrebuildsFolder(explicitEntrypoint), + "runtime.node" + ); + } else { + runtimeLibrary = join(dirname(await getBundledRuntimePath()), "runtime.node"); + } // Load the FFI host lazily so the native `koffi` addon (and its // platform-specific `koffi.node`) is only loaded on the in-process path; // out-of-process (stdio/tcp) consumers never touch the native dependency. @@ -2645,12 +2830,7 @@ export class CopilotClient { args.push("--remote"); } - const host = FfiRuntimeHost.create( - entrypoint, - CopilotClient.getNapiPrebuildsFolder(entrypoint), - environment, - args - ); + const host = FfiRuntimeHost.create(runtimeLibrary, explicitEntrypoint, environment, args); this.ffiHost = host; await host.start(); } @@ -2673,20 +2853,6 @@ export class CopilotClient { this.connection.listen(); } - /** - * Resolves the CLI entrypoint used for in-process FFI hosting: `COPILOT_CLI_PATH` - * when set, otherwise the bundled platform-package entrypoint. - */ - private resolveCliPathForFfi(): string { - return this.resolvedEnv.COPILOT_CLI_PATH ?? getBundledCliPath(); - } - - /** - * Returns the napi prebuilds folder name for the current host — the - * `-` convention (e.g. `win32-x64`, `darwin-arm64`, - * `linux-x64`, `linuxmusl-x64`) under which the runtime ships - * `prebuilds//runtime.node`. - */ private static getNapiPrebuildsFolder(entrypoint: string): string { const arch = process.arch; if (arch !== "x64" && arch !== "arm64") { @@ -2730,6 +2896,10 @@ export class CopilotClient { } this.state = "error"; const reason = err instanceof Error ? (err.stack ?? err.message) : String(err); + const stderrOutput = this.stderrBuffer.trim(); + this.processTransportError = new Error( + `CLI server connection failed: ${reason}${stderrOutput ? `\nstderr: ${stderrOutput}` : ""}` + ); this.logDebug(`stdin pipe error: ${reason}`); try { this.connection?.dispose(); @@ -2879,6 +3049,7 @@ export class CopilotClient { this.connection.onClose(() => { this.state = "disconnected"; + this.githubTokenProviders.clear(); }); this.connection.onError((_error) => { diff --git a/nodejs/src/extension.ts b/nodejs/src/extension.ts index c3ae0fd874..ac0ccdb7ce 100644 --- a/nodejs/src/extension.ts +++ b/nodejs/src/extension.ts @@ -27,6 +27,35 @@ export type JoinSessionConfig = Omit< "onPermissionRequest" | "extensionSdkPath" > & { onPermissionRequest?: PermissionHandler; + /** + * Names of sensitive environment variables this extension needs, such as + * `"GITHUB_TOKEN"`. + * + * The Copilot CLI strips sensitive variables from every extension process + * before it starts, so an extension that needs one must ask for it by name. + * The CLI prompts the user with the extension's name and the exact list of + * variables requested. On approval the granted values are written into this + * process's `process.env` before {@link joinSession} resolves, so they are + * readable afterwards. On denial the join rejects and the extension does not + * load, so its tools never reach the model. + * + * An approval is remembered against the exact set of names the user saw, so + * asking for an additional variable later prompts again. Names that are unset + * or that the CLI does not filter from extensions are not prompted for. An + * empty list means the same as omitting the option: nothing is requested. + * + * Requires a Copilot CLI that supports extension environment access; older + * CLIs ignore the request and grant nothing. + * + * @example + * ```typescript + * const session = await joinSession({ + * requestedEnvironmentVariables: ["GITHUB_TOKEN"], + * }); + * const token = process.env.GITHUB_TOKEN; + * ``` + */ + requestedEnvironmentVariables?: string[]; /** * Factory handles to register when the extension joins the session. * @@ -56,6 +85,8 @@ export { type FactoryRunResult, type FactoryRunStatus, type FactoryRunSummary, + type FactoryListRunsOptions, + type FactoryRunsPage, type FactoryRunDetail, type FactoryProgressPage, type FactoryProgressLine, @@ -94,6 +125,7 @@ export async function joinSession(config: JoinSessionConfig = {}): Promise & { - /** Completed factory result. */ - result?: JsonValue; -}; - +export type { FactoryRunResult }; export type { FactoryAgentSummary, FactoryPhaseStatus, @@ -49,6 +28,22 @@ export type { FactoryRunSummary, } from "./generated/rpc.js"; +/** + * Options for paging durable factory runs. + * + * @experimental Part of the experimental Agent Factories surface and may + * change or be removed in future SDK or CLI releases. + */ +export type FactoryListRunsOptions = FactoryListRunsRequest; + +/** + * A page of durable factory runs and its paging metadata. + * + * @experimental Part of the experimental Agent Factories surface and may + * change or be removed in future SDK or CLI releases. + */ +export type FactoryRunsPage = FactoryListRunsResult; + /** * Run statuses a factory run can no longer move away from. * @@ -85,12 +80,16 @@ export type JsonValue = | { [key: string]: JsonValue }; /** - * Conservative JSON shape language accepted for structured factory agent output. + * 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 — **not** a full JSON Schema + * 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`. + * `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 @@ -115,8 +114,20 @@ 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. * @@ -185,7 +196,10 @@ export interface FactoryContext { factory(name: string, args?: JsonValue): Promise; /** Caller-supplied input, forwarded verbatim. */ args: TArgs; - /** The same full session instance returned by `joinSession`. */ + /** + * 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; @@ -246,6 +260,10 @@ export interface RunOptions { args?: TArgs; /** Optional per-invocation resource ceiling overrides. */ limits?: FactoryLimits; + /** Whether to notify the originating session when the factory completes. */ + notifyOnComplete?: boolean; + /** Whether to emit factory phase names to the session transcript. */ + logPhaseNames?: boolean; /** * Prior run whose persisted identity, arguments, journal, and accounting should be resumed. * @@ -263,6 +281,10 @@ export interface RunOptions { export interface ResumeOptions { /** Optional per-invocation resource ceiling overrides. */ limits?: FactoryLimits; + /** Whether to notify the originating session when the factory completes. */ + notifyOnComplete?: boolean; + /** Whether to emit factory phase names to the session transcript. */ + logPhaseNames?: boolean; } /** @@ -275,8 +297,11 @@ export type FactoryResumeErrorCode = | "not_found" | "non_resumable" | "already_active" - | "reapproval_declined" - | "no_approval_provider"; + | "factory_already_running" + | "factory_limits_invalid" + | "factory_session_disposed" + | "factory_storage_unavailable" + | "factory_storage_corrupt"; /** * Friendly factory API exposed on a session. @@ -290,9 +315,12 @@ export interface SessionFactoryApi { * * The envelope is returned for every outcome, including `error`, `halted`, * and `cancelled` — inspect `status` and read `result` only when the run - * completed. A declined fresh run resolves with a terminal `cancelled` - * envelope. Failures that occur before a run exists (such as an unknown - * factory or an already-active session) still reject. + * 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( @@ -302,9 +330,9 @@ export interface SessionFactoryApi { /** * Resume a run from its persisted factory name, arguments, journal, and accounting. * - * Resolves with the run envelope like {@link SessionFactoryApi.run}. A - * pre-execution failure, including declined reapproval, rejects with - * {@link FactoryResumeError}. + * 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. */ @@ -324,8 +352,22 @@ export interface SessionFactoryApi { * {@link SessionFactoryApi.cancel} to actually stop it. */ waitForRun(runId: string, options?: { signal?: AbortSignal }): Promise; - /** List this session's durable factory runs in creation order. */ + /** + * List the newest default page of this session's durable factory runs. + * + * This backwards-compatible overload returns only the runs array. Pass + * paging options to receive the full page, including its cursors and + * truncation metadata. + */ listRuns(): Promise; + /** + * Page this session's durable factory runs. + * + * `afterSeq` and `beforeSeq` are exclusive cursors. The result includes + * `oldestSeq`, `newestSeq`, `hasMoreNewer`, and `omittedOlder` so callers + * can continue paging without using the raw RPC client. + */ + listRuns(options: FactoryListRunsOptions): 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. */ diff --git a/nodejs/src/ffiRuntimeHost.ts b/nodejs/src/ffiRuntimeHost.ts index a92aa1589a..4795e325ce 100644 --- a/nodejs/src/ffiRuntimeHost.ts +++ b/nodejs/src/ffiRuntimeHost.ts @@ -7,10 +7,8 @@ * and speaking JSON-RPC over its C ABI (FFI) instead of spawning a CLI child process * and communicating over stdio/TCP. * - * The native `host_start` export spawns the CLI worker itself - * (`node --embedded-host` for a `.js` entrypoint, or ` - * --embedded-host` for a packaged binary), so the SDK never launches the worker - * directly. LSP `Content-Length:`-framed JSON-RPC bytes are pumped across the ABI: + * The native `host_start` export constructs the Rust server synchronously in this + * process. LSP `Content-Length:`-framed JSON-RPC bytes are pumped across the ABI: * writes go to `connection_write`; inbound frames arrive on a native callback that * feeds {@link FfiRuntimeHost.receiveStream}. The existing `vscode-jsonrpc` * `StreamMessageReader`/`StreamMessageWriter` handle framing unchanged — this is a @@ -19,7 +17,7 @@ import { existsSync } from "node:fs"; import koffi from "koffi"; -import { dirname, join, resolve } from "node:path"; +import { resolve } from "node:path"; import { PassThrough, Writable } from "node:stream"; const SYMBOL_PREFIX = "copilot_runtime_"; @@ -97,14 +95,12 @@ function loadLibrary(libraryPath: string): FfiLibrary { return loadedLibrary; } -function buildArgvJson(cliEntrypoint: string, args: readonly string[]): Buffer { - // A `.js` entrypoint is launched via node; the packaged single-file CLI binary - // embeds its own Node and is invoked directly. `--no-auto-update` pins the worker - // to the bundled pkg matching the loaded cdylib, instead of drifting to a newer - // version installed under the user's `~/.copilot/pkg` (which would cause ABI skew). - const argv = cliEntrypoint.toLowerCase().endsWith(".js") - ? ["node", cliEntrypoint, "--embedded-host", "--no-auto-update"] - : [cliEntrypoint, "--embedded-host", "--no-auto-update"]; +function buildArgvJson(cliEntrypoint: string | undefined, args: readonly string[]): Buffer { + const argv = cliEntrypoint + ? cliEntrypoint.toLowerCase().endsWith(".js") + ? ["node", cliEntrypoint, "--embedded-host", "--no-auto-update"] + : [cliEntrypoint, "--embedded-host", "--no-auto-update"] + : []; argv.push(...args); return Buffer.from(JSON.stringify(argv), "utf8"); } @@ -140,7 +136,7 @@ export class FfiRuntimeHost { private constructor( private readonly libraryPath: string, - private readonly cliEntrypoint: string, + private readonly cliEntrypoint: string | undefined, private readonly environment: Record | undefined, private readonly args: readonly string[] ) { @@ -161,41 +157,38 @@ export class FfiRuntimeHost { } /** - * Resolves the cdylib next to the given CLI entrypoint and prepares the FFI host. - * The cdylib is resolved as `prebuilds//runtime.node` relative to - * the entrypoint directory (the napi-rs `-` layout, e.g. - * `linux-x64`). Throws if it cannot be found. + * Loads the runtime cdylib at the given path and prepares the FFI host. */ static create( - cliEntrypoint: string, - prebuildsFolder: string, + libraryPath: string, + cliEntrypoint: string | undefined, environment: Record | undefined, args: readonly string[] ): FfiRuntimeHost { - const fullEntrypoint = resolve(cliEntrypoint); - const distDir = dirname(fullEntrypoint); - const libraryPath = join(distDir, "prebuilds", prebuildsFolder, "runtime.node"); - if (!existsSync(libraryPath)) { - throw new Error(`FFI runtime library not found. Looked for '${libraryPath}'.`); + const fullLibraryPath = resolve(libraryPath); + if (!existsSync(fullLibraryPath)) { + throw new Error(`FFI runtime library not found at '${fullLibraryPath}'.`); } - return new FfiRuntimeHost(libraryPath, fullEntrypoint, environment, args); + return new FfiRuntimeHost( + fullLibraryPath, + cliEntrypoint ? resolve(cliEntrypoint) : undefined, + environment, + args + ); } - /** - * Starts the in-process runtime: spawns the CLI worker via the native host, - * waits for readiness, and opens the FFI JSON-RPC connection. - */ + /** Starts the in-process Rust runtime and opens the FFI JSON-RPC connection. */ async start(): Promise { const argvJson = buildArgvJson(this.cliEntrypoint, this.args); const envJson = buildEnvJson(this.environment); - // The native host spawns the CLI worker itself and has no cwd parameter, so the - // worker inherits this process's cwd. A custom working directory is intentionally + // The native host has no cwd parameter, so it uses this process's cwd. A custom + // working directory is intentionally // unsupported for the in-process transport (rejected by the client constructor) // rather than mutating the shared process-global cwd here. - // host_start blocks until the worker connects back and signals readiness - // (up to ~30s); run it as an async FFI call so the Node event loop isn't blocked. + // host_start constructs the native engine synchronously; run it as an async FFI + // call so the Node event loop isn't blocked. this.serverId = await new Promise((resolvePromise, rejectPromise) => { this.lib.hostStart.async( argvJson, @@ -212,9 +205,7 @@ export class FfiRuntimeHost { ); }); if (!this.serverId) { - throw new Error( - `copilot_runtime_host_start failed (library '${this.libraryPath}', entrypoint '${this.cliEntrypoint}').` - ); + throw new Error(`copilot_runtime_host_start failed (library '${this.libraryPath}').`); } this.outboundCallback = koffi.register( diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts index cdd3b29eba..716f76cddc 100644 --- a/nodejs/src/generated/rpc.ts +++ b/nodejs/src/generated/rpc.ts @@ -5,10 +5,20 @@ 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"; +import type { AbortReason, AgentModelPolicy, Attachment, AutoTier, ContextTier, EmbeddedBlobResourceContents, EmbeddedTextResourceContents, McpOauthHttpResponse, McpOauthWWWAuthenticateParams, McpServerSource, McpServerStatus, ModelChangeSource, PermissionMode, PermissionPromptRequest, PermissionRule, ReasoningSummary, SessionEvent, SessionLimitsConfig, SessionMode, ShutdownType, SkillSource, TaskCompleteData, TaskCompletionOutcome, 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. + * Authentication credentials accepted only at native protocol ingress. Runtime outputs use credential-free `AuthIdentity` metadata. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "AuthInfo". @@ -18,10 +28,21 @@ export type AuthInfo = | HMACAuthInfo | EnvAuthInfo | TokenAuthInfo + | TokenProviderAuthInfo | CopilotApiTokenAuthInfo | UserAuthInfo | GhCliAuthInfo | ApiKeyAuthInfo; +/** + * User to log out + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "AccountLogoutRequest". + */ +/** @experimental */ +export type AccountLogoutRequest = { + [k: string]: unknown | undefined; +}; /** * Resolved Anthropic adaptive-thinking capability for a model. * @@ -223,20 +244,6 @@ export type AgentRegistrySpawnValidationErrorField = | "model" /** The permissionMode parameter */ | "permissionMode"; -/** - * Current or requested allow-all mode. - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "PermissionsAllowAllMode". - */ -/** @experimental */ -export type PermissionsAllowAllMode = - /** Permission requests follow the normal approval flow. */ - | "off" - /** Tool, path, and URL permission requests are automatically approved. */ - | "on" - /** 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. */ - | "auto"; /** * Authentication type * @@ -257,8 +264,405 @@ export type AuthInfoType = | "api-key" /** Authentication from a GitHub token. */ | "token" + /** Authentication from an SDK GitHub token callback. */ + | "token-provider" /** Authentication from a Copilot API token. */ | "copilot-api-token"; +/** + * Validation errors from the most recent authentication attempt. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "AuthValidationErrors". + */ +/** @experimental */ +export type AuthValidationErrors = AuthValidationError[]; +/** + * Root JSON Schema type for a built-in tool input. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "BuiltinToolInputSchemaType". + */ +/** @experimental */ +export type BuiltinToolInputSchemaType = /** The tool accepts a JSON object. */ "object"; +/** + * Custom input-format kind. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "BuiltinToolFormatType". + */ +/** @experimental */ +export type BuiltinToolFormatType = /** The tool input is parsed with the supplied grammar. */ "grammar"; +/** + * Telemetry-safety policy for a built-in tool. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "BuiltinToolSafeForTelemetry". + */ +/** @experimental */ +export type BuiltinToolSafeForTelemetry = boolean | BuiltinToolSafeTelemetryFields; +/** + * 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; +/** + * Canonical digest algorithm for a validated MCP card + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CardDigestAlgorithm". + */ +/** @experimental */ +export type CardDigestAlgorithm = /** SHA-256 over RFC 8785 canonical JSON encoded as UTF-8. */ "sha256-rfc8785"; +/** + * SHA-256 digest encoded as exactly 64 lowercase hexadecimal characters. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CardDigestValue". + */ +/** @experimental */ +export type CardDigestValue = string; +/** + * Where a candidate's card came from. Exactly one of a URL or embedded data: the union has no variant carrying both, and no variant carrying neither, so the rule holds structurally rather than by validation. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogCandidateSource". + */ +/** @experimental */ +export type CatalogCandidateSource = CatalogCandidateSourceUrl | CatalogCandidateSourceEmbedded; +/** + * Why the catalog authority did not accept the caller's identity + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogAuthenticationRequiredReason". + */ +/** @experimental */ +export type CatalogAuthenticationRequiredReason = + /** No credential was presented, so there is nothing to refresh and the caller must sign in. */ + | "no-credential" + /** A credential was presented and its lifetime has elapsed. A silent refresh is worth attempting before prompting anyone. */ + | "credential-expired" + /** A credential was presented and the authority refused it, for example because it was revoked, malformed, or issued for another audience. Refreshing the same rejected credential is not useful; the caller must sign in again. */ + | "credential-rejected"; +/** + * One inert catalog result, represented as an MCP server or discovery-only AI skill variant so kind, media type, provenance, and installability cannot contradict each other. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogCandidate". + */ +/** @experimental */ +export type CatalogCandidate = CatalogMcpServerCandidate | CatalogAiSkillCandidate; +/** + * JSON MCP card media type accepted for install planning + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpServerCardMediaType". + */ +/** @experimental */ +export type McpServerCardMediaType = + /** The current MCP server card media type. */ + | "application/mcp-server-card+json" + /** The legacy MCP server card media type, accepted for compatibility. */ + | "application/mcp-server+json"; +/** + * Whether an MCP server candidate can be planned for installation + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogMcpServerInstallability". + */ +/** @experimental */ +export type CatalogMcpServerInstallability = + /** An install plan can be computed for this MCP server candidate. */ + | "installable" + /** Policy forbids installing this MCP server candidate. */ + | "not-installable-policy"; +/** + * What kind of resource a catalog candidate describes + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogCandidateKind". + */ +/** @experimental */ +export type CatalogCandidateKind = + /** An MCP server, which can be planned for installation. */ + | "mcp-server" + /** An AI skill, which is discoverable but not installable through this surface. */ + | "ai-skill"; +/** + * A wire feature a caller can require of the catalog surface, negotiated per request. A grant means the runtime understands the feature's contract, not that the deployment has enabled the operation; typed unavailable results report availability separately. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogCapability". + */ +/** @experimental */ +export type CatalogCapability = + /** Understands the current `application/mcp-server-card+json` media type. */ + | "mcp-server-card" + /** Understands the legacy `application/mcp-server+json` media type. */ + | "legacy-mcp-server-card" + /** Understands `application/ai-skill` candidates as discovery-only and typed non-installable. */ + | "ai-skill-discovery" + /** Understands side-effect-free MCP install-plan requests, results, and plan handles; `planning-unavailable` separately reports that planning is not enabled. */ + | "mcp-install-planning" + /** Understands plans that enumerate every eligible transport rather than a single preferred one. */ + | "multiple-transport-choice"; +/** + * Bounded extensible wire-feature identifier. Known values are described by `CatalogCapability`; newer callers may send future identifiers so an older runtime can return a typed negotiation refusal instead of failing schema validation. Capability negotiation establishes contract understanding, while each operation's result separately reports runtime availability. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogCapabilityId". + */ +/** @experimental */ +export type CatalogCapabilityId = string; +/** + * Which wire-contract rule an upstream response broke + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogContractViolationReason". + */ +/** @experimental */ +export type CatalogContractViolationReason = + /** A result carried both a URL and embedded data, when exactly one is permitted. */ + | "both-url-and-data" + /** A result carried neither a URL nor embedded data, when exactly one is required. */ + | "neither-url-nor-data" + /** Two results claimed the same normalised identity. */ + | "duplicate-identity" + /** A result declared no media type, or one this contract does not model. */ + | "unknown-media-type"; +/** + * Which kind of opaque handle was presented + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogHandleType". + */ +/** @experimental */ +export type CatalogHandleType = + /** A search candidate handle. */ + | "candidate" + /** An install plan handle. */ + | "plan"; +/** + * Why a presented handle was rejected + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogHandleRejectionReason". + */ +/** @experimental */ +export type CatalogHandleRejectionReason = + /** The handle is unparseable, unknown, or was issued for a different operation. */ + | "invalid" + /** The handle's time to live has elapsed. */ + | "stale" + /** The handle has already been used, and handles are single-use. */ + | "replayed" + /** The handle was issued by a different runtime instance. */ + | "foreign"; +/** + * Which request field was rejected before any work was done + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogInvalidRequestField". + */ +/** @experimental */ +export type CatalogInvalidRequestField = + /** The search query was empty or longer than permitted. */ + | "query" + /** The requested result count fell outside its permitted range. */ + | "limit" + /** The requested candidate kinds were empty or contained a duplicate. */ + | "kinds" + /** The negotiation block was missing or malformed. */ + | "contract" + /** The plan source was missing or malformed. */ + | "source" + /** The supplied card was missing its media type, URL, or data. */ + | "card" + /** The requested configuration scope is not one this runtime writes. */ + | "scope"; +/** + * How a card failed validation + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogMalformedCardReason". + */ +/** @experimental */ +export type CatalogMalformedCardReason = + /** The document is not well-formed JSON. */ + | "invalid-json" + /** The document does not satisfy its media type's schema. */ + | "schema-violation" + /** The declared media type is not one this runtime understands. */ + | "unsupported-media-type" + /** A field the media type requires is absent. */ + | "missing-required-field" + /** The document exceeded the permitted size. */ + | "size-limit-exceeded"; +/** + * Media type a catalog card is interpreted as + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogMediaType". + */ +/** @experimental */ +export type CatalogMediaType = + /** The current MCP server card media type. */ + | "application/mcp-server-card+json" + /** The legacy MCP server card media type, accepted for compatibility. */ + | "application/mcp-server+json" + /** An AI skill card. Representable and searchable, but typed non-installable. */ + | "application/ai-skill"; +/** + * Why capability and protocol-version negotiation refused a caller + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogNegotiationRefusedReason". + */ +/** @experimental */ +export type CatalogNegotiationRefusedReason = + /** The caller's protocol version is below the lowest this runtime serves. */ + | "unsupported-protocol-version" + /** The caller requires at least one capability this runtime cannot honour. */ + | "unsupported-capability"; +/** + * Categorised network failure, low cardinality so it can be aggregated without carrying a URL + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogNetworkFailureReason". + */ +/** @experimental */ +export type CatalogNetworkFailureReason = + /** No network is available, so nothing was attempted. */ + | "offline" + /** The authority's name could not be resolved. */ + | "dns" + /** The request exceeded its time budget. */ + | "timeout" + /** The TLS handshake or certificate validation failed. */ + | "tls" + /** The connection was refused or reset. */ + | "connection-refused" + /** The configured proxy returned 407 and requires authentication. */ + | "proxy-authentication-required" + /** The authority rate-limited requests and supplied or implied a bounded cooldown. */ + | "rate-limited" + /** The authority returned a transient 5xx response. */ + | "service-unavailable" + /** The authority returned another status the runtime treats as a failure. */ + | "http-status" + /** The response exceeded the permitted size. */ + | "response-too-large" + /** A redirect was refused by the runtime's redirect policy. */ + | "redirect-rejected"; +/** + * Why a discoverable candidate cannot be installed + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogNotInstallableReason". + */ +/** @experimental */ +export type CatalogNotInstallableReason = + /** This kind of resource is not installable through this surface. */ + | "kind-not-installable" + /** AI skills are discoverable but have no typed importer in this phase. */ + | "ai-skill-not-installable" + /** Policy forbids installing this candidate. */ + | "policy-forbids"; +/** + * Which authority produced a policy decision + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanPolicySource". + */ +/** @experimental */ +export type McpPlanPolicySource = + /** No policy applied, so the server is permitted by default. */ + | "none" + /** An enterprise allowlist evaluated the server. */ + | "enterprise-allowlist" + /** The registry the card came from evaluated the server. */ + | "registry-policy" + /** Local trust settings evaluated the server. */ + | "local-trust"; +/** + * Outcome of a catalog.search call: either bounded inert candidates, or one typed refusal. Never a partial success. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogSearchResult". + */ +/** @experimental */ +export type CatalogSearchResult = + | CatalogSearchSucceeded + | CatalogNegotiationRefusedError + | CatalogUnsupportedKindError + | CatalogInvalidRequestError + | CatalogAuthenticationRequiredError + | CatalogPolicyRejectedError + | CatalogNetworkFailureError + | CatalogUnsafeRetrievalError + | CatalogMalformedCardError + | CatalogContractViolationError + | CatalogUnavailableError; +/** + * Which hardened-fetch control refused a retrieval + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogUnsafeRetrievalReason". + */ +/** @experimental */ +export type CatalogUnsafeRetrievalReason = + /** The URL used a scheme the runtime refuses to fetch. */ + | "blocked-scheme" + /** The URL embedded credentials. */ + | "credentials-in-url" + /** The URL resolved to a loopback, private, link-local, or cloud metadata address. */ + | "blocked-address" + /** A redirect target resolved to a blocked address. */ + | "redirect-to-blocked-address" + /** The configured proxy policy refused the request. */ + | "proxy-rejected" + /** The authority is not permitted for card retrieval. */ + | "host-not-permitted"; +/** + * Why a catalog operation is not available on this runtime + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogUnavailableReason". + */ +/** @experimental */ +export type CatalogUnavailableReason = + /** Bounded search is not wired up on this runtime build. */ + | "search-unavailable" + /** Install planning is not wired up on this runtime build. */ + | "planning-unavailable" + /** No catalog authority is configured for this runtime. */ + | "authority-not-configured" + /** The surface is disabled by policy on this runtime. */ + | "disabled-by-policy"; +/** + * Why no usable transport could be offered + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogUnavailableTransportReason". + */ +/** @experimental */ +export type CatalogUnavailableTransportReason = + /** The card advertises no transport this runtime can use. */ + | "no-eligible-transport" + /** Every advertised transport is of a kind this runtime does not implement. */ + | "transport-not-supported" + /** Eligible remotes could not be enumerated, so no explicit choice can be offered. */ + | "remote-enumeration-unavailable"; /** * Coarse command category for grouping and behavior: runtime built-in, skill-backed command, or SDK/client-owned command * @@ -281,6 +685,21 @@ export type SlashCommandKind = */ /** @experimental */ export type SlashCommandInputCompletion = /** Input should complete filesystem directories. */ "directory"; +/** + * Whether a pending slash-command invocation effect was applied or cancelled by the host. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CommandsInvocationEffectOutcome". + */ +/** @experimental */ +export type CommandsInvocationEffectOutcome = + /** The host applied the pending invocation effect. */ + | "applied" + /** The host cancelled the pending invocation effect, so any provisional state must be reverted. */ + | "cancelled"; + +/** @experimental */ +export type CommandsInvocationOrigin = "settings"; /** * Optional filters controlling which command sources to include in the listing. * @@ -373,6 +792,9 @@ export type DebugCollectLogsDestination = * When true, create the archive atomically without overwriting an existing file by appending ` (N)` before the extension as needed. Defaults to false. */ noOverwrite?: boolean; + /** + * Destination variant discriminator. + */ kind: "archive"; } | { @@ -380,6 +802,9 @@ export type DebugCollectLogsDestination = * Directory where redacted files should be staged. The directory is created if needed. */ outputDirectory: string; + /** + * Destination variant discriminator. + */ kind: "directory"; }; /** @@ -444,6 +869,64 @@ export type DiscoveredExtensionMode = | "load_only" /** Extensions are loaded and the agent can create, reload, and manage them. */ | "load_and_augment"; +/** + * Hook event name. Discovery emits the file-configurable subset; SDK callbacks additionally support callback-only events. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "HookType". + */ +/** @experimental */ +export type HookType = + /** Runs before a tool is invoked. */ + | "preToolUse" + /** Runs before an MCP tool is invoked. */ + | "preMcpToolCall" + /** Runs after a tool completes successfully. */ + | "postToolUse" + /** Runs after a tool fails. */ + | "postToolUseFailure" + /** Runs after the user submits a prompt. */ + | "userPromptSubmitted" + /** Runs after the runtime transforms the submitted prompt for the model, before it is added to session history. */ + | "userPromptTransformed" + /** Runs when a session starts. */ + | "sessionStart" + /** Runs when a session ends. */ + | "sessionEnd" + /** Runs after an agent result is produced. */ + | "postResult" + /** Runs before a pull request description is generated. */ + | "prePRDescription" + /** Runs when the agent encounters an error. */ + | "errorOccurred" + /** Runs when the agent stops. */ + | "agentStop" + /** Runs when a subagent starts. */ + | "subagentStart" + /** Runs when a subagent stops. */ + | "subagentStop" + /** Runs before conversation context is compacted. */ + | "preCompact" + /** Runs when the agent requests permission. */ + | "permissionRequest" + /** Runs when the agent emits a notification. */ + | "notification"; +/** + * Configuration tier that contributed a discovered hook action. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "HookOrigin". + */ +/** @experimental */ +export type HookOrigin = + /** Hook loaded from user settings or the user's hook directory. */ + | "user" + /** Hook loaded from repository settings or the repository hook directory. */ + | "repository" + /** Hook provided by an enabled installed or explicit plugin. Projectless rows omit projectPath and do not expand a project directory. */ + | "plugin" + /** Hook enforced by centrally managed policy. */ + | "policy"; /** * Server transport type: stdio, http, sse (deprecated), or memory * @@ -620,7 +1103,9 @@ export type FactoryDurableOperation = /** Reading a journal entry without treating storage failure as a cache miss. */ | "journalGet" /** Persisting a journal entry before reporting success. */ - | "journalPut"; + | "journalPut" + /** Renewing the durable owner lease that proves this process still owns the run. */ + | "refreshLease"; /** * Current or terminal state of a factory run. * @@ -659,6 +1144,9 @@ export type FactoryRunFailure = * Factory run identifier. */ runId: string; + /** + * Factory failure variant discriminator. + */ type: "factory_limit_reached"; } | { @@ -670,6 +1158,9 @@ export type FactoryRunFailure = * Human-readable reason the resume did not proceed. */ reason: string; + /** + * Factory failure variant discriminator. + */ type: "factory_resume_declined"; } | { @@ -682,21 +1173,48 @@ export type FactoryRunFailure = * Factory run identifier. */ runId: string; + /** + * Factory failure variant discriminator. + */ type: "factory_durable_failure"; - }; -/** - * Cumulative resource ceiling that stopped a factory run. - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "FactoryRunFailureKind". - */ -/** @experimental */ -export type FactoryRunFailureKind = - /** The run admitted the approved maximum total number of subagents. */ - | "maxTotalSubagents" - /** 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. */ + } + | { + /** + * Factory run identifier. + */ + runId: string; + /** + * Confirmed usage in nano-AIU, representing the floor of what the run spent. + */ + drainedNanoAiu: number; + /** + * Factory failure variant discriminator. + */ + type: "factory_accounting_incomplete"; + } + | { + /** + * Factory run identifier. + */ + runId: string; + /** + * Factory failure variant discriminator. + */ + type: "factory_provider_disconnected"; + }; +/** + * Cumulative resource ceiling that stopped a factory run. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "FactoryRunFailureKind". + */ +/** @experimental */ +export type FactoryRunFailureKind = + /** The run admitted the approved maximum total number of subagents. */ + | "maxTotalSubagents" + /** 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"; /** * Kind of factory progress line. @@ -738,6 +1256,50 @@ export type FilterMapping = [k: string]: ContentFilterMode; } | ContentFilterMode; +/** + * Why the runtime is requesting a GitHub credential. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "GitHubTokenAcquireReason". + */ +/** @experimental */ +export type GitHubTokenAcquireReason = + /** The runtime is acquiring the registration's first credential. */ + | "initial" + /** The runtime is replacing a credential that is approaching expiry. */ + | "refresh"; +/** + * SDK host response to a GitHub credential request. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "GitHubTokenAcquireResult". + */ +/** @experimental */ +export type GitHubTokenAcquireResult = + | { + /** + * GitHub access token acquired by the SDK host. + */ + accessToken: string; + /** + * OAuth token type. Defaults to bearer when omitted. + */ + tokenType?: string; + /** + * Remaining token lifetime in seconds when callback execution completes. It must exceed the one-hour preflight refresh threshold. + */ + expiresIn: number; + /** + * GitHub credential response variant discriminator. + */ + kind: "token"; + } + | { + /** + * GitHub credential response variant discriminator. + */ + kind: "cancelled"; + }; /** * Optional compaction parameters. * @@ -844,49 +1406,6 @@ export type HistoryRewindOutcome = | "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. - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "HookType". - */ -/** @experimental */ -/** @internal */ -export type HookType = - /** Runs before a tool is invoked. */ - | "preToolUse" - /** Runs before an MCP tool is invoked. */ - | "preMcpToolCall" - /** Runs after a tool completes successfully. */ - | "postToolUse" - /** Runs after a tool fails. */ - | "postToolUseFailure" - /** Runs after the user submits a prompt. */ - | "userPromptSubmitted" - /** Runs after the runtime transforms the submitted prompt for the model, before it is added to session history. */ - | "userPromptTransformed" - /** Runs when a session starts. */ - | "sessionStart" - /** Runs when a session ends. */ - | "sessionEnd" - /** Runs after an agent result is produced. */ - | "postResult" - /** Runs before a pull request description is generated. */ - | "prePRDescription" - /** Runs when the agent encounters an error. */ - | "errorOccurred" - /** Runs when the agent stops. */ - | "agentStop" - /** Runs when a subagent starts. */ - | "subagentStart" - /** Runs when a subagent stops. */ - | "subagentStop" - /** Runs before conversation context is compacted. */ - | "preCompact" - /** Runs when the agent requests permission. */ - | "permissionRequest" - /** Runs when the agent emits a notification. */ - | "notification"; /** * Source for direct repo installs (when marketplace is empty) * @@ -1112,13 +1631,21 @@ export type McpAppsSetHostContextDetailsPlatform = /** Host runs on a mobile device */ | "mobile"; /** - * MCP server configuration (stdio process or remote HTTP/SSE) + * Serializable MCP server configuration (stdio process or remote HTTP/SSE) * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "McpServerConfig". + * via the `definition` "McpSerializableServerConfig". + */ +/** @experimental */ +export type McpSerializableServerConfig = McpServerConfigStdio | McpServerConfigHttp; +/** + * Telemetry-obfuscation policy for an MCP server's tools. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpSafeForTelemetry". */ /** @experimental */ -export type McpServerConfig = McpServerConfigStdio | McpServerConfigHttp; +export type McpSafeForTelemetry = boolean | McpSafeForTelemetryFields; /** * Set to `true` to use defaults, or provide an object with additional auth or OIDC settings. * @@ -1139,6 +1666,18 @@ export type McpServerConfigDeferTools = | "auto" /** Tools are always included in the initial tool list, even when tool search is enabled. */ | "never"; +/** + * Local MCP transport type. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpServerConfigStdioType". + */ +/** @experimental */ +export type McpServerConfigStdioType = + /** Legacy alias for the local stdio transport. */ + | "local" + /** Server communicates over stdio with a local child process. */ + | "stdio"; /** * Remote transport type. Defaults to "http" when omitted. * @@ -1163,6 +1702,14 @@ export type McpServerConfigHttpOauthGrantType = | "authorization_code" /** Headless client credentials flow using the configured OAuth client. */ | "client_credentials"; +/** + * Structured MCP elicitation mode. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpElicitationFormMode". + */ +/** @experimental */ +export type McpElicitationFormMode = "form"; /** * Host response: supply dynamic headers or decline this refresh. * @@ -1178,11 +1725,173 @@ export type McpHeadersHandlePendingHeadersRefreshRequest = headers: { [k: string]: string | undefined; }; + /** + * Headers-refresh response variant discriminator. + */ kind: "headers"; } | { + /** + * Headers-refresh response variant discriminator. + */ kind: "none"; }; +/** + * One eligible way to run the server, represented as a tagged package or remote variant so package identity and endpoint states cannot contradict the install method. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanTransportChoice". + */ +/** @experimental */ +export type McpPlanTransportChoice = McpPlanTransportChoicePackage | McpPlanTransportChoiceRemote; +/** + * Transport exposed by a locally launched package + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanPackageTransport". + */ +/** @experimental */ +export type McpPlanPackageTransport = + /** A locally launched process spoken to over standard input and output. */ + "stdio"; +/** + * Discriminator for a package-backed transport choice + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanPackageInstallMethod". + */ +/** @experimental */ +export type McpPlanPackageInstallMethod = /** Install and run a local package. */ "package"; +/** + * One non-secret value a transport choice needs, represented as a scalar or enumerated variant so enum values cannot be missing or attached to another type. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanRequiredValue". + */ +/** @experimental */ +export type McpPlanRequiredValue = McpPlanRequiredValueScalar | McpPlanRequiredValueEnum; +/** + * Discriminator for a scalar required value + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanRequiredValueScalarKind". + */ +/** @experimental */ +export type McpPlanRequiredValueScalarKind = /** The value uses one scalar type. */ "scalar"; +/** + * Where a required value is applied when the planned server is launched + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanValueCategory". + */ +/** @experimental */ +export type McpPlanValueCategory = + /** Set as an environment variable on the launched process. */ + | "environment-variable" + /** Passed to the runtime that launches the package. */ + | "runtime-argument" + /** Passed to the packaged server itself. */ + | "package-argument" + /** Sent as a request header to a remote endpoint. */ + | "header" + /** Substituted into the remote endpoint URL. */ + | "url-variable"; +/** + * Scalar type a required value must conform to + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanScalarValueType". + */ +/** @experimental */ +export type McpPlanScalarValueType = + /** Free text. */ + | "string" + /** A number. */ + | "number" + /** A boolean. */ + | "boolean" + /** A filesystem path. */ + | "path"; +/** + * Discriminator for an enumerated required value + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanRequiredValueEnumKind". + */ +/** @experimental */ +export type McpPlanRequiredValueEnumKind = /** The value uses a fixed non-empty enumeration. */ "enum"; +/** + * Discriminator for an enumerated required value + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanEnumValueType". + */ +/** @experimental */ +export type McpPlanEnumValueType = /** One of a fixed, non-empty set of permitted values. */ "enum"; +/** + * A runtime-assigned secret placeholder. The identifier is carried once, inside the placeholder, so it cannot contradict a separate secret-id field. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanSecretReference". + */ +/** @experimental */ +export type McpPlanSecretReference = string; +/** + * Transport exposed by a remote endpoint + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanRemoteTransport". + */ +/** @experimental */ +export type McpPlanRemoteTransport = + /** An HTTP endpoint. */ + | "http" + /** A streamable HTTP endpoint. */ + | "streamable-http" + /** A server-sent events endpoint. */ + | "sse"; +/** + * Discriminator for a remote-endpoint transport choice + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanRemoteInstallMethod". + */ +/** @experimental */ +export type McpPlanRemoteInstallMethod = /** Connect to a remote endpoint. */ "remote"; +/** + * Configuration scope an MCP install plan targets + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanScope". + */ +/** @experimental */ +export type McpPlanScope = /** The user's own MCP configuration. */ "user"; +/** + * What policy decided for a planned server + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanPolicyDecision". + */ +/** @experimental */ +export type McpPlanPolicyDecision = + /** Policy permits the server. */ + | "allowed" + /** Policy forbids the server, so the plan cannot be applied. */ + | "blocked" + /** Policy permits the server only after an explicit approval. */ + | "requires-approval"; +/** + * Whether a planned configuration change would create or modify an entry + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanConfigurationOperation". + */ +/** @experimental */ +export type McpPlanConfigurationOperation = + /** Creates a configuration entry that does not exist yet. */ + | "add" + /** Modifies a configuration entry that already exists. */ + | "update"; /** * Consumer allowed to call an MCP tool. * @@ -1209,16 +1918,22 @@ export type McpOauthPendingRequestResponse = */ accessToken: string; /** - * OAuth token type. Defaults to Bearer when omitted. + * OAuth token type. Defaults to bearer when omitted. */ tokenType?: string; /** * Token lifetime in seconds, if known. */ expiresIn?: number; + /** + * OAuth response variant discriminator. + */ kind: "token"; } | { + /** + * OAuth response variant discriminator. + */ kind: "cancelled"; }; /** @@ -1234,51 +1949,250 @@ export type McpOauthLoginGrantType = /** Headless OAuth flow where a confidential client authenticates directly with a client secret. */ | "client_credentials"; /** - * 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. + * Why a passive MCP OAuth probe determined authentication is needed. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "McpSamplingExecutionAction". + * via the `definition` "McpOauthProbeNeedsAuthReason". */ /** @experimental */ -export type McpSamplingExecutionAction = - /** The sampling inference completed and produced a result. */ - | "success" - /** The sampling inference failed or was rejected. */ - | "failure" - /** The sampling inference was cancelled before completion. */ - | "cancelled"; +export type McpOauthProbeNeedsAuthReason = + /** No token was sent and the server requires authentication. */ + | "initial" + /** A cached token was sent and rejected. */ + | "refresh" + /** The server returned a 403 insufficient_scope challenge, indicating additional scopes or audience are needed. */ + | "upscope"; /** - * 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". + * Passive MCP OAuth probe result. `authenticated` means the server accepted the probe request while an OAuth-origin access token was attached; it does not prove the server required or independently validated that token. The probe does not make a second unauthenticated request. Failed is an expected probe-domain outcome; JSON-RPC errors are reserved for API-call failures. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "McpSetEnvValueModeDetails". + * via the `definition` "McpOauthProbeResult". */ /** @experimental */ -export type McpSetEnvValueModeDetails = - /** Treat MCP server environment values as literal strings. */ - | "direct" - /** Treat MCP server environment values as host-side references to resolve before launch. */ - | "indirect"; +export type McpOauthProbeResult = + | { + httpResponse: McpOauthHttpResponse; + /** + * Probe outcome variant discriminator. + */ + status: "no-auth-required"; + } + | { + httpResponse: McpOauthHttpResponse; + /** + * Probe outcome variant discriminator. + */ + status: "authenticated"; + } + | { + httpResponse: McpOauthHttpResponse; + reason: McpOauthProbeNeedsAuthReason; + wwwAuthenticateParams?: McpOauthWWWAuthenticateParams; + /** + * Probe outcome variant discriminator. + */ + status: "needs-auth"; + } + | { + /** + * Human-readable probe failure detail. + */ + error: string; + httpResponse?: McpOauthHttpResponse; + /** + * Probe outcome variant discriminator. + */ + status: "failed"; + }; /** - * Per-source context-window attribution, or null if the session has not yet been initialized (no system prompt or tool metadata cached). + * What an install plan is computed from: a candidate handle from a previous search, or a card supplied directly. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "SessionContextAttribution". + * via the `definition` "McpPlanInstallSource". */ /** @experimental */ -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; - /** - * Flat list of per-source attribution entries. Group by `kind` and render unrecognized kinds generically. Nesting and rollups are expressed via `parentId`. - */ - entries: { - /** - * Source category for this entry. Not a closed set — tolerate unknown values. Known values today: `skill`, `subagent`, `mcpServer`, `tool`, `system`, `toolDefinition`, `plugin`. - */ - kind: string; +export type McpPlanInstallSource = McpPlanInstallSourceCandidate | McpPlanInstallSourceCard; +/** + * Discriminator for a candidate-backed install-plan source + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanInstallSourceCandidateKind". + */ +/** @experimental */ +export type McpPlanInstallSourceCandidateKind = /** Plan from a candidate returned by catalog search. */ "candidate"; +/** + * Discriminator for a caller-supplied-card install-plan source + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanInstallSourceCardKind". + */ +/** @experimental */ +export type McpPlanInstallSourceCardKind = /** Plan directly from a caller-supplied card. */ "card"; +/** + * A card supplied directly by the caller. Exactly one of a URL or embedded data, encoded structurally so neither both nor neither can be expressed. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpServerCardReference". + */ +/** @experimental */ +export type McpServerCardReference = McpServerCardUrl | McpServerCardEmbedded; +/** + * Discriminator for a URL-backed MCP server card + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpServerCardUrlKind". + */ +/** @experimental */ +export type McpServerCardUrlKind = /** Retrieve the card from its URL. */ "url"; +/** + * Discriminator for an embedded MCP server card + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpServerCardEmbeddedKind". + */ +/** @experimental */ +export type McpServerCardEmbeddedKind = /** Use the embedded card document. */ "embedded"; +/** + * Outcome of an mcp.planInstall call: either a normalised plan, or one typed refusal. Nothing is written in either case. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanInstallResult". + */ +/** @experimental */ +export type McpPlanInstallResult = + | McpPlanInstallPlanned + | CatalogNegotiationRefusedError + | CatalogHandleRejectedError + | CatalogInvalidRequestError + | CatalogAuthenticationRequiredError + | CatalogPolicyRejectedError + | CatalogNetworkFailureError + | CatalogUnsafeRetrievalError + | CatalogMalformedCardError + | CatalogContractViolationError + | CatalogUnavailableTransportError + | CatalogNotInstallableError + | CatalogUnavailableError; +/** + * MCP server configuration (stdio, remote HTTP/SSE, or in-process) + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpServerConfig". + */ +/** @experimental */ +export type McpServerConfig = (McpServerConfigStdio | McpServerConfigHttp) | undefined; +/** + * 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. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpSamplingExecutionAction". + */ +/** @experimental */ +export type McpSamplingExecutionAction = + /** The sampling inference completed and produced a result. */ + | "success" + /** The sampling inference failed or was rejected. */ + | "failure" + /** The sampling inference was cancelled before completion. */ + | "cancelled"; +/** + * In-process MCP transport type. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpServerConfigMemoryType". + */ +/** @experimental */ +/** @internal */ +export type McpServerConfigMemoryType = "memory"; +/** + * 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". + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpSetEnvValueModeDetails". + */ +/** @experimental */ +export type McpSetEnvValueModeDetails = + /** Treat MCP server environment values as literal strings. */ + | "direct" + /** Treat MCP server environment values as host-side references to resolve before launch. */ + | "indirect"; +/** + * Per-source context-window attribution, or null if the session has not yet been initialized (no system prompt or tool metadata cached). + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionContextAttribution". + */ +/** @experimental */ +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`. + */ + entries: { + /** + * Source category for this entry. Not a closed set — tolerate unknown values. Known values today: `skill`, `subagent`, `mcpServer`, `tool`, `system`, `toolDefinition`, `plugin`. + */ + kind: string; /** * 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`. */ @@ -1600,7 +2514,8 @@ export type PermissionDecisionApproveForSessionApproval = | PermissionDecisionApproveForSessionApprovalCustomTool | PermissionDecisionApproveForSessionApprovalExtensionManagement | PermissionDecisionApproveForSessionApprovalFactory - | PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess; + | PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess + | PermissionDecisionApproveForSessionApprovalExtensionEnvAccess; /** * Approval to persist for this location * @@ -1618,7 +2533,70 @@ export type PermissionDecisionApproveForLocationApproval = | PermissionDecisionApproveForLocationApprovalCustomTool | PermissionDecisionApproveForLocationApprovalExtensionManagement | PermissionDecisionApproveForLocationApprovalFactory - | PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess; + | PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess + | PermissionDecisionApproveForLocationApprovalExtensionEnvAccess; +/** + * 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 assisted-approval judge recommendation. */ + | "assisted_approval" + /** 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" + /** An Agent Client Protocol host. */ + | "acp" + /** A generic Copilot SDK client. */ + | "sdk"; +/** + * Response capability available to the client when it settled a permission request. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "PermissionResponseCapability". + */ +/** @experimental */ +export type PermissionResponseCapability = + /** The client could ask a user for this decision. */ + | "interactive" + /** The client could return an automated response but could not ask a user. */ + | "headless" + /** The client had no response path available. */ + | "none"; /** * Tool approval to persist and apply * @@ -1636,7 +2614,8 @@ export type PermissionsLocationsAddToolApprovalDetails = | PermissionsLocationsAddToolApprovalDetailsCustomTool | PermissionsLocationsAddToolApprovalDetailsExtensionManagement | PermissionsLocationsAddToolApprovalDetailsFactory - | PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess; + | PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess + | PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess; /** * Whether the location is a git repo or directory * @@ -1649,6 +2628,24 @@ export type PermissionLocationType = | "repo" /** The permission location is persisted at the working directory. */ | "dir"; +/** + * Optional source for permission-mode telemetry. Defaults to `rpc` when omitted for SDK callers. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "PermissionModeSource". + */ +/** @experimental */ +export type PermissionModeSource = + /** The mode was set from a CLI command-line flag. */ + | "cli_flag" + /** The mode was set by a slash command. */ + | "slash_command" + /** The mode was set by confirming autopilot behavior. */ + | "autopilot_confirmation" + /** The mode was set at startup by the `defaultPermissionMode` user setting. */ + | "user_setting" + /** The mode was set through an RPC caller. */ + | "rpc"; /** * Allowed values for the `PermissionsConfigureAdditionalContentExclusionPolicyScope` enumeration. * @@ -1673,22 +2670,6 @@ export type PermissionsModifyRulesScope = | "session" /** Persist the rule change for this project location. */ | "location"; -/** - * Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers. - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "PermissionsSetAllowAllSource". - */ -/** @experimental */ -export type PermissionsSetAllowAllSource = - /** Allow-all was enabled from a CLI command-line flag. */ - | "cli_flag" - /** Allow-all was enabled by a slash command. */ - | "slash_command" - /** Allow-all was enabled by confirming autopilot behavior. */ - | "autopilot_confirmation" - /** Allow-all was enabled through an RPC caller. */ - | "rpc"; /** * Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers. * @@ -1703,6 +2684,8 @@ export type PermissionsSetApproveAllSource = | "slash_command" /** Allow-all was enabled by confirming autopilot behavior. */ | "autopilot_confirmation" + /** Allow-all was enabled at startup by the `defaultPermissionMode` user setting. */ + | "user_setting" /** Allow-all was enabled through an RPC caller. */ | "rpc"; /** @@ -1738,6 +2721,18 @@ export type PluginsReloadRequest = */ deferRepoHooks?: boolean; }; +/** + * Controls whether the runtime may defer loading an external tool definition. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ProtocolExternalToolDefer". + */ +/** @experimental */ +export type ProtocolExternalToolDefer = + /** The runtime may defer the tool according to its tool-loading policy. */ + | "auto" + /** The runtime must include the tool without deferring it. */ + | "never"; /** * Provider family. Matches the `type` field of a BYOK provider config. * @@ -1896,6 +2891,22 @@ export type RemoteSessionMode = | "export" /** Enable both remote session export and remote steering. */ | "on"; +/** + * What a remote host says one of its sessions is doing right now. Deliberately coarse: this is what a host can report for EVERY session in a catalogue listing, without a client subscribing to each one. AHP's `SessionSummary.status` is the source today; `input-needed` covers both a permission prompt and an `ask_user` question, since the summary does not say which. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "RemoteSessionHostStatus". + */ +/** @experimental */ +export type RemoteSessionHostStatus = + /** No turn is running. */ + | "idle" + /** A turn is running. */ + | "working" + /** The session is blocked on the user: a permission prompt or an `ask_user` question. */ + | "input-needed" + /** The session ended its last turn in an error. */ + | "error"; /** * Whether the remote task originated from CCA or CLI `--remote`. * @@ -1908,6 +2919,37 @@ export type RemoteSessionMetadataTaskType = | "cca" /** CLI remote task. */ | "cli"; +/** + * Origin of the sandbox choice supplied by an internal client. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SandboxConfigSource". + */ +/** @experimental */ +/** @internal */ +export type SandboxConfigSource = + /** The client applied the default because no sandbox preference was configured. */ + | "never_configured" + /** The user's persisted settings enabled the sandbox. */ + | "user_enabled" + /** The user's persisted settings disabled the sandbox. */ + | "user_disabled" + /** A command-line flag selected the sandbox state for this session. */ + | "session_flag" + /** The user disabled the sandbox for the current session. */ + | "session_disabled" + /** The client disabled the sandbox because the host cannot enforce it. */ + | "unsupported_host" + /** A repository policy selected the sandbox state. */ + | "repository_policy"; +/** + * Current authentication information, or null when no authentication is active. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionAuthInfoResult". + */ +/** @experimental */ +export type SessionAuthInfoResult = AuthIdentity | null; /** * Session capability enabled for this session * @@ -2084,10 +3126,16 @@ export type SessionLimitPredictionRequest = export type SessionLimitPredictionResult = | { prediction: SessionLimitPredictionDetails; + /** + * Prediction result variant discriminator. + */ kind: "available"; } | { reason: SessionLimitPredictionUnavailableReason; + /** + * Prediction result variant discriminator. + */ kind: "unavailable"; }; /** @@ -2307,6 +3355,21 @@ export type SessionsOpenProgressStatus = | "in-progress" /** The step has completed successfully. */ | "complete"; +/** + * Authentication credentials accepted by session.gitHubAuth.setCredentials. Session-owned token-provider identities cannot be installed through this method. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SettableAuthInfo". + */ +/** @experimental */ +export type SettableAuthInfo = + | HMACAuthInfo + | EnvAuthInfo + | SettableTokenAuthInfo + | CopilotApiTokenAuthInfo + | UserAuthInfo + | GhCliAuthInfo + | ApiKeyAuthInfo; /** * Rust-owned settings predicates exposed across the SDK boundary. Raw feature-flag names are intentionally not part of the contract. * @@ -2420,7 +3483,11 @@ export type SlashCommandInvocationResult = | SlashCommandTextResult | SlashCommandAgentPromptResult | SlashCommandCompletedResult - | SlashCommandSelectSubcommandResult; + | SlashCommandSelectSubcommandResult + | SlashCommandAddTimelineEntryResult + | SlashCommandShowDialogResult + | SlashCommandSetModelResult + | SlashCommandSetPlanModelResult; /** * Subagent settings to apply, or null to clear the live session override * @@ -2520,6 +3587,32 @@ export type TaskShellInfoAttachmentMode = */ /** @experimental */ export type TaskProgress = (TaskAgentProgress | TaskShellProgress) | null; +/** + * Canonical result returned by a session tool. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ToolResult". + */ +/** @experimental */ +export type ToolResult = string | ToolResultExpanded; +/** + * Execution outcome classification. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ToolResultType". + */ +/** @experimental */ +export type ToolResultType = + /** The tool completed successfully. */ + | "success" + /** The tool failed. */ + | "failure" + /** The tool exceeded its execution timeout. */ + | "timeout" + /** The tool request was rejected before execution. */ + | "rejected" + /** Permission policy denied the tool request. */ + | "denied"; /** * User's choice for auto-mode switching: yes (allow this turn), yes_always (allow + persist as setting), or no (decline). * @@ -2692,6 +3785,30 @@ export type AccountGetAllUsersResult = AccountAllUsers[]; */ /** @experimental */ export type SessionCancelAllBackgroundAgentsResult = number; +/** + * Authentication accounts available to the internal session host. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionGitHubAuthGetAllAuthAvailableResult". + */ +/** @experimental */ +export type SessionGitHubAuthGetAllAuthAvailableResult = SessionAuthStatus[]; +/** + * Whether the current authentication was logged out. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionGitHubAuthLogoutResult". + */ +/** @experimental */ +export type SessionGitHubAuthLogoutResult = boolean; +/** + * Whether the requested authentication was logged out. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionGitHubAuthLogoutUserResult". + */ +/** @experimental */ +export type SessionGitHubAuthLogoutUserResult = boolean; /** * Parameters for aborting the current turn @@ -2721,7 +3838,7 @@ export interface AbortResult { error?: string; } /** - * Authenticated account entry returned by `account.getAllUsers`, with auth info and an optional associated token. + * Authenticated account entry returned by `account.getAllUsers`. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "AccountAllUsers". @@ -2729,13 +3846,17 @@ export interface AbortResult { /** @experimental */ export interface AccountAllUsers { authInfo: AuthInfo; + /** + * Opaque identifier accepted by account and model selection APIs + */ + selectionId?: string; /** * Associated token, if available */ token?: string; } /** - * Authentication-info variant for GitHub-internal HMAC auth, carrying the public GitHub host and HMAC secret. + * Authentication-info input variant for GitHub-internal HMAC auth, carrying the public GitHub host and HMAC secret. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "HMACAuthInfo". @@ -2817,6 +3938,9 @@ export interface CopilotUserResponse { [k: string]: unknown | undefined; } | ({ + /** + * GitHub login of the organization. + */ login?: | ( | { @@ -2825,6 +3949,9 @@ export interface CopilotUserResponse { | string ) | null; + /** + * Display name of the organization. + */ name?: | ( | { @@ -2913,10 +4040,25 @@ export interface CopilotUserResponse { */ /** @experimental */ export interface CopilotUserResponseEndpoints { + /** + * Copilot API endpoint URL. + */ api?: string; + /** + * Origin-tracker endpoint URL. + */ "origin-tracker"?: string; + /** + * Copilot proxy endpoint URL. + */ proxy?: string; + /** + * Copilot telemetry endpoint URL. + */ telemetry?: string; + /** + * Experimental-service endpoint URL. + */ exp?: string; } /** @@ -3119,7 +4261,7 @@ export interface CopilotUserResponseQuotaSnapshotsPremiumInteractions { token_based_billing?: boolean; } /** - * Authentication-info variant for a token sourced from an environment variable, with host, optional login, token, and env var name. + * Authentication-info input variant for a token sourced from an environment variable, with host, optional login, token, and env var name. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "EnvAuthInfo". @@ -3149,7 +4291,7 @@ export interface EnvAuthInfo { copilotUser?: CopilotUserResponse; } /** - * Authentication-info variant for SDK-configured token authentication, carrying host and the secret token value. + * Authentication-info input variant for SDK-configured token authentication, carrying host and the secret token value. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "TokenAuthInfo". @@ -3168,18 +4310,44 @@ export interface TokenAuthInfo { * The token value itself. Treat as a secret. */ token: string; + /** + * Opaque native GitHub credential registration backing this token identity, when applicable. + */ + registrationId?: string; copilotUser?: CopilotUserResponse; } /** - * Authentication-info variant for direct Copilot API token auth sourced from environment variables, with public GitHub host. + * Authentication-info variant backed by an SDK GitHub token callback. It carries routing metadata but never a plaintext token. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "CopilotApiTokenAuthInfo". + * via the `definition` "TokenProviderAuthInfo". */ /** @experimental */ -export interface CopilotApiTokenAuthInfo { +export interface TokenProviderAuthInfo { /** - * Direct Copilot API authentication via the `GITHUB_COPILOT_API_TOKEN` + `COPILOT_API_URL` environment-variable pair. The token itself is read from the environment by the runtime, not carried in this struct. + * SDK callback-backed GitHub token authentication. + */ + type: "token-provider"; + /** + * Authentication host. + */ + host: string; + /** + * Opaque SDK callback registration identifier. + */ + registrationId: string; + copilotUser?: CopilotUserResponse; +} +/** + * Authentication-info variant for direct Copilot API token auth sourced from environment variables, with public GitHub host. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CopilotApiTokenAuthInfo". + */ +/** @experimental */ +export interface CopilotApiTokenAuthInfo { + /** + * Direct Copilot API authentication via the `GITHUB_COPILOT_API_TOKEN` + `COPILOT_API_URL` environment-variable pair. The token itself is read from the environment by the runtime, not carried in this struct. */ type: "copilot-api-token"; /** @@ -3211,7 +4379,7 @@ export interface UserAuthInfo { copilotUser?: CopilotUserResponse; } /** - * Authentication-info variant for GitHub CLI credentials, carrying host, login, and the `gh auth token` value. + * Authentication-info input variant for GitHub CLI credentials, carrying host, login, and the `gh auth token` value. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "GhCliAuthInfo". @@ -3237,7 +4405,7 @@ export interface GhCliAuthInfo { copilotUser?: CopilotUserResponse; } /** - * Authentication-info variant for API-key authentication to a non-GitHub LLM provider, carrying the secret `apiKey` and host. + * Authentication-info input variant for API-key authentication to a non-GitHub LLM provider, carrying the secret `apiKey` and host. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "ApiKeyAuthInfo". @@ -3276,7 +4444,11 @@ export interface AccountGetCurrentAuthResult { /** @experimental */ export interface AccountGetQuotaRequest { /** - * GitHub token for per-user quota lookup. When provided, resolves this token to determine the user's quota instead of using the global auth. + * Opaque account identifier returned by `account.getAllUsers`. When omitted, the current account is used. + */ + selectionId?: string; + /** + * GitHub token accepted for compatibility with existing SDK clients. When provided, resolves this token instead of using the current account. */ gitHubToken?: string; } @@ -3337,7 +4509,7 @@ export interface AccountQuotaSnapshot { resetDate?: string; } /** - * Credentials to store after successful authentication + * Credentials to validate and store. Omit login to resolve the authenticated user from the token. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "AccountLoginRequest". @@ -3349,9 +4521,9 @@ export interface AccountLoginRequest { */ host: string; /** - * User login/username + * User login/username. When omitted, the runtime validates the token and resolves the login from GitHub. */ - login: string; + login?: string; /** * GitHub authentication token */ @@ -3370,16 +4542,6 @@ export interface AccountLoginResult { */ storedInVault: boolean; } -/** - * User to log out - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "AccountLogoutRequest". - */ -/** @experimental */ -export interface AccountLogoutRequest { - authInfo: AuthInfo; -} /** * Logout result indicating if more users remain * @@ -3442,7 +4604,7 @@ export interface AgentGetCurrentResult { agent?: AgentInfo | null; } /** - * Agent metadata, including identifiers, display details, source, tools, model, MCP servers, skills, and file path. + * Agent metadata, including identifiers, display details, source, tools, model, models, MCP servers, skills, and file path. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "AgentInfo". @@ -3482,13 +4644,18 @@ export interface AgentInfo { * Authored preferred model id for this agent. Runtime model selection may choose a different model; omitted means no authored preference. */ model?: string; + /** + * Authored preferred model ids for this agent, in priority order. Runtime model selection chooses the first available model; omitted means no authored preference. + */ + models?: string[]; + modelPolicy?: AgentModelPolicy; /** * MCP server configurations attached to this agent, keyed by server name. Server config shape mirrors the MCP `mcpServers` schema. * * @experimental */ mcpServers?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; /** * Skill names preloaded into this agent's context. Omitted means none. @@ -3537,12 +4704,6 @@ export interface AgentRegistryLiveTargetEntry { * TCP port the entry's JSON-RPC server is listening on */ port: number; - /** - * Connection token (null when the target is unauthenticated) - * - * @internal - */ - token?: string | null; /** * Session ID of the foreground session for this entry */ @@ -3582,6 +4743,12 @@ export interface AgentRegistryLiveTargetEntry { * Wall-clock milliseconds since the watcher last observed this entry (heartbeat freshness) */ lastSeenMs: number; + /** + * Connection token (null when the target is unauthenticated) + * + * @internal + */ + token?: string | null; } /** * Per-spawn log-capture outcome; populated from spawnLiveTarget. @@ -3804,36 +4971,48 @@ export interface AgentsGetDiscoveryPathsRequest { excludeHostAgents?: boolean; } /** - * Indicates whether the operation succeeded and reports the post-mutation state. + * Credential-free authentication identity safe to expose to hosts and user interfaces. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "AllowAllPermissionSetResult". + * via the `definition` "AuthIdentity". */ /** @experimental */ -export interface AllowAllPermissionSetResult { +export interface AuthIdentity { + type: AuthInfoType; /** - * Whether the operation succeeded + * Authentication host */ - success: boolean; + host: string; /** - * Authoritative full allow-all state after the mutation + * Authenticated login, when available */ - enabled: boolean; - mode?: PermissionsAllowAllMode; + login?: string; + /** + * Name of the environment variable that supplied the credential, when applicable + */ + envVar?: string; + /** + * Opaque SDK GitHub credential registration backing this identity. Routing metadata only; never a credential. + */ + registrationId?: string; + copilotUser?: CopilotUserResponse; } /** - * Current allow-all permission mode. + * Validation error from an authentication attempt. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "AllowAllPermissionState". + * via the `definition` "AuthValidationError". */ /** @experimental */ -export interface AllowAllPermissionState { +export interface AuthValidationError { /** - * Whether full allow-all permissions are currently active + * Authentication validation error message */ - enabled: boolean; - mode?: PermissionsAllowAllMode; + message: string; + /** + * Optional message returned by GitHub + */ + githubMessage?: string; } /** * The running runtime's complete catalog of well-known built-in model IDs, including supported models and additional IDs with built-in metadata. @@ -3857,10 +5036,102 @@ export interface BuiltInModelCatalog { /** @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. + * Well-known runtime model ID suitable for provider or provider-model metadata. This is not necessarily the provider-facing deployment or model name and does not indicate CAPI entitlement or provider availability. */ id: string; } +/** + * Rust-owned metadata and input schema for a built-in tool. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "BuiltinToolDescriptor". + */ +/** @experimental */ +export interface BuiltinToolDescriptor { + /** + * Stable name used to invoke the built-in tool. + */ + name: string; + /** + * Optional human-readable title for the tool. + */ + title: string | null; + /** + * Model-facing description of the tool's behavior. + */ + description: string; + /** + * JSON Schema for the tool input, or null when the tool uses a custom format. + */ + inputSchema: BuiltinToolInputSchema | null; + /** + * Optional supplemental usage instructions for the tool. + */ + instructions: string | null; + /** + * Optional tool category discriminator. + */ + type: string | null; + /** + * Optional custom input format used instead of a JSON Schema. + */ + format: BuiltinToolFormat | null; + safeForTelemetry: BuiltinToolSafeForTelemetry; + /** + * Whether the tool executes commands in a terminal. + */ + isTerminal: boolean; + /** + * Whether the tool provides a specialized intention summary. + */ + hasSummariseIntention: boolean; +} +/** + * JSON Schema object accepted by a built-in tool. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "BuiltinToolInputSchema". + */ +/** @experimental */ +export interface BuiltinToolInputSchema { + type: BuiltinToolInputSchemaType; + [k: string]: JsonValue | undefined; +} +/** + * Custom grammar input format accepted by a built-in tool. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "BuiltinToolFormat". + */ +/** @experimental */ +export interface BuiltinToolFormat { + type: BuiltinToolFormatType; + /** + * Grammar syntax used by the format definition. + */ + syntax: string; + /** + * Grammar definition accepted by the tool. + */ + definition: string; +} +/** + * Per-field telemetry-safety policy for a built-in tool. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "BuiltinToolSafeTelemetryFields". + */ +/** @experimental */ +export interface BuiltinToolSafeTelemetryFields { + /** + * Whether the tool name may be included in telemetry without obfuscation. + */ + name?: boolean; + /** + * Whether tool input names may be included in telemetry without obfuscation. + */ + inputsNames?: boolean; +} /** * Cancellation result for a user-requested shell command. * @@ -3892,16 +5163,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. * @@ -3921,19 +5182,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. @@ -4078,9 +5327,7 @@ export interface OpenCanvasInstance { /** * Input supplied when the instance was opened */ - input?: { - [k: string]: unknown | undefined; - }; + input?: JsonValue; } /** * Canvas open parameters. @@ -4105,9 +5352,7 @@ export interface CanvasOpenRequest { /** * Canvas open input */ - input?: { - [k: string]: unknown | undefined; - }; + input?: JsonValue; } /** * Canvas close parameters sent to the provider. @@ -4180,9 +5425,7 @@ export interface CanvasProviderInvokeActionRequest { /** * Action input */ - input?: { - [k: string]: unknown | undefined; - }; + input?: JsonValue; host?: CanvasHostContext; session?: CanvasSessionContext; } @@ -4213,9 +5456,7 @@ export interface CanvasProviderOpenRequest { /** * Canvas open input */ - input?: { - [k: string]: unknown | undefined; - }; + input?: JsonValue; host?: CanvasHostContext; session?: CanvasSessionContext; } @@ -4240,6 +5481,40 @@ export interface CanvasProviderOpenResult { */ status?: string; } +/** + * Internal canvas provider registration parameters. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CanvasProviderRegisterRequest". + */ +/** @experimental */ +export interface CanvasProviderRegisterRequest { + /** + * Connection identifier for callback routing + */ + connectionId: string; + /** + * Provider metadata supplied by the host + */ + info: JsonValue; + /** + * Canvas contributions supplied by the provider + */ + canvases: JsonValue[]; +} +/** + * Internal canvas provider unregistration parameters. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CanvasProviderUnregisterRequest". + */ +/** @experimental */ +export interface CanvasProviderUnregisterRequest { + /** + * Connection identifier to unregister + */ + connectionId: string; +} /** * Options scoped to the built-in CAPI (Copilot API) provider. * @@ -4248,151 +5523,702 @@ export interface CanvasProviderOpenResult { */ /** @experimental */ export interface CapiSessionOptions { + autoTier?: AutoTier; /** * 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. */ enableWebSocketResponses?: boolean; } /** - * Slash commands available in the session, after applying any include/exclude filters. + * Semantic digest of a strictly parsed and schema-validated JSON MCP card. Both URL-backed and embedded cards are canonicalised with RFC 8785 JSON Canonicalization Scheme, encoded as UTF-8, and hashed with SHA-256. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "CommandList". + * via the `definition` "CardDigest". */ /** @experimental */ -export interface CommandList { - /** - * Commands available in this session - */ - commands: SlashCommandInfo[]; +export interface CardDigest { + algorithm: CardDigestAlgorithm; + value: CardDigestValue; } /** - * Slash-command metadata with name, aliases, description, kind, input hint, execution allowance, and schedulability. + * An inert AI skill catalog result. AI skills are discovery-only and cannot be represented as installable through this surface. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "SlashCommandInfo". + * via the `definition` "CatalogAiSkillCandidate". */ /** @experimental */ -export interface SlashCommandInfo { +export interface CatalogAiSkillCandidate { /** - * Canonical command name without a leading slash + * Opaque, runtime-instance scoped, TTL-bound, single-use handle for this candidate. Carries no readable information and is rejected when stale, replayed, or presented to a different runtime instance. Never logged. */ - name: string; + handle: string; /** - * Canonical aliases without leading slashes + * ISO 8601 timestamp after which the handle is stale and will be rejected. */ - aliases?: string[]; + handleExpiresAt: string; /** - * Human-readable command description + * Discriminator: this candidate describes an AI skill */ - description: string; - kind: SlashCommandKind; - input?: SlashCommandInput; + kind: "ai-skill"; /** - * Whether the command may run while an agent turn is active + * Media type of the underlying AI skill card */ - allowDuringAgentExecution: boolean; + mediaType: "application/ai-skill"; /** - * Whether the command is experimental + * AI skills are discovery-only and cannot be installed through this surface */ - experimental?: boolean; + installability: "not-installable-kind"; /** - * Whether the command may be the target of `/every` / `/after` schedules. Resolution happens at every tick, so only set this when the command is safe to re-invoke and produces an agent prompt. + * Display name taken verbatim from the card. Inert untrusted text. */ - schedulable?: boolean; + displayName: string; + /** + * Description taken verbatim from the card. Inert untrusted text. + */ + description?: string; + /** + * Publisher taken verbatim from the card. Inert untrusted text. + */ + publisher?: string; + source: CatalogCandidateSource; + provenance: CatalogAiSkillCandidateProvenance; } /** - * Optional unstructured input hint + * Candidate whose card is retrieved from a URL through the runtime's hardened fetch boundary. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "SlashCommandInput". + * via the `definition` "CatalogCandidateSourceUrl". */ /** @experimental */ -export interface SlashCommandInput { - /** - * Hint to display when command input has not been provided - */ - hint: string; +export interface CatalogCandidateSourceUrl { /** - * Optional literal choices the input accepts, each with a human-facing description; clients may render these as selectable options + * Discriminator: the card is URL-backed, and carries no embedded data */ - choices?: SlashCommandInputChoice[]; + kind: "url"; /** - * When true, the command requires non-empty input; clients should render the input hint as required + * Card URL as advertised. Inert untrusted data: the runtime retrieves it only through its own hardened boundary, and it is never logged. */ - required?: boolean; - completion?: SlashCommandInputCompletion; + url: string; +} +/** + * Candidate whose card reference arrived inline. The document and its content-derived properties stay behind the runtime boundary. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogCandidateSourceEmbedded". + */ +/** @experimental */ +export interface CatalogCandidateSourceEmbedded { /** - * When true, clients should pass the full text after the command name as a single argument rather than splitting on whitespace + * Discriminator: the card is embedded, and carries no URL */ - preserveMultilineInput?: boolean; + kind: "embedded"; } /** - * A literal choice the command input accepts, with a human-facing description + * Where and when an AI skill catalog reference was observed. Discovery provenance deliberately carries no content digest because search does not establish the exact validated content a later plan will bind. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "SlashCommandInputChoice". + * via the `definition` "CatalogAiSkillCandidateProvenance". */ /** @experimental */ -export interface SlashCommandInputChoice { +export interface CatalogAiSkillCandidateProvenance { /** - * The literal choice value (e.g. 'on', 'off', 'show') + * Host of the catalog authority that advertised the reference, without path, query, or credentials. Inert untrusted data. */ - name: string; + authority: string; /** - * Human-readable description shown alongside the choice + * ISO 8601 timestamp at which the runtime observed the catalog reference. This is not a retrieval or validation timestamp. */ - description: string; + observedAt: string; + /** + * Media type advertised for the referenced AI skill card + */ + mediaType: "application/ai-skill"; } /** - * Pending command request ID and an optional error if the client handler failed. + * An optional catalog authentication exchange did not establish the caller's identity. Anonymous search remains supported; this refusal is reserved for an operation that cannot continue after the attempted exchange. It is distinct from `policy-rejected` and from a network failure, and the reason identifies the recovery action. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "CommandsHandlePendingCommandRequest". + * via the `definition` "CatalogAuthenticationRequiredError". */ /** @experimental */ -export interface CommandsHandlePendingCommandRequest { +export interface CatalogAuthenticationRequiredError { /** - * Request ID from the command invocation event + * Discriminator: the caller is not authenticated */ - requestId: string; + kind: "authentication-required"; + reason: CatalogAuthenticationRequiredReason; /** - * Error message if the command handler failed + * Human-readable explanation, safe to surface. Never contains a credential or token, nor a query, URL, handle, or secret. */ - error?: string; + message: string; } /** - * Indicates whether the pending client-handled command was completed successfully. + * An inert MCP server catalog result. Every free-text field is untrusted external data and must never be treated as an instruction, and the handle is the only way to refer to the candidate in a later operation. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "CommandsHandlePendingCommandResult". + * via the `definition` "CatalogMcpServerCandidate". */ /** @experimental */ -export interface CommandsHandlePendingCommandResult { +export interface CatalogMcpServerCandidate { /** - * Whether the command was handled successfully + * Opaque, runtime-instance scoped, TTL-bound, single-use handle for this candidate. Carries no readable information and is rejected when stale, replayed, or presented to a different runtime instance. Never logged. */ - success: boolean; + handle: string; + /** + * ISO 8601 timestamp after which the handle is stale and will be rejected. + */ + handleExpiresAt: string; + /** + * Discriminator: this candidate describes an MCP server + */ + kind: "mcp-server"; + mediaType: McpServerCardMediaType; + installability: CatalogMcpServerInstallability; + /** + * Display name taken verbatim from the card. Inert untrusted text. + */ + displayName: string; + /** + * Description taken verbatim from the card. Inert untrusted text. + */ + description?: string; + /** + * Publisher taken verbatim from the card. Inert untrusted text. + */ + publisher?: string; + source: CatalogCandidateSource; + provenance: CatalogMcpServerCandidateProvenance; } /** - * Slash command name and optional raw input string to invoke. + * Where and when an MCP server catalog reference was observed. Discovery provenance deliberately carries no content digest because search does not establish the exact validated content a later plan will bind. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "CommandsInvokeRequest". + * via the `definition` "CatalogMcpServerCandidateProvenance". */ /** @experimental */ -export interface CommandsInvokeRequest { +export interface CatalogMcpServerCandidateProvenance { /** - * Command name. Leading slashes are stripped and the name is matched case-insensitively. + * Host of the catalog authority that advertised the reference, without path, query, or credentials. Inert untrusted data. */ - name: string; + authority: string; /** - * Raw input after the command name + * ISO 8601 timestamp at which the runtime observed the catalog reference. This is not a retrieval or validation timestamp. */ - input?: string; + observedAt: string; + mediaType: McpServerCardMediaType; } /** - * Queued-command request ID and the result indicating whether the host executed it (and whether to stop processing further queued commands). + * The protocol version and capability set a caller requires, supplied on every catalog request so negotiation cannot be skipped by omission. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogClientContract". + */ +/** @experimental */ +export interface CatalogClientContract { + /** + * SDK protocol version the caller was generated against. A caller below the runtime's minimum supported version is refused rather than served a partial result. + */ + protocolVersion: number; + /** + * Wire features the caller requires the runtime to understand. Identifiers are bounded but extensible so a newer caller can negotiate with an older runtime. Requiring an unknown feature yields a typed refusal listing what is understood, never a partial grant. A grant does not promise that a deployment has enabled the operation; typed unavailable results report that separately. + * + * @maxItems 32 + */ + requiredCapabilities: CatalogCapabilityId[]; +} +/** + * An upstream catalog response broke the wire contract. Most importantly, every result must carry exactly one of a URL or embedded data: a result carrying both, or neither, is refused here rather than being guessed at. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogContractViolationError". + */ +/** @experimental */ +export interface CatalogContractViolationError { + /** + * Discriminator: the upstream response broke the contract + */ + kind: "contract-violation"; + reason: CatalogContractViolationReason; + /** + * Human-readable explanation, safe to surface. Never echoes response content, nor a query, URL, handle, or secret. + */ + message: string; +} +/** + * A presented handle was not accepted. Handles are runtime-instance scoped, TTL-bound, and single-use, so each way of failing is reported distinctly. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogHandleRejectedError". + */ +/** @experimental */ +export interface CatalogHandleRejectedError { + /** + * Discriminator: a handle was rejected + */ + kind: "handle-rejected"; + handleType: CatalogHandleType; + reason: CatalogHandleRejectionReason; + /** + * Human-readable explanation, safe to surface. Never contains the handle itself, nor a query, URL, or secret. + */ + message: string; +} +/** + * The request was rejected before any work was done, because a bounded field fell outside its permitted range or a required field was unusable. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogInvalidRequestError". + */ +/** @experimental */ +export interface CatalogInvalidRequestError { + /** + * Discriminator: the request itself was invalid + */ + kind: "invalid-request"; + field: CatalogInvalidRequestField; + /** + * Human-readable explanation, safe to surface. Never echoes the offending value, nor a query, URL, handle, or secret. + */ + message: string; +} +/** + * A card could not be parsed or did not satisfy its declared media type's schema. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogMalformedCardError". + */ +/** @experimental */ +export interface CatalogMalformedCardError { + /** + * Discriminator: the card was malformed + */ + kind: "malformed-card"; + reason: CatalogMalformedCardReason; + mediaType?: CatalogMediaType; + /** + * Human-readable explanation, safe to surface. Never echoes card content, nor a query, URL, handle, or secret. + */ + message: string; +} +/** + * The protocol version and capability set the runtime actually honoured for a successful catalog operation. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogNegotiatedContract". + */ +/** @experimental */ +export interface CatalogNegotiatedContract { + /** + * Protocol version of the runtime that served the request. + */ + runtimeProtocolVersion: number; + /** + * Wire features the runtime understood for this operation. Always a superset of the caller's required features, because any shortfall is a refusal instead. Operation availability remains a separate typed result. + */ + grantedCapabilities: CatalogCapability[]; +} +/** + * The caller's protocol version or required capabilities cannot be honoured. Returned instead of a partial or ambiguous success. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogNegotiationRefusedError". + */ +/** @experimental */ +export interface CatalogNegotiationRefusedError { + /** + * Discriminator: capability or protocol-version negotiation failed + */ + kind: "negotiation-refused"; + reason: CatalogNegotiationRefusedReason; + /** + * Protocol version of the runtime that refused the request. + */ + runtimeProtocolVersion: number; + /** + * Lowest caller protocol version this runtime will serve. + */ + minimumSupportedProtocolVersion: number; + /** + * Every wire feature this runtime understands, so the caller can retry within that contract. This list does not imply that every deployment has enabled every operation. + */ + supportedCapabilities: CatalogCapability[]; + /** + * The subset of the caller's bounded extensible capability identifiers this runtime cannot honour. + * + * @maxItems 32 + */ + unsupportedCapabilities: CatalogCapabilityId[]; + /** + * Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. + */ + message: string; +} +/** + * The runtime could not reach the catalog authority or retrieve a card. Covers being offline as well as transport-level failure. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogNetworkFailureError". + */ +/** @experimental */ +export interface CatalogNetworkFailureError { + /** + * Discriminator: the network operation failed + */ + kind: "network-failure"; + reason: CatalogNetworkFailureReason; + /** + * HTTP status code, when the failure was a rejected response. + */ + statusCode?: number; + /** + * Bounded cooldown in seconds before another catalog request should be attempted, when the authority supplied a numeric Retry-After value or the runtime applied its documented fallback. + */ + retryAfterSeconds?: number; + /** + * Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. + */ + message: string; +} +/** + * The candidate is discoverable but cannot be installed. `application/ai-skill` resolves here, because it stays searchable while remaining typed non-installable. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogNotInstallableError". + */ +/** @experimental */ +export interface CatalogNotInstallableError { + /** + * Discriminator: the candidate cannot be installed + */ + kind: "not-installable"; + reason: CatalogNotInstallableReason; + /** + * Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. + */ + message: string; +} +/** + * Registry or enterprise policy refused the operation. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogPolicyRejectedError". + */ +/** @experimental */ +export interface CatalogPolicyRejectedError { + /** + * Discriminator: policy refused the operation + */ + kind: "policy-rejected"; + source: McpPlanPolicySource; + /** + * Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. + */ + message: string; +} +/** + * A bounded catalog search. Both the query length and the result count are capped by the schema so a caller cannot request an unbounded scan. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogSearchRequest". + */ +/** @experimental */ +export interface CatalogSearchRequest { + contract: CatalogClientContract; + /** + * Free-text search query. Persisted as tool input for session continuity, but omitted from telemetry. + */ + query: string; + /** + * Maximum number of candidates to return. Defaults to 10 when omitted. + */ + limit?: number; + /** + * Restrict results to these candidate kinds. When omitted, every kind the runtime supports is searched. + * + * @minItems 1 + * @maxItems 2 + */ + kinds?: [CatalogCandidateKind] | [CatalogCandidateKind, CatalogCandidateKind]; +} +/** + * A completed catalog search: inert candidate summaries, each carrying a single-use handle. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogSearchSucceeded". + */ +/** @experimental */ +export interface CatalogSearchSucceeded { + /** + * Discriminator: the search completed + */ + kind: "succeeded"; + /** + * Pseudonymous identifier for this search, issued by the runtime or by the catalog authority it queried and never by the caller, so it cannot be forged or replayed to attribute an install to a search that never happened. Always present on a success, so a result set can be tied to the installs it leads to. It identifies a search rather than a person: it is derived from no user, account, device, or query data, and must never be joined with user identity to re-identify anyone. + */ + searchId: string; + /** + * Matching candidates, never more than the requested limit. All text is inert untrusted data. + * + * @maxItems 50 + */ + candidates: CatalogCandidate[]; + /** + * Whether further matches existed beyond the requested limit. + */ + truncated: boolean; + negotiated: CatalogNegotiatedContract; +} +/** + * The request asked for a candidate kind this runtime does not serve. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogUnsupportedKindError". + */ +/** @experimental */ +export interface CatalogUnsupportedKindError { + /** + * Discriminator: an unsupported candidate kind was requested + */ + kind: "unsupported-kind"; + /** + * The kinds from the request that are not supported. + */ + requestedKinds: CatalogCandidateKind[]; + /** + * Every candidate kind this runtime can serve. + */ + supportedKinds: CatalogCandidateKind[]; + /** + * Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. + */ + message: string; +} +/** + * Retrieval was refused by the runtime's hardened fetch boundary before any request left the process, or before a redirect was followed. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogUnsafeRetrievalError". + */ +/** @experimental */ +export interface CatalogUnsafeRetrievalError { + /** + * Discriminator: retrieval was refused as unsafe + */ + kind: "unsafe-retrieval"; + reason: CatalogUnsafeRetrievalReason; + /** + * Human-readable explanation, safe to surface. Never contains the refused URL, nor a query, handle, or secret. + */ + message: string; +} +/** + * The operation is not available on this runtime. Distinct from a network failure: nothing was attempted. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogUnavailableError". + */ +/** @experimental */ +export interface CatalogUnavailableError { + /** + * Discriminator: the operation is not available + */ + kind: "unavailable"; + reason: CatalogUnavailableReason; + /** + * Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. + */ + message: string; +} +/** + * No transport this runtime can use is available for the requested server. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CatalogUnavailableTransportError". + */ +/** @experimental */ +export interface CatalogUnavailableTransportError { + /** + * Discriminator: no usable transport is available + */ + kind: "unavailable-transport"; + reason: CatalogUnavailableTransportReason; + /** + * Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. + */ + message: string; +} +/** + * Slash commands available in the session, after applying any include/exclude filters. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CommandList". + */ +/** @experimental */ +export interface CommandList { + /** + * Commands available in this session + */ + commands: SlashCommandInfo[]; +} +/** + * Slash-command metadata with name, aliases, description, kind, input hint, execution allowance, and schedulability. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SlashCommandInfo". + */ +/** @experimental */ +export interface SlashCommandInfo { + /** + * Canonical command name without a leading slash + */ + name: string; + /** + * Canonical aliases without leading slashes + */ + aliases?: string[]; + /** + * Human-readable command description + */ + description: string; + kind: SlashCommandKind; + input?: SlashCommandInput; + /** + * Whether the command may run while an agent turn is active + */ + allowDuringAgentExecution: boolean; + /** + * Whether the command is experimental + */ + experimental?: boolean; + /** + * Whether the command may be the target of `/every` / `/after` schedules. Resolution happens at every tick, so only set this when the command is safe to re-invoke and produces an agent prompt. + */ + schedulable?: boolean; +} +/** + * Optional unstructured input hint + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SlashCommandInput". + */ +/** @experimental */ +export interface SlashCommandInput { + /** + * Hint to display when command input has not been provided + */ + hint: string; + /** + * Optional literal choices the input accepts, each with a human-facing description; clients may render these as selectable options + */ + choices?: SlashCommandInputChoice[]; + /** + * When true, the command requires non-empty input; clients should render the input hint as required + */ + required?: boolean; + completion?: SlashCommandInputCompletion; + /** + * When true, clients should pass the full text after the command name as a single argument rather than splitting on whitespace + */ + preserveMultilineInput?: boolean; +} +/** + * A literal choice the command input accepts, with a human-facing description + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SlashCommandInputChoice". + */ +/** @experimental */ +export interface SlashCommandInputChoice { + /** + * The literal choice value (e.g. 'on', 'off', 'show') + */ + name: string; + /** + * Human-readable description shown alongside the choice + */ + description: string; +} +/** + * The pending slash-command invocation effect to finalize, plus whether the host applied or cancelled it. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CommandsFinalizeInvocationEffectRequest". + */ +/** @experimental */ +export interface CommandsFinalizeInvocationEffectRequest { + /** + * The slash-command result object that produced the pending effect, echoed back unchanged. + */ + effect: {}; + outcome: CommandsInvocationEffectOutcome; +} +/** + * Whether finalizing the invocation effect succeeded, and the failure reason when it did not. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CommandsFinalizeInvocationEffectResult". + */ +/** @experimental */ +export interface CommandsFinalizeInvocationEffectResult { + /** + * Whether the pending invocation effect was finalized successfully. + */ + success: boolean; + /** + * Failure reason when the invocation effect could not be finalized. + */ + error?: string; +} +/** + * Pending command request ID and an optional error if the client handler failed. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CommandsHandlePendingCommandRequest". + */ +/** @experimental */ +export interface CommandsHandlePendingCommandRequest { + /** + * Request ID from the command invocation event + */ + requestId: string; + /** + * Error message if the command handler failed + */ + error?: string; +} +/** + * Indicates whether the pending client-handled command was completed successfully. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CommandsHandlePendingCommandResult". + */ +/** @experimental */ +export interface CommandsHandlePendingCommandResult { + /** + * Whether the command was handled successfully + */ + success: boolean; +} +/** + * Slash command name and optional raw input string to invoke. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CommandsInvokeRequest". + */ +/** @experimental */ +export interface CommandsInvokeRequest { + /** + * Command name. Leading slashes are stripped and the name is matched case-insensitively. + */ + name: string; + /** + * Raw input after the command name + */ + input?: string; + origin?: CommandsInvocationOrigin; +} +/** + * Queued-command request ID and the result indicating whether the host executed it (and whether to stop processing further queued commands). * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "CommandsRespondToQueuedCommandRequest". @@ -4540,9 +6366,33 @@ export interface ConfigureSessionExtensionsParams { * * @internal */ - controller?: { - [k: string]: unknown | undefined; - }; + controller?: OpaqueInProcessValue; +} +/** + * Identity of the integrating host, declared once on the `server.connect` handshake so telemetry from this connection is attributed to a single, consistent surface. All fields are optional; omit them to keep the default attribution. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ConnectClientInfo". + */ +/** @experimental */ +/** @internal */ +export interface ConnectClientInfo { + /** + * Name of the host editor, e.g. `"vscode"`. + */ + editorName?: string; + /** + * Version of the host editor, e.g. `"1.124.2"`. Ignored unless it looks like a version string. + */ + editorVersion?: string; + /** + * Name of the Copilot extension within the host, e.g. `"copilot-chat"`. + */ + extensionName?: string; + /** + * Version of the Copilot extension within the host, e.g. `"0.54.0"`. Ignored unless it looks like a version string. + */ + extensionVersion?: string; } /** * Metadata for a connected remote session. @@ -4626,7 +6476,7 @@ export interface ConnectRemoteSessionParams { sessionId: string; } /** - * Parameters for the `server.connect` handshake: an optional connection token and optional connection-level opt-ins (e.g. GitHub telemetry forwarding). + * Connection-level opt-ins for the `server.connect` handshake. Transport authentication is consumed by the native protocol boundary before dispatch. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "ConnectRequest". @@ -4634,14 +6484,15 @@ export interface ConnectRemoteSessionParams { /** @experimental */ /** @internal */ export interface ConnectRequest { - /** - * Connection token; required when the server was started with COPILOT_CONNECTION_TOKEN - */ - 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. 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; + clientInfo?: ConnectClientInfo; + /** + * Connection token; required when the server was started with COPILOT_CONNECTION_TOKEN + */ + token?: string; } /** * Handshake result reporting the server's protocol version and package version on success. @@ -4787,7 +6638,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 @@ -5010,6 +6861,37 @@ export interface DiscoveredExtensionsEnableRequest { */ ids: string[]; } +/** + * One server-discovered hook action from user, repository, plugin, or managed-policy configuration. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "DiscoveredHook". + */ +/** @experimental */ +export interface DiscoveredHook { + /** + * Deterministic identifier for this server-discovered action row. It remains stable while the project, origin, source, event, action content, and duplicate ordinal are unchanged. This is row identity, not the key persisted in disabledHooks. + */ + id: string; + hookType: HookType; + origin: HookOrigin; + /** + * Human-readable source label, such as a hook file path, settings source, or plugin name. + */ + source?: string; + /** + * Input project path for which this server-side action was resolved. Set on every row returned for project-scoped discovery, including repeated user and policy actions. + */ + projectPath?: string; + /** + * Whether this action is enabled under the server-side discovery settings. Concrete sessions may differ because they can add session-specific directories, plugins, or trust. False when its disable key is present in the user's disabled-hooks setting or disable-all settings suppress the action. + */ + enabled: boolean; + /** + * Durable content hash used by hook enablement. Identical actions may intentionally share this key. Omitted when changing the user's disabled-hooks setting cannot change the action's current server-discovered state, including managed-policy hooks, session-start prompt actions, actions suppressed by disable-all settings, and projectless plugin actions that require project-directory expansion. + */ + disableKey?: string; +} /** * MCP server discovered by `mcp.discover`, with config source, optional plugin source, transport type, and enabled state. * @@ -5049,6 +6931,10 @@ export interface 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. */ command: string; + /** + * Optional user-facing text for the queue row. The command string is shown when omitted. + */ + displayText?: string | null; } /** * Indicates whether the command was accepted into the local execution queue. @@ -5217,10 +7103,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. * @@ -5288,7 +7227,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 @@ -5328,7 +7267,7 @@ export interface ExternalToolTextResultForLlmBinaryResultsForLlm { * Optional metadata from the producing tool. */ metadata?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; } /** @@ -5405,6 +7344,10 @@ export interface ExternalToolTextResultForLlmContentShellExit { * Whether outputPreview is known to be incomplete or truncated */ outputTruncated?: boolean; + /** + * Path reported in the shell session's filesystem namespace when shell output exceeded the configured large-output threshold. + */ + outputFilePath?: string; } /** * Image content block with base64-encoded data @@ -5565,13 +7508,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. @@ -5606,9 +7556,7 @@ 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. @@ -5618,18 +7566,61 @@ export interface FactoryAgentResult { */ /** @experimental */ export interface FactoryAgentSummary { + /** + * Stable direct-agent identifier. + */ agentId: string; + /** + * Tool-call identifier that launched the agent. + */ toolCallId: string; + /** + * Owning factory run identifier. + */ runId: string; + /** + * Phase identifier active when the agent was launched, or null. + */ phaseId: string | null; + /** + * Friendly, non-unique name intended for display + */ label: string; + /** + * Friendly, non-unique name intended for display + */ + displayName?: string; + /** + * Registered agent type. + */ agentType: string; + /** + * Current durable or live agent status. + */ status: string; + /** + * Model requested when the agent was launched. + */ requestedModel?: string; + /** + * Concrete model resolved for the agent. + */ resolvedModel?: string; + /** + * Epoch milliseconds when the agent started. + */ startedAt?: number; + /** + * Epoch milliseconds when the agent completed. + */ completedAt?: number; + /** + * Accumulated active agent time in milliseconds. + */ activeMs: number; + /** + * Prompt-safe live activity text. + */ activity?: string; } /** @@ -5653,7 +7644,13 @@ export interface FactoryCancelRequest { */ /** @experimental */ export interface FactoryCurrentPhase { + /** + * Current phase identifier. + */ id: string; + /** + * Zero-based declared phase ordinal, or null for an undeclared phase. + */ ordinal: number | null; } /** @@ -5664,9 +7661,21 @@ export interface FactoryCurrentPhase { */ /** @experimental */ export interface FactoryDeclaredLimits { + /** + * Maximum concurrently active subagents. + */ maxConcurrentSubagents?: number; + /** + * Maximum total subagents spawned by the run. + */ maxTotalSubagents?: number; + /** + * Maximum accumulated active execution time in seconds. + */ timeoutSeconds?: number; + /** + * Maximum AI credits consumed by subagents and descendants. + */ maxAiCredits?: number; } /** @@ -5696,9 +7705,7 @@ export interface FactoryExecuteRequest { /** * Factory input value. */ - args: { - [k: string]: unknown | undefined; - }; + args: JsonValue; } /** * Result returned by an extension factory closure. @@ -5711,9 +7718,7 @@ export interface FactoryExecuteResult { /** * Factory result value. */ - result?: { - [k: string]: unknown | undefined; - }; + result?: JsonValue; } /** * Parameters for paging factory progress. @@ -5793,9 +7798,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. @@ -5820,27 +7823,57 @@ export interface FactoryJournalPutRequest { /** * JSON result to memoize. */ - resultJson: { - [k: string]: unknown | undefined; - }; + resultJson: JsonValue; } /** - * Empty parameters for listing factory runs. + * Parameters for paging factory runs. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "FactoryListRunsRequest". */ /** @experimental */ -export interface FactoryListRunsRequest {} +export interface FactoryListRunsRequest { + /** + * Exclusive forward cursor. + */ + afterSeq?: number; + /** + * Exclusive backward cursor. + */ + beforeSeq?: number; + /** + * Maximum terminal runs to return. Defaults to 200 and is capped at 500. + */ + limit?: number; +} /** - * Factory runs in durable creation order. + * A page of factory runs in durable creation order. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "FactoryListRunsResult". */ /** @experimental */ export interface FactoryListRunsResult { + /** + * Factory run summaries in durable creation order. + */ runs: FactoryRunSummary[]; + /** + * Oldest terminal-run cursor in this page, or null when the terminal window is empty. + */ + oldestSeq?: number | null; + /** + * Newest terminal-run cursor in this page, or null when the terminal window is empty. + */ + newestSeq?: number | null; + /** + * Whether terminal runs newer than this page exist. + */ + hasMoreNewer?: boolean; + /** + * Number of terminal runs older than this page. + */ + omittedOlder?: number; } /** * Durable factory run summary with read-time live overlays. @@ -5850,24 +7883,72 @@ export interface FactoryListRunsResult { */ /** @experimental */ export interface FactoryRunSummary { + /** + * Factory run identifier. + */ runId: string; + /** + * Registered factory name. + */ factoryName: string; + /** + * Human-readable factory description. + */ description: string; status: FactoryRunStatus; + /** + * Monotonic durable run revision. + */ revision: number; + /** + * Epoch milliseconds when the run was created. + */ createdAt: number; + /** + * Epoch milliseconds when execution first started, or null before start. + */ startedAt: number | null; + /** + * Epoch milliseconds when the durable run was last updated. + */ updatedAt: number; + /** + * Epoch milliseconds when the run completed, or null while nonterminal. + */ completedAt: number | null; + /** + * Current phase identity, or null before any phase is entered. + */ currentPhase: FactoryCurrentPhase | null; + /** + * Number of phases declared by the factory. + */ declaredPhaseCount: number; + /** + * Number of direct factory agents currently live. + */ liveAgentCount: number; + /** + * Total direct factory agents spawned across all attempts. + */ totalSpawnedAgentCount: number; consumed: FactoryRunConsumed; declaredLimits: FactoryDeclaredLimits; + /** + * Approved effective resource ceilings, or null until approved. + */ approved: FactoryDeclaredLimits | null; + /** + * Epoch milliseconds when this live-overlay snapshot was observed. + */ observedAt: number; + /** + * Epoch milliseconds when the current active segment started, or null while inactive. + */ activeSegmentStartedAt: number | null; + /** + * Terminal run outcome, or null while nonterminal. + */ terminal: FactoryRunTerminal | null; } /** @@ -5878,8 +7959,17 @@ export interface FactoryRunSummary { */ /** @experimental */ export interface FactoryRunConsumed { + /** + * Accumulated active execution time in milliseconds. + */ activeMs: number; + /** + * Total subagents spawned by the run. + */ subagents: number; + /** + * AI usage consumed by the run in nano-AIU. + */ nanoAiu: number; } /** @@ -5890,9 +7980,18 @@ export interface FactoryRunConsumed { */ /** @experimental */ export interface FactoryRunTerminal { + /** + * Human-readable terminal reason. + */ reason?: string; failure?: FactoryRunFailure; + /** + * Human-readable terminal error. + */ error?: string; + /** + * Prompt-safe preview of the completed result. + */ resultPreview?: string; } /** @@ -5942,18 +8041,54 @@ export interface FactoryLogRequest { */ /** @experimental */ export interface FactoryPhaseObservation { + /** + * Phase identifier. + */ id: string; + /** + * Zero-based declared phase ordinal, or null for an undeclared phase. + */ ordinal: number | null; + /** + * Human-readable phase title. + */ title: string; + /** + * Optional human-readable phase detail. + */ detail?: string; status: FactoryPhaseStatus; + /** + * Most recent run attempt that entered this phase, or `0` if the phase has never been entered. + */ lastEnteredRunAttempt: number; + /** + * Number of times execution entered this phase. + */ entryCount: number; + /** + * Epoch milliseconds when this phase first started; for a skipped phase, the synthetic skip timestamp (equal to `completedAt`). + */ startedAt?: number; + /** + * Epoch milliseconds when this phase completed; for a skipped phase, the synthetic skip timestamp (equal to `startedAt`). + */ completedAt?: number; + /** + * Completed active time accumulated by this phase in milliseconds. + */ accumulatedActiveMs: number; + /** + * Current live active time for this phase in milliseconds. + */ currentActiveMs: number; + /** + * Total direct agents associated with this phase. + */ totalAgentCount: number; + /** + * Direct agents in this phase that are currently live. + */ liveAgentCount: number; } /** @@ -5994,10 +8129,25 @@ export interface FactoryProgressLine { */ /** @experimental */ export interface FactoryProgressPage { + /** + * Progress records in sequence order. + */ records: FactoryProgressLine[]; + /** + * Oldest sequence number in this page, or null when empty. + */ oldestSeq: number | null; + /** + * Newest sequence number in this page, or null when empty. + */ newestSeq: number | null; + /** + * Whether progress records older than this page exist. + */ hasMoreOlder: boolean; + /** + * Whether progress records newer than this page exist. + */ hasMoreNewer: boolean; /** * Run revision reflected by this page. @@ -6017,6 +8167,14 @@ export interface FactoryResumeRequest { */ runId: string; limits?: FactoryRunLimits; + /** + * Whether to notify the originating session when the factory completes. + */ + notifyOnComplete?: boolean; + /** + * Whether to emit factory phase names to the session transcript. + */ + logPhaseNames?: boolean; } /** * Wire-only per-invocation factory resource ceiling overrides. @@ -6069,13 +8227,15 @@ export interface FactoryRunResult { * Factory run identifier. */ runId: string; + /** + * One-based execution attempt represented by this envelope. Absent before the first attempt starts or when returned by an older runtime. + */ + attempt?: number; status: FactoryRunStatus; /** * Completed factory result. */ - result?: { - [k: string]: unknown | undefined; - }; + result?: JsonValue; /** * Error message for an errored run. */ @@ -6088,9 +8248,7 @@ 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. @@ -6100,26 +8258,80 @@ export interface FactoryRunResult { */ /** @experimental */ export interface FactoryRunDetail { + /** + * Factory run identifier. + */ runId: string; + /** + * Registered factory name. + */ factoryName: string; + /** + * Human-readable factory description. + */ description: string; status: FactoryRunStatus; + /** + * Monotonic durable run revision. + */ revision: number; + /** + * Epoch milliseconds when the run was created. + */ createdAt: number; + /** + * Epoch milliseconds when execution first started, or null before start. + */ startedAt: number | null; + /** + * Epoch milliseconds when the durable run was last updated. + */ updatedAt: number; + /** + * Epoch milliseconds when the run completed, or null while nonterminal. + */ completedAt: number | null; + /** + * Current phase identity, or null before any phase is entered. + */ currentPhase: FactoryCurrentPhase | null; + /** + * Number of phases declared by the factory. + */ declaredPhaseCount: number; + /** + * Number of direct factory agents currently live. + */ liveAgentCount: number; + /** + * Total direct factory agents spawned across all attempts. + */ totalSpawnedAgentCount: number; consumed: FactoryRunConsumed; declaredLimits: FactoryDeclaredLimits; + /** + * Approved effective resource ceilings, or null until approved. + */ approved: FactoryDeclaredLimits | null; + /** + * Epoch milliseconds when this live-overlay snapshot was observed. + */ observedAt: number; + /** + * Epoch milliseconds when the current active segment started, or null while inactive. + */ activeSegmentStartedAt: number | null; + /** + * Terminal run outcome, or null while nonterminal. + */ terminal: FactoryRunTerminal | null; + /** + * Lifecycle and timing observations for each factory phase. + */ phases: FactoryPhaseObservation[]; + /** + * Durable identities and live statuses for direct factory agents. + */ agents: FactoryAgentSummary[]; progress: FactoryProgressPage; } @@ -6138,9 +8350,7 @@ export interface FactoryRunRequest { /** * Factory input value. */ - args: { - [k: string]: unknown | undefined; - }; + args: JsonValue; options?: RunOptions; } /** @@ -6151,12 +8361,77 @@ export interface FactoryRunRequest { */ /** @experimental */ export interface RunOptions { + limits?: FactoryRunLimits; + /** + * Whether to notify the originating session when the factory completes. + */ + notifyOnComplete?: boolean; + /** + * Whether to emit factory phase names to the session transcript. + */ + logPhaseNames?: boolean; + /** + * Run identifier whose journal and progress should seed this resumed run. + */ + resumeFromRunId?: string; +} +/** + * Internal parameters for resuming a factory run from a tool. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "FactoryToolResumeRequest". + */ +/** @experimental */ +/** @internal */ +export interface FactoryToolResumeRequest { + /** + * Factory run identifier. + */ + runId: string; + limits?: FactoryRunLimits; + /** + * Opaque identifier of the originating tool call. + */ + toolCallId?: string; +} +/** + * Options for an internal tool-originated factory invocation. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "FactoryToolRunOptions". + */ +/** @experimental */ +/** @internal */ +export interface FactoryToolRunOptions { limits?: FactoryRunLimits; /** * Run identifier whose journal and progress should seed this resumed run. */ resumeFromRunId?: string; } +/** + * Internal parameters for invoking a registered factory from a tool. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "FactoryToolRunRequest". + */ +/** @experimental */ +/** @internal */ +export interface FactoryToolRunRequest { + /** + * Registered factory name. + */ + name: string; + /** + * Factory input value. + */ + args: JsonValue; + options?: FactoryToolRunOptions; + /** + * Opaque identifier of the originating tool call. + */ + toolCallId?: string; +} /** * Optional user prompt to combine with the fleet orchestration instructions. * @@ -6270,6 +8545,14 @@ export interface GitHubTelemetryClientInfo { * Stable machine identifier for the device. */ dev_device_id?: string; + /** + * Distinct CPU model names for the host, comma-separated. + */ + cpu_model?: string; + /** + * Number of logical CPU cores on the host. + */ + cpu_count?: number; } /** * 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. @@ -6341,6 +8624,28 @@ export interface GitHubTelemetryNotification { restricted: boolean; event: GitHubTelemetryEvent; } +/** + * Asks the SDK client to acquire a GitHub access token from an opaque callback registration. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "GitHubTokenAcquireRequest". + */ +/** @experimental */ +export interface GitHubTokenAcquireRequest { + /** + * Opaque identifier generated by the SDK for this callback registration. + */ + registrationId: string; + /** + * Effective GitHub host for which the callback must return a token. + */ + host: string; + /** + * Session receiving the token. Absent only before a cloud session has been assigned its id. + */ + sessionId?: string; + reason: GitHubTokenAcquireReason; +} /** * Pending external tool call request ID, with the tool result or an error describing why it failed. * @@ -6715,7 +9020,7 @@ export interface HistoryTruncateResult { export interface HookInvokeRequest { sessionId: string; hookType: HookType; - input: unknown; + input: JsonValue; } /** * Optional output returned by an SDK callback hook. @@ -6726,7 +9031,45 @@ export interface HookInvokeRequest { /** @experimental */ /** @internal */ export interface HookInvokeResponse { - output?: unknown; + output?: JsonValue; +} +/** + * Optional project paths and host-exclusion behavior for server-scoped hook discovery. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "HooksDiscoverRequest". + */ +/** @experimental */ +export interface HooksDiscoverRequest { + /** + * Optional project directory paths whose trusted repository and project-expanded plugin hooks should be discovered. When omitted or empty, user, managed-policy, and globally enabled installed or explicit plugin hooks are returned without project expansion. + */ + projectPaths?: string[]; + /** + * When true, omit host-owned user and plugin hook rows and their diagnostics. Managed-policy hooks and trusted repository hooks remain visible, and host disabledHooks still contribute to each remaining row's effective enabled state. This filters sources rather than simulating a host with no settings. + */ + excludeHostHooks?: boolean; +} +/** + * Server-discovered hook actions and partial-load diagnostics from user, repository, plugin, and managed-policy sources. Concrete sessions may include additional session-specific hook sources. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "HooksDiscoverResult". + */ +/** @experimental */ +export interface HooksDiscoverResult { + /** + * All discovered hook actions. Byte-identical actions remain separate rows even when they share a disable key. + */ + hooks: DiscoveredHook[]; + /** + * Non-fatal source-loading warnings. Discovery remains complete for the affected source, although the source had a recoverable issue. Repository-settings warnings are prefixed with their project path when attribution is available. + */ + warnings: string[]; + /** + * Errors for hook sources or actions that could not be loaded, making the result partially incomplete. Other valid actions are still returned. Project-resolution and repository-settings errors are prefixed with their project path. + */ + errors: string[]; } /** * Installed plugin record from global state, with marketplace, version, install time, enabled state, cache path, and source. @@ -6765,6 +9108,10 @@ export interface InstalledPlugin { * 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; + /** + * Absolute path of the marketplace directory a live plugin was resolved from. Present only on live, never-persisted records — those synthesized at session start for a directory/local marketplace, whose cache_path points at the real plugin directory on disk rather than a copy under the installed-plugins cache. Its presence is what marks a record as live, and no record carrying it is ever written to the persisted installedPlugins key. + */ + installed_from?: string; } /** * Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref or full commit SHA, and optional subpath. @@ -6778,12 +9125,21 @@ export interface InstalledPluginSourceGitHub { * Constant value. Always "github". */ source: "github"; + /** + * GitHub repository in `owner/repo` form. + */ repo: string; + /** + * Optional Git ref to resolve. + */ ref?: string; /** * Optional full 40-character hexadecimal commit SHA. */ sha?: string; + /** + * Optional repository-relative path to the plugin. + */ path?: string; } /** @@ -6798,12 +9154,21 @@ export interface InstalledPluginSourceUrl { * Constant value. Always "url". */ source: "url"; + /** + * URL of the plugin source. + */ url: string; + /** + * Optional Git ref to resolve. + */ ref?: string; /** * Optional full 40-character hexadecimal commit SHA. */ sha?: string; + /** + * Optional source-relative path to the plugin. + */ path?: string; } /** @@ -6818,6 +9183,9 @@ export interface InstalledPluginSourceLocal { * Constant value. Always "local". */ source: "local"; + /** + * Local filesystem path to the plugin. + */ path: string; } /** @@ -6848,6 +9216,10 @@ export interface InstalledPluginInfo { * Whether the plugin is currently enabled for new sessions */ enabled: boolean; + /** + * Absolute path of the marketplace directory a live plugin was resolved from. Present only on live, never-persisted records — a plugin belonging to a directory/local marketplace, which is loaded from its real directory on every pass instead of a copy under the installed-plugins cache. Its presence is what marks a listed plugin as live: such a plugin is always present on disk, so `enabled` is its only meaningful state and it is never "not installed". + */ + installedFrom?: string; } /** * Canonical file or directory where custom instructions can be discovered or created, with location, kind, preference, and project path. @@ -7347,6 +9719,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. * @@ -7512,7 +9901,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. @@ -7657,7 +10046,7 @@ export interface McpAppsListToolsResult { * App-callable tools from the server */ tools: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }[]; } /** @@ -7718,7 +10107,7 @@ export interface McpAppsResourceContent { * Resource-level metadata (CSP, permissions, etc.) */ _meta?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; } /** @@ -7798,7 +10187,7 @@ export interface McpConfigAddRequest { * Unique name for the MCP server */ name: string; - config: McpServerConfig; + config: McpSerializableServerConfig; } /** * Stdio MCP server configuration launched as a child process. @@ -7808,6 +10197,11 @@ export interface McpConfigAddRequest { */ /** @experimental */ export interface McpServerConfigStdio { + /** + * Optional human-readable server name. + */ + displayName?: string; + safeForTelemetry?: McpSafeForTelemetry; /** * Tools to include. Defaults to all tools if not specified. */ @@ -7818,7 +10212,7 @@ export interface McpServerConfigStdio { isDefaultServer?: boolean; filterMapping?: FilterMapping; /** - * Timeout in milliseconds for tool calls to this server. + * Timeout in milliseconds for tool discovery and tool calls. */ timeout?: number; oidc?: McpServerAuthConfig; @@ -7828,6 +10222,43 @@ export interface McpServerConfigStdio { * Set to true to disable persisted MCP tool snapshots for this server. Live tool discovery is unaffected. */ disableToolCache?: boolean; + /** + * Whether secret masking is disabled for calls to this server. + */ + disableSecretMasking?: boolean; + /** + * Tool names excluded after the include filter is applied. + */ + excludeTools?: string[]; + /** + * Event types this server receives as Copilot notifications. + */ + events?: string[]; + /** + * Copilot notification types this server may send to the host. + */ + notifications?: string[]; + source?: McpServerSource; + /** + * Plugin that provided this server. + */ + sourcePlugin?: string; + /** + * Version of the plugin that provided this server. + */ + sourcePluginVersion?: string; + /** + * Whether the providing plugin uses the Open Plugin Spec. + */ + sourcePluginSpec?: boolean; + /** + * Source file path recorded while loading the config. + */ + sourcePath?: string; + /** + * Configuration warnings recorded while loading the server. + */ + configWarnings?: string[]; /** * Executable command used to start the Stdio MCP server process. */ @@ -7846,6 +10277,24 @@ export interface McpServerConfigStdio { env?: { [k: string]: string | undefined; }; + type?: McpServerConfigStdioType; +} +/** + * Per-field MCP telemetry-obfuscation policy. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpSafeForTelemetryFields". + */ +/** @experimental */ +export interface McpSafeForTelemetryFields { + /** + * Whether the MCP tool name may be included in telemetry without obfuscation. + */ + name: boolean; + /** + * Whether MCP tool input names may be included in telemetry without obfuscation. + */ + inputsNames: boolean; } /** * Authentication settings with optional redirect port configuration. @@ -7868,6 +10317,11 @@ export interface McpServerAuthConfigRedirectPort { */ /** @experimental */ export interface McpServerConfigHttp { + /** + * Optional human-readable server name. + */ + displayName?: string; + safeForTelemetry?: McpSafeForTelemetry; /** * Tools to include. Defaults to all tools if not specified. */ @@ -7879,7 +10333,7 @@ export interface McpServerConfigHttp { isDefaultServer?: boolean; filterMapping?: FilterMapping; /** - * Timeout in milliseconds for tool calls to this server. + * Timeout in milliseconds for tool discovery and tool calls. */ timeout?: number; oidc?: McpServerAuthConfig; @@ -7889,6 +10343,43 @@ export interface McpServerConfigHttp { * Set to true to disable persisted MCP tool snapshots for this server. Live tool discovery is unaffected. */ disableToolCache?: boolean; + /** + * Whether secret masking is disabled for calls to this server. + */ + disableSecretMasking?: boolean; + /** + * Tool names excluded after the include filter is applied. + */ + excludeTools?: string[]; + /** + * Event types this server receives as Copilot notifications. + */ + events?: string[]; + /** + * Copilot notification types this server may send to the host. + */ + notifications?: string[]; + source?: McpServerSource; + /** + * Plugin that provided this server. + */ + sourcePlugin?: string; + /** + * Version of the plugin that provided this server. + */ + sourcePluginVersion?: string; + /** + * Whether the providing plugin uses the Open Plugin Spec. + */ + sourcePluginSpec?: boolean; + /** + * Source file path recorded while loading the config. + */ + sourcePath?: string; + /** + * Configuration warnings recorded while loading the server. + */ + configWarnings?: string[]; /** * URL of the remote MCP server endpoint. */ @@ -7899,6 +10390,10 @@ export interface McpServerConfigHttp { headers?: { [k: string]: string | undefined; }; + /** + * Dynamic-header refresh cache lifetime in milliseconds. + */ + headersRefreshTtlMs?: number; /** * OAuth client ID for a pre-registered remote MCP OAuth client. */ @@ -7947,7 +10442,7 @@ export interface McpConfigList { * All MCP servers from user config, keyed by name */ servers: { - [k: string]: McpServerConfig; + [k: string]: McpSerializableServerConfig; }; } /** @@ -7975,10 +10470,10 @@ export interface McpConfigUpdateRequest { * Name of the MCP server to update */ name: string; - config: McpServerConfig; + config: McpSerializableServerConfig; } /** - * Opaque auth info used to configure GitHub MCP. + * Credential-free authentication identity used to configure GitHub MCP. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "McpConfigureGitHubRequest". @@ -7991,9 +10486,7 @@ export interface McpConfigureGitHubRequest { * * @internal */ - authInfo: { - [k: string]: unknown | undefined; - }; + authInfo: OpaqueInProcessValue; } /** * Result of configuring GitHub MCP. @@ -8079,9 +10572,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; } /** @@ -8101,8 +10592,25 @@ export interface McpExecuteSamplingRequest { * via the `definition` "McpExecuteSamplingResult". */ /** @experimental */ -export interface McpExecuteSamplingResult { - [k: string]: unknown | undefined; +export interface McpExecuteSamplingResult { + [k: string]: unknown | undefined; +} +/** + * MCP server whose connection attempt failed. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpFailedServer". + */ +/** @experimental */ +export interface McpFailedServer { + /** + * The config key of the server that failed to connect. + */ + name: string; + /** + * The captured connection failure detail. + */ + error?: string; } /** * MCP server filtered by policy, with name, reason, and optional redacted reason. @@ -8228,6 +10736,300 @@ export interface McpServerNeedsAuthInfo { */ timestamp: number; } +/** + * A normalised, inert description of what installing an MCP server would involve. Carries no raw card, no install specification, and no secret value. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpInstallPlan". + */ +/** @experimental */ +export interface McpInstallPlan { + /** + * Opaque, runtime-instance scoped, TTL-bound, single-use handle for this plan. Rejected when stale, replayed, or presented to a different runtime instance. Never logged. + */ + planHandle: string; + /** + * ISO 8601 timestamp after which the plan handle is stale and will be rejected. Abandoning a plan needs no call: an unused handle simply expires, so cancellation before commit is side-effect free. + */ + planHandleExpiresAt: string; + identity: McpPlanResourceIdentity; + provenance: McpPlanProvenance; + /** + * Every eligible transport, so a host can present an explicit choice. A completed plan always has at least one; when none is eligible, planning returns `CatalogUnavailableTransportError` instead. + * + * @minItems 1 + * @maxItems 50 + */ + transportChoices: [McpPlanTransportChoice, ...McpPlanTransportChoice[]]; + /** + * Identifier of the choice the runtime would pick by default. Omitted when there is no eligible transport, or when the runtime expresses no preference. + */ + recommendedTransportChoiceId?: string; + target: McpPlanTarget; + policy: McpPlanPolicyResult; + /** + * The configuration changes installing would make, described rather than serialised, so the mutable configuration payload stays behind the runtime boundary. + */ + configurationChanges: McpPlanConfigurationChange[]; + /** + * Whether applying this plan would require an MCP reload to take effect. Planning itself never reloads. + */ + reloadRequired: boolean; + /** + * Whether the plan cannot be applied without further input, because a required value has no default or a secret must be supplied. + */ + requiresInteractiveConfiguration: boolean; +} +/** + * Normalised identity of the MCP server a plan targets, independent of how the card spelled it. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanResourceIdentity". + */ +/** @experimental */ +export interface McpPlanResourceIdentity { + /** + * Canonical, normalised name of the server, for example `io.github.owner/server`. + */ + canonicalName: string; + /** + * Local configuration key the server would be recorded under. + */ + serverName: string; + /** + * Version advertised by the card, when it declares one. + */ + version?: string; + /** + * Registry identifier of the server, when it came from a registry. + */ + registryId?: string; +} +/** + * Provenance of the exact validated JSON MCP card content bound privately to a completed plan and its opaque handle. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanProvenance". + */ +/** @experimental */ +export interface McpPlanProvenance { + /** + * Authority associated with the validated card, without path, query, or credentials. Inert untrusted data. + */ + authority: string; + /** + * ISO 8601 timestamp at which the runtime completed strict parsing and schema validation of the card content. + */ + validatedAt: string; + cardDigest: CardDigest; + mediaType: McpServerCardMediaType; +} +/** + * An eligible local-package transport choice. Package identity is required and a remote endpoint cannot be represented. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanTransportChoicePackage". + */ +/** @experimental */ +export interface McpPlanTransportChoicePackage { + /** + * Stable identifier for this choice within the plan, used to select it when the plan is applied. + */ + choiceId: string; + transport: McpPlanPackageTransport; + installMethod: McpPlanPackageInstallMethod; + /** + * Packaging ecosystem, for example `oci` or `npm`. + */ + packageType: string; + /** + * Package identifier. Inert untrusted data. + */ + packageIdentifier: string; + /** + * Typed values this choice requires, excluding secrets. + */ + requiredValues: McpPlanRequiredValue[]; + /** + * Secrets this choice requires, referenced by placeholder only. + */ + secretPlaceholders: McpPlanSecretPlaceholder[]; +} +/** + * One non-secret scalar value a transport choice needs before it can be applied. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanRequiredValueScalar". + */ +/** @experimental */ +export interface McpPlanRequiredValueScalar { + kind: McpPlanRequiredValueScalarKind; + /** + * Key the value is supplied under. Inert untrusted data. + */ + key: string; + category: McpPlanValueCategory; + valueType: McpPlanScalarValueType; + /** + * Whether the value must be present for the plan to be applicable. + */ + required: boolean; + /** + * Default supplied by the card, when the value can be resolved without input. Presence is the authoritative indication that a default exists. Inert untrusted data. + */ + defaultValue?: string; + /** + * Human-readable label from the card. Inert untrusted text. + */ + title?: string; + /** + * Human-readable explanation from the card. Inert untrusted text. + */ + description?: string; + /** + * Whether the value may be supplied more than once. + */ + isRepeated: boolean; +} +/** + * One enumerated non-secret value a transport choice needs before it can be applied. The permitted values are structurally required. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanRequiredValueEnum". + */ +/** @experimental */ +export interface McpPlanRequiredValueEnum { + kind: McpPlanRequiredValueEnumKind; + /** + * Key the value is supplied under. Inert untrusted data. + */ + key: string; + category: McpPlanValueCategory; + valueType: McpPlanEnumValueType; + /** + * Whether the value must be present for the plan to be applicable. + */ + required: boolean; + /** + * Default supplied by the card, when the value can be resolved without input. Presence is the authoritative indication that a default exists. Inert untrusted data. + */ + defaultValue?: string; + /** + * Human-readable label from the card. Inert untrusted text. + */ + title?: string; + /** + * Human-readable explanation from the card. Inert untrusted text. + */ + description?: string; + /** + * Non-empty permitted value set. Inert untrusted data. + * + * @minItems 1 + */ + enumValues: [string, ...string[]]; + /** + * Whether the value may be supplied more than once. + */ + isRepeated: boolean; +} +/** + * A secret a transport choice needs, referenced by placeholder. No secret value ever appears in a plan, and the placeholder resolves against the keychain only when a plan is applied. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanSecretPlaceholder". + */ +/** @experimental */ +export interface McpPlanSecretPlaceholder { + /** + * Key the secret is supplied under. Inert untrusted data. + */ + key: string; + placeholder: McpPlanSecretReference; + /** + * Human-readable label from the card. Inert untrusted text. + */ + title?: string; +} +/** + * An eligible remote-endpoint transport choice. The endpoint is required and package identity cannot be represented. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanTransportChoiceRemote". + */ +/** @experimental */ +export interface McpPlanTransportChoiceRemote { + /** + * Stable identifier for this choice within the plan, used to select it when the plan is applied. + */ + choiceId: string; + transport: McpPlanRemoteTransport; + installMethod: McpPlanRemoteInstallMethod; + /** + * Endpoint URL. Inert untrusted data. + */ + endpoint: string; + /** + * Typed values this choice requires, excluding secrets. + */ + requiredValues: McpPlanRequiredValue[]; + /** + * Secrets this choice requires, referenced by placeholder only. + */ + secretPlaceholders: McpPlanSecretPlaceholder[]; +} +/** + * Where a plan would be written. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanTarget". + */ +/** @experimental */ +export interface McpPlanTarget { + scope: McpPlanScope; + /** + * Configuration key the server would be recorded under within that scope. + */ + configKey: string; +} +/** + * Outcome of evaluating the planned server against registry and enterprise policy. Evaluation is read-only. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanPolicyResult". + */ +/** @experimental */ +export interface McpPlanPolicyResult { + decision: McpPlanPolicyDecision; + source: McpPlanPolicySource; + /** + * Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. + */ + reason?: string; +} +/** + * One change applying the plan would make, described rather than serialised so the configuration payload stays behind the runtime boundary. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanConfigurationChange". + */ +/** @experimental */ +export interface McpPlanConfigurationChange { + operation: McpPlanConfigurationOperation; + scope: McpPlanScope; + /** + * Configuration key the change applies to. + */ + configKey: string; + /** + * Names of the configuration fields the change would set, without their values. + */ + changedFields: string[]; + /** + * Secret placeholders the written configuration would reference. The constrained placeholder type cannot carry a literal secret value. + */ + secretReferences: McpPlanSecretReference[]; +} /** * Server name to check running status for. * @@ -8410,6 +11212,19 @@ export interface McpOauthLoginResult { */ authorizationUrl?: string; } +/** + * Remote MCP server name for a passive OAuth status probe. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpOauthProbeRequest". + */ +/** @experimental */ +export interface McpOauthProbeRequest { + /** + * Name of the configured remote MCP server to probe. + */ + serverName: string; +} /** * Pending MCP OAuth request id to respond to. * @@ -8436,6 +11251,92 @@ export interface McpOauthRespondResult { */ success: boolean; } +/** + * A computed MCP install plan. Nothing has been applied: the plan describes what installing would change, and the plan handle is what a later apply operation would consume. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanInstallPlanned". + */ +/** @experimental */ +export interface McpPlanInstallPlanned { + /** + * Discriminator: a plan was computed and nothing was changed + */ + kind: "planned"; + plan: McpInstallPlan; + negotiated: CatalogNegotiatedContract; +} +/** + * A side-effect-free request for an MCP install plan. Computing a plan never writes configuration, stores a secret, or reloads MCP servers. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanInstallRequest". + */ +/** @experimental */ +export interface McpPlanInstallRequest { + contract: CatalogClientContract; + source: McpPlanInstallSource; + scope?: McpPlanScope; +} +/** + * Plan from a candidate returned by a previous catalog search. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanInstallSourceCandidate". + */ +/** @experimental */ +export interface McpPlanInstallSourceCandidate { + kind: McpPlanInstallSourceCandidateKind; + /** + * Single-use candidate handle. Consumed by this call, so a replay of the same handle is rejected. + */ + candidateHandle: string; + /** + * The runtime- or authority-minted `searchId` returned with the search that produced this candidate. A search implementation binds it to private candidate-handle context; a planning implementation must verify that context before returning a plan. The unavailable planning implementation in this contract layer validates presence but does not claim the verification has occurred. It identifies a search rather than a person and must never be joined with user identity to re-identify anyone. + */ + searchId: string; +} +/** + * Plan from a card supplied directly by the caller, without a preceding search. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpPlanInstallSourceCard". + */ +/** @experimental */ +export interface McpPlanInstallSourceCard { + kind: McpPlanInstallSourceCardKind; + card: McpServerCardReference; +} +/** + * An MCP server card to be retrieved from a URL through the runtime's hardened fetch boundary. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpServerCardUrl". + */ +/** @experimental */ +export interface McpServerCardUrl { + kind: McpServerCardUrlKind; + mediaType: McpServerCardMediaType; + /** + * Card URL. Retrieved only through the runtime's hardened boundary, with scheme, credential, address-range, redirect, timeout, and response-size controls applied. Never logged. + */ + url: string; +} +/** + * An MCP server card supplied inline as an inert document. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpServerCardEmbedded". + */ +/** @experimental */ +export interface McpServerCardEmbedded { + kind: McpServerCardEmbeddedKind; + mediaType: McpServerCardMediaType; + /** + * The card document verbatim, treated as inert untrusted bytes. The runtime parses and validates it; the host is not expected to interpret it. Never logged. + */ + data: string; +} /** * Registration parameters for an external MCP client. * @@ -8454,25 +11355,47 @@ 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; +} +/** + * In-process MCP reload configuration. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpReloadConfig". + */ +/** @experimental */ +/** @internal */ +export interface McpReloadConfig { + mcpServers: { + [k: string]: McpServerConfig | undefined; }; + disabledServers?: string[]; + enabledServers?: string[]; + /** + * Server names the CLI enabled for this session via `--enable-mcp-server`. + */ + cliEnabledServers?: string[]; + mcp3pEnabled?: boolean; + includeWorkspaceSources?: boolean; + configFilter?: OpaqueInProcessValue; + githubMcpToolOptions?: OpaqueInProcessValue; + githubMcpUserOverride?: boolean; + secretStore?: OpaqueInProcessValue; + activeGitHubToken?: string; + useCachedToolSnapshots?: boolean; + forceRestart?: boolean; } /** * Opaque MCP reload configuration. @@ -8483,14 +11406,7 @@ export interface McpRegisterExternalClientRequest { /** @experimental */ /** @internal */ export interface McpReloadWithConfigRequest { - /** - * Opaque runtime MCP reload configuration. Marked internal: an in-process runtime shape (reloadMcpServers throws over the wire). - * - * @internal - */ - config: { - [k: string]: unknown | undefined; - }; + config: unknown; } /** * Indicates whether the auto-managed `github` MCP server was removed (false when nothing to remove). @@ -8546,13 +11462,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; }; } /** @@ -8583,7 +11499,7 @@ export interface McpResourceIcon { * Server-provided non-standard icon fields preserved from the MCP response */ additionalProperties?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; } /** @@ -8610,7 +11526,7 @@ export interface McpResourceAnnotations { * Server-provided non-standard annotation fields preserved from the MCP response */ additionalProperties?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; } /** @@ -8641,7 +11557,7 @@ export interface McpResourceContent { * Resource-level metadata (CSP, permissions, etc.) */ _meta?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; } /** @@ -8749,13 +11665,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; }; } /** @@ -8800,7 +11716,7 @@ export interface McpRestartServerRequest { * Name of the MCP server to restart */ serverName: string; - config?: McpServerConfig; + config?: McpSerializableServerConfig; } /** * Outcome of an MCP sampling execution: success result, failure error, or cancellation. @@ -8844,6 +11760,84 @@ export interface McpServer { */ error?: string; } +/** + * In-process MCP server configuration used by embedded SDK clients. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpServerConfigMemory". + */ +/** @experimental */ +/** @internal */ +export interface McpServerConfigMemory { + type: McpServerConfigMemoryType; + /** + * In-process MCP server instance. This value cannot cross a JSON-RPC boundary. + * + * @internal + */ + serverInstance: OpaqueInProcessValue; + /** + * Tools to include. Defaults to all tools if not specified. + */ + tools?: string[]; + /** + * Optional human-readable server name. + */ + displayName?: string; + /** + * Whether this server is a built-in fallback. + */ + isDefaultServer?: boolean; + filterMapping?: FilterMapping; + safeForTelemetry?: McpSafeForTelemetry; + /** + * Timeout in milliseconds for tool discovery and tool calls. + */ + timeout?: number; + oidc?: McpServerAuthConfig; + deferTools?: McpServerConfigDeferTools; + /** + * Whether persisted tool snapshots are disabled. + */ + disableToolCache?: boolean; + /** + * Whether secret masking is disabled for calls to this server. + */ + disableSecretMasking?: boolean; + /** + * Tool names excluded after the include filter is applied. + */ + excludeTools?: string[]; + /** + * Event types this server receives as Copilot notifications. + */ + events?: string[]; + /** + * Copilot notification types this server may send to the host. + */ + notifications?: string[]; + source?: McpServerSource; + /** + * Plugin that provided this server. + */ + sourcePlugin?: string; + /** + * Version of the plugin that provided this server. + */ + sourcePluginVersion?: string; + /** + * Whether the providing plugin uses the Open Plugin Spec. + */ + sourcePluginSpec?: boolean; + /** + * Source file path recorded while loading the config. + */ + sourcePath?: string; + /** + * Configuration warnings recorded while loading the server. + */ + configWarnings?: string[]; +} /** * MCP servers configured for the session, with their connection status and host-level state. * @@ -8890,7 +11884,7 @@ export interface McpStartServerRequest { * Name of the MCP server to start */ serverName: string; - config?: McpServerConfig; + config?: McpSerializableServerConfig; } /** * MCP server startup filtering result. @@ -8908,6 +11902,10 @@ export interface McpStartServersResult { * Non-default servers allowed by policy */ allowedServers?: McpAllowedServer[]; + /** + * Servers whose connection attempt failed. + */ + failedServers?: McpFailedServer[]; } /** * Server name for an individual MCP server stop. @@ -8922,6 +11920,19 @@ export interface McpStopServerRequest { */ serverName: string; } +/** + * Metadata controlling an MCP task's lifetime. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpTaskMetadata". + */ +/** @experimental */ +export interface McpTaskMetadata { + /** + * Task time-to-live. + */ + ttl?: number; +} /** * Server name identifying the external client to remove. * @@ -9218,8 +12229,25 @@ 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; + /** + * Context-window tiers this model offers, when the provider advertises them independently of tiered token pricing. Copilot models carry their tiers in `billing.tokenPrices`; a provider that has no pricing to publish (an agent host reached over AHP, for example) declares them here instead, so the model picker can still offer the tier toggle. + */ + supportedContextTiers?: string[]; modelPickerCategory?: ModelPickerCategory; modelPickerPriceCategory?: ModelPickerPriceCategory; + warningText?: ModelWarningText; + /** + * Informational notices the service published for this model, such as an upcoming change or a recommended alternative. Present only when the service published at least one notice. Hosts should surface these without implying anything is wrong with the model. + */ + infoMessages?: ModelMessage[]; + /** + * Warnings the service published for this model, such as a deprecated client version. Present only when the service published at least one warning. The model remains usable; hosts should surface these as advisory rather than blocking. + */ + warningMessages?: ModelMessage[]; } /** * Model capabilities and limits @@ -9355,6 +12383,10 @@ export interface ModelBillingTokenPrices { * AI Credits cost per billing batch of cache-write (cache creation) tokens. */ cacheWritePrice?: number; + /** + * AI Credits cost per billing batch of 1-hour cache-write (cache creation) tokens. + */ + cacheWrite1hPrice?: number; /** * Number of tokens per standard billing batch */ @@ -9399,6 +12431,10 @@ export interface ModelBillingTokenPricesLongContext { * AI Credits cost per billing batch of cache-write (cache creation) tokens. */ cacheWritePrice?: number; + /** + * AI Credits cost per billing batch of 1-hour cache-write (cache creation) tokens. + */ + cacheWrite1hPrice?: number; /** * @deprecated * 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. @@ -9433,6 +12469,81 @@ export interface ModelBillingPromo { * Human-readable promotion message. Does not include the expiry timestamp; consumers may format endsAt and append it when present. */ message?: string; + /** + * Whether the service asked hosts to give this promotion a prominent surface, such as a dedicated banner, in addition to listing it with the model. `true` requests that surface and `false` asks for the model list only. Absent means the service expressed no preference — for example a response that predates the field — so hosts should apply their own default rather than read it as `false`. + */ + showBanner?: boolean; +} +/** + * Service-published warning text that hosts should display when presenting a model. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ModelWarningText". + */ +/** @experimental */ +export interface ModelWarningText { + /** + * Data-retention warning for the model. The text may contain Markdown links and should be rendered as Markdown when supported. + */ + dataRetention?: string; +} +/** + * A service-published message about a model, carrying a stable machine-readable code alongside human-readable text. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ModelMessage". + */ +/** @experimental */ +export interface ModelMessage { + /** + * Stable machine-readable identifier for the message, such as `client_version_deprecated`. Hosts can key custom presentation off this; unrecognized codes should fall back to displaying `message`. + */ + code: string; + /** + * Human-readable message text intended for display to the user. + */ + message: string; +} +/** + * Managed, repository, and CLI model overrides to overlay onto the session at startup. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ModelApplyStartupOverlayRequest". + */ +/** @experimental */ +export interface ModelApplyStartupOverlayRequest { + /** + * Model required by device-managed policy, when configured. + */ + deviceManagedModel?: string; + /** + * Model required by server-managed policy, when configured. + */ + serverManagedModel?: string; + /** + * Startup default model from the enterprise policy helper, when configured. Weakest of the managed sources: it applies only when neither device nor server policy names a model, and an explicit user selection still wins. + */ + policyHelperModel?: string; + /** + * Model selected by repository settings, when configured. + */ + repoModel?: string; + /** + * Reasoning effort selected by repository settings, when configured. + */ + repoReasoningEffort?: string; + /** + * Context tier selected by repository settings, when configured. + */ + repoContextTier?: string; + /** + * Model explicitly selected by the CLI, when provided. + */ + cliModel?: string; + /** + * Whether the overlay is being applied while resuming a deferred session. + */ + deferredResume?: boolean; } /** * Optional capability overrides (vision, tool_calls, reasoning, etc.). @@ -9519,6 +12630,40 @@ export interface ModelList { */ models: Model[]; } + +/** @experimental */ +export interface ModelPickerPersistenceRequest { + settingsContext: ModelPickerSettingsContext; + /** + * Whether reasoning effort was explicitly selected and should be persisted. + */ + reasoningEffortExplicit?: boolean; + /** + * Whether context tier was explicitly selected and should be persisted. + */ + contextTierExplicit?: boolean; +} +/** + * Filesystem and environment context used to resolve model-picker settings. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ModelPickerSettingsContext". + */ +/** @experimental */ +export interface ModelPickerSettingsContext { + /** + * Optional Copilot configuration directory containing persisted settings. + */ + configDir?: string; + /** + * User home directory used when resolving persisted settings. + */ + homeDirectory: string; + /** + * Environment variables consulted while resolving model-picker settings. + */ + environment: {}; +} /** * Reasoning effort level to apply to the currently selected model. * @@ -9549,10 +12694,30 @@ export interface ModelSetReasoningEffortResult { /** @experimental */ export interface 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. + * Opaque account identifier returned by `account.getAllUsers`. When omitted, the current account is used. + */ + selectionId?: string; + /** + * GitHub token accepted for compatibility with existing SDK clients. When provided, resolves this token instead of using the current account. */ gitHubToken?: string; } + +/** @experimental */ +export interface ModelSwitchConfirmation { + /** + * Display name of the model that requires compaction confirmation. + */ + targetModelDisplayName: string; + /** + * Current conversation token count before switching models. + */ + currentTokens: number; + /** + * Target model token limit used by the compaction preflight. + */ + targetLimit: number; +} /** * Target model identifier and optional reasoning effort, summary, capability overrides, and context tier. * @@ -9573,10 +12738,32 @@ export interface ModelSwitchToRequest { verbosity?: Verbosity; modelCapabilities?: ModelCapabilitiesOverride; contextTier?: ContextTier; + source?: ModelChangeSource; /** * 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; + /** + * Explicit response to a model-switch compaction preflight. Omit to request a confirmation projection when compaction is necessary. + */ + compactionDecision?: string; + /** + * When true, evaluate context-window compaction policy before applying the switch. + */ + runCompactionPreflight?: boolean; + /** + * Optional repository settings scope to persist after the switch commits. + */ + repoScope?: string; + /** + * Settings scope used when persisting the selected model. + */ + modelChangeScope?: string; + /** + * Require the target to be currently available and enabled before applying the switch. + */ + requireAvailable?: boolean; + pickerPersistence?: ModelPickerPersistenceRequest; } /** * The model identifier active on the session after the switch. @@ -9594,6 +12781,27 @@ export interface 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. */ deferred?: boolean; + /** + * Lifecycle result for the requested switch + */ + status?: string; + confirmation?: ModelSwitchConfirmation; + /** + * Persistence failure encountered after applying the model switch. + */ + persistenceError?: string; + /** + * User-facing outcome message for the model switch. + */ + message?: string; + /** + * User-facing warning produced while applying the model switch. + */ + warning?: string; + /** + * Deprecation warnings associated with the selected model or options. + */ + deprecationWarnings?: string[]; } /** * Agent interaction mode to apply to the session. @@ -9604,9 +12812,97 @@ export interface ModelSwitchToResult { /** @experimental */ export interface ModeSetRequest { mode: SessionMode; + /** + * Session whose plan-mode base state should be inherited. + */ + inheritPlanBaseFromSessionId?: string; + /** + * Whether a dedicated plan model is configured. + */ + planModelConfigured?: boolean; + /** + * Dedicated model to use in plan mode, when configured. + */ + planModel?: string; + /** + * Reasoning effort to use with the dedicated plan model. + */ + planReasoningEffort?: string; + /** + * Context tier to use with the dedicated plan model. + */ + planContextTier?: string; + /** + * Explicit response to a model-switch compaction preflight. + */ + compactionDecision?: string; + /** + * Whether leaving plan mode should restore the session's previous model. + */ + restorePlanModel?: boolean; + /** + * Whether the selected plan model should be persisted. + */ + persistPlanSelection?: boolean; + pickerSettingsContext?: ModelPickerSettingsContext; + /** + * Action to perform when leaving plan mode. + */ + planExitAction?: string; } /** - * A named BYOK provider connection (transport + credentials). + * Outcome of a session mode change, including any model switch it triggered and follow-up the host must perform. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ModeSetResult". + */ +/** @experimental */ +export interface ModeSetResult { + /** + * Lifecycle status of the requested mode change. + */ + status: string; + /** + * Whether applying the mode changed the active model. + */ + modelChanged: boolean; + confirmation?: ModelSwitchConfirmation; + /** + * User-facing warning produced while applying the mode change. + */ + warning?: string; + /** + * User-facing outcome message for the model switch triggered by the mode change. + */ + message?: string; + /** + * Deprecation warnings associated with the model selected by the mode change. + */ + deprecationWarnings?: string[]; + /** + * Whether the host must defer implementing the requested mode change. + */ + deferImplementation?: boolean; + /** + * Whether the host should arm an interactive continuation after the mode change. + */ + armInteractiveContinuation?: boolean; +} +/** + * Result of moving in-flight MCP loading to the background. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "MoveMcpLoadingToBackgroundResult". + */ +/** @experimental */ +export interface MoveMcpLoadingToBackgroundResult { + /** + * Whether an in-flight MCP load was moved to the background, releasing turns that were waiting on it. False when no MCP load was in flight or the waiting turns had already been released. + */ + movedToBackground: boolean; +} +/** + * External SDK input for a named custom model provider. Ingested by the native protocol boundary before host dispatch. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "NamedProviderConfig". @@ -9614,33 +12910,33 @@ export interface ModeSetRequest { /** @experimental */ export interface NamedProviderConfig { /** - * Stable identifier referenced by BYOK model definitions. Must not contain '/'. + * Unique provider name used to qualify model selection IDs. */ name: string; type?: ProviderConfigType; wireApi?: ProviderConfigWireApi; transport?: ProviderConfigTransport; /** - * API endpoint URL. + * Base URL for provider API requests. */ baseUrl: string; /** - * API key. Optional for local providers like Ollama. + * Static API key used to authenticate provider requests. */ apiKey?: string; /** - * Bearer token for authentication. Sets the Authorization header directly. Takes precedence over apiKey when both are set. + * Static bearer token used to authenticate provider requests. */ bearerToken?: string; azure?: ProviderConfigAzure; /** - * Custom HTTP headers to include in all outbound requests to the provider. + * Additional HTTP headers included with provider requests. */ headers?: { [k: string]: string | undefined; }; /** - * 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. + * Whether the host supplies bearer tokens dynamically. */ hasBearerTokenProvider?: boolean; } @@ -9717,8 +13013,14 @@ export interface NameSetRequest { */ /** @experimental */ export interface OptionsUpdateAdditionalContentExclusionPolicy { + /** + * Content-exclusion rules to apply. + */ rules: OptionsUpdateAdditionalContentExclusionPolicyRule[]; - last_updated_at: unknown; + /** + * Opaque policy update timestamp supplied by the host. + */ + last_updated_at: JsonValue; scope: OptionsUpdateAdditionalContentExclusionPolicyScope; } /** @@ -9729,8 +13031,17 @@ export interface OptionsUpdateAdditionalContentExclusionPolicy { */ /** @experimental */ export interface OptionsUpdateAdditionalContentExclusionPolicyRule { + /** + * Path patterns covered by this rule. + */ paths: string[]; + /** + * Conditions of which at least one must match. + */ ifAnyMatch?: string[]; + /** + * Conditions none of which may match. + */ ifNoneMatch?: string[]; source: OptionsUpdateAdditionalContentExclusionPolicyRuleSource; } @@ -9742,7 +13053,13 @@ export interface OptionsUpdateAdditionalContentExclusionPolicyRule { */ /** @experimental */ export interface OptionsUpdateAdditionalContentExclusionPolicyRuleSource { + /** + * Name of the policy source. + */ name: string; + /** + * Type of the policy source. + */ type: string; } /** @@ -9961,13 +13278,36 @@ export interface PermissionDecisionApproveForSessionApprovalFactory { /** @experimental */ export interface PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess { /** - * Approval covering an extension's request to access a permission-gated capability. + * Approval covering an extension's request to access a permission-gated capability. + */ + kind: "extension-permission-access"; + /** + * Extension name. + */ + extensionName: string; +} +/** + * Session-scoped approval details for an extension's access to sensitive environment variables, keyed by extension name and the exact set of variable names. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "PermissionDecisionApproveForSessionApprovalExtensionEnvAccess". + */ +/** @experimental */ +export interface PermissionDecisionApproveForSessionApprovalExtensionEnvAccess { + /** + * Approval covering an extension's request to read sensitive environment variables. */ - kind: "extension-permission-access"; + kind: "extension-env-access"; /** * Extension name. */ extensionName: string; + /** + * Names of the sensitive environment variables this approval covers. Values are never persisted. + * + * @minItems 1 + */ + environmentVariables: [string, ...string[]]; } /** * Permission-decision request variant to approve and persist a permission for a project location, with approval details and location key. @@ -10149,6 +13489,29 @@ export interface PermissionDecisionApproveForLocationApprovalExtensionPermission */ extensionName: string; } +/** + * Location-scoped approval details for an extension's access to sensitive environment variables, keyed by extension name and the exact set of variable names. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "PermissionDecisionApproveForLocationApprovalExtensionEnvAccess". + */ +/** @experimental */ +export interface PermissionDecisionApproveForLocationApprovalExtensionEnvAccess { + /** + * Approval covering an extension's request to read sensitive environment variables. + */ + kind: "extension-env-access"; + /** + * Extension name. + */ + extensionName: string; + /** + * Names of the sensitive environment variables this approval covers. Values are never persisted. + * + * @minItems 1 + */ + environmentVariables: [string, ...string[]]; +} /** * Permission-decision request variant to permanently approve a URL domain across sessions. * @@ -10351,6 +13714,19 @@ 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; + responseCapability?: PermissionResponseCapability; +} /** * Pending permission request ID and the decision to apply (approve/reject and scope). * @@ -10364,6 +13740,7 @@ export interface PermissionDecisionRequest { */ requestId: string; result: PermissionDecision; + decisionContext?: PermissionDecisionContext; } /** * Location-scoped tool approval to persist. @@ -10541,6 +13918,29 @@ export interface PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAc */ extensionName: string; } +/** + * Location-persisted tool approval details for an extension's access to sensitive environment variables, keyed by extension name and the exact set of variable names. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess". + */ +/** @experimental */ +export interface PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess { + /** + * Approval covering an extension's request to read sensitive environment variables. + */ + kind: "extension-env-access"; + /** + * Extension name. + */ + extensionName: string; + /** + * Names of the sensitive environment variables this approval covers. Values are never persisted. + * + * @minItems 1 + */ + environmentVariables: [string, ...string[]]; +} /** * Working directory to load persisted location permissions for. * @@ -10620,7 +14020,7 @@ export interface PermissionLocationResolveResult { /** @experimental */ export interface PermissionPathsAddParams { /** - * Directory to add to the allow-list. The runtime resolves and validates the path before adding. + * Directory to add to the allow-list. The runtime resolves and validates the path before adding, then loads conventional `.github/skills/` and `.github/agents/` definitions under it when their subsystem gates are enabled. Adding the directory is therefore also a trust decision for configuration stored there. */ path: string; } @@ -10663,7 +14063,7 @@ export interface PermissionPathsConfig { */ unrestricted?: boolean; /** - * 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). + * Additional directories to allow tool access to (in addition to the session's working directory). Conventional `.github/skills/` and `.github/agents/` definitions under them also join the session catalogs when their subsystem gates are enabled, so supplying a directory is a trust decision for configuration stored there. When `unrestricted` is true, these are still pre-populated on the UnrestrictedPathManager so they remain visible via getDirectories() (e.g. for @-mention completion). */ additionalDirectories?: string[]; /** @@ -10782,8 +14182,14 @@ export interface PermissionRulesSet { */ /** @experimental */ export interface PermissionsConfigureAdditionalContentExclusionPolicy { + /** + * Content-exclusion rules to apply. + */ rules: PermissionsConfigureAdditionalContentExclusionPolicyRule[]; - last_updated_at: unknown; + /** + * Opaque policy update timestamp supplied by the host. + */ + last_updated_at: JsonValue; scope: PermissionsConfigureAdditionalContentExclusionPolicyScope; } /** @@ -10794,8 +14200,17 @@ export interface PermissionsConfigureAdditionalContentExclusionPolicy { */ /** @experimental */ export interface PermissionsConfigureAdditionalContentExclusionPolicyRule { + /** + * Path patterns covered by this rule. + */ paths: string[]; + /** + * Conditions of which at least one must match. + */ ifAnyMatch?: string[]; + /** + * Conditions none of which may match. + */ ifNoneMatch?: string[]; source: PermissionsConfigureAdditionalContentExclusionPolicyRuleSource; } @@ -10807,7 +14222,13 @@ export interface PermissionsConfigureAdditionalContentExclusionPolicyRule { */ /** @experimental */ export interface PermissionsConfigureAdditionalContentExclusionPolicyRuleSource { + /** + * Name of the policy source. + */ name: string; + /** + * Type of the policy source. + */ type: string; } /** @@ -10881,10 +14302,20 @@ export interface PermissionsFolderTrustAddTrustedResult { * No parameters. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "PermissionsGetAllowAllRequest". + * via the `definition` "PermissionsGetModeRequest". + */ +/** @experimental */ +export interface PermissionsGetModeRequest {} +/** + * Current permission mode. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "PermissionsGetModeResult". */ /** @experimental */ -export interface PermissionsGetAllowAllRequest {} +export interface PermissionsGetModeResult { + mode: PermissionMode; +} /** * Indicates whether the operation succeeded. * @@ -11015,50 +14446,60 @@ export interface PermissionsResetSessionApprovalsResult { success: boolean; } /** - * Allow-all mode to apply for the session. + * Allow-all toggle for tool permission requests, with an optional telemetry source. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "PermissionsSetAllowAllRequest". + * via the `definition` "PermissionsSetApproveAllRequest". */ /** @experimental */ -export interface PermissionsSetAllowAllRequest { - mode?: PermissionsAllowAllMode; +export interface PermissionsSetApproveAllRequest { /** - * 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"`. + * Whether to auto-approve all tool permission requests */ - enabled?: boolean; + enabled: boolean; + source?: PermissionsSetApproveAllSource; +} +/** + * Indicates whether the operation succeeded. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "PermissionsSetApproveAllResult". + */ +/** @experimental */ +export interface PermissionsSetApproveAllResult { /** - * 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. + * Whether the operation succeeded */ - model?: string; - source?: PermissionsSetAllowAllSource; + success: boolean; } /** - * Allow-all toggle for tool permission requests, with an optional telemetry source. + * Permission mode to apply for the session. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "PermissionsSetApproveAllRequest". + * via the `definition` "PermissionsSetModeRequest". */ /** @experimental */ -export interface PermissionsSetApproveAllRequest { +export interface PermissionsSetModeRequest { + mode: PermissionMode; /** - * Whether to auto-approve all tool permission requests + * Optional judge model id for assisted mode. When omitted, the session resolves the provider default: `gpt-5.5` for CAPI sessions and the active session model for BYOK sessions. */ - enabled: boolean; - source?: PermissionsSetApproveAllSource; + assistedApprovalModel?: string; + source?: PermissionModeSource; } /** - * Indicates whether the operation succeeded. + * Indicates whether the requested permission mode was applied and reports the authoritative post-mutation mode. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "PermissionsSetApproveAllResult". + * via the `definition` "PermissionsSetModeResult". */ /** @experimental */ -export interface PermissionsSetApproveAllResult { +export interface PermissionsSetModeResult { /** * Whether the operation succeeded */ success: boolean; + mode: PermissionMode; } /** * Toggles whether permission prompts should be bridged into session events for this client. @@ -11176,7 +14617,7 @@ export interface PlanReadResult { /** @experimental */ export interface PlanReadSqlTodosResult { /** - * Rows from the session SQL todos table, ordered by creation time and id. + * Rows from the session SQL todos table, ordered by creation time with insertion order used to break ties when available and id used for WITHOUT ROWID tables. */ rows: PlanSqlTodosRow[]; } @@ -11204,6 +14645,10 @@ export interface PlanSqlTodosRow { * Todo status. */ status?: string; + /** + * Todo creation time, as stored by the session SQL schema's `datetime('now')` default: `YYYY-MM-DD HH:MM:SS` in UTC. Lets clients attribute todos to the work item that created them (e.g. scoping a goal's progress to the todos it produced) rather than to the whole session. + */ + createdAt?: string; } /** * Todo rows + dependency edges read from the session SQL database. @@ -11214,7 +14659,7 @@ export interface PlanSqlTodosRow { /** @experimental */ export interface PlanReadSqlTodosWithDependenciesResult { /** - * Rows from the session SQL todos table, ordered by creation time and id. Empty when no database, no todos table, or the SELECT failed. + * Rows from the session SQL todos table, ordered by creation time with insertion order used to break ties when available and id used for WITHOUT ROWID tables. Empty when no database, no todos table, or the SELECT failed. */ rows: PlanSqlTodosRow[]; /** @@ -11325,6 +14770,21 @@ export interface PluginListResult { */ plugins: InstalledPluginInfo[]; } +/** + * Trusted built-in plugin directories to use for this runtime process. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "PluginsBuiltinSetRequest". + */ +/** @experimental */ +export interface PluginsBuiltinSetRequest { + /** + * Complete replacement set of trusted built-in plugin directories. Every entry must be an absolute local filesystem path no longer than 4096 characters. + * + * @maxItems 64 + */ + paths: string[]; +} /** * Plugin names (or specs) to disable. * @@ -11524,6 +14984,52 @@ export interface PluginUpdateResult { */ skillsInstalled: number; } +/** + * Serializable definition of a caller-implemented tool whose execution is handled over the SDK connection. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ProtocolExternalToolDefinition". + */ +/** @experimental */ +export interface ProtocolExternalToolDefinition { + /** + * Unique model-visible tool name. + */ + name: string; + /** + * Model-visible explanation of what the tool does. + */ + description: string; + /** + * Optional human-readable display title. + */ + title?: string; + /** + * JSON Schema describing the tool's input arguments. + */ + parameters?: { + [k: string]: JsonValue | undefined; + }; + /** + * Whether this definition replaces a built-in tool with the same name. + */ + overridesBuiltInTool?: boolean; + /** + * Whether execution bypasses the normal tool permission prompt. + */ + skipPermission?: boolean; + defer?: ProtocolExternalToolDefer; + /** + * Whether the tool executes commands in a terminal. + */ + isTerminal?: boolean; + /** + * Optional caller-defined metadata associated with the tool. + */ + metadata?: { + [k: string]: JsonValue | undefined; + }; +} /** * BYOK providers and/or models to add to the session's registry at runtime. Both fields are optional; provide providers, models, or both. * @@ -11554,7 +15060,7 @@ export interface ProviderModelConfig { */ id: string; /** - * Name of the NamedProviderConfig that serves this model. + * Name of the configured provider that serves this model. */ provider: string; /** @@ -11594,7 +15100,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). @@ -11624,6 +15130,11 @@ export interface ProviderConfig { * Well-known model ID used for capability lookup. When set, agent behavior config and token limits are inferred from this model. */ modelId?: string; + modelCapabilities?: ModelCapabilitiesOverride; + /** + * Provider name used for model and telemetry attribution. + */ + providerName?: string; /** * The model identifier sent to the provider API for inference (the "wire" model), as opposed to modelId which is the well-known base. */ @@ -11716,7 +15227,7 @@ export interface ProviderTokenAcquireRequest { */ sessionId: string; /** - * 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`. + * 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 the configured provider name. */ providerName: string; } @@ -12229,9 +15740,7 @@ export interface QueueConsumeSystemNotificationsRequest { /** * Opaque runtime-owned filter object. */ - filter: { - [k: string]: unknown | undefined; - }; + filter: JsonValue; } /** * Inputs for marking session.idle deferred in native state. @@ -12254,6 +15763,9 @@ export interface QueueDeferSessionIdleRequest { */ /** @experimental */ export interface QueueDuplicateAtRequest { + /** + * Stable opaque ID of the queued item to duplicate. + */ id: string; } /** @@ -12451,6 +15963,10 @@ export interface QueuePendingItems { * Stable opaque id for the canonical queued item. Batch rows share one id. */ id: string; + /** + * Stable identity of the queued user message. Present for message rows and absent for slash commands and model changes. + */ + messageId?: string; kind: QueuePendingItemsKind; /** * Human-readable text to display for this queue entry in the UI @@ -12474,6 +15990,10 @@ export interface QueuePendingItemsResult { * Display text for messages currently in the immediate steering queue (interjections sent during a running turn). */ steeringMessages: string[]; + /** + * How many leading entries of `steeringMessages` have already been folded into the running turn (and so have an emitted `user.message`), as opposed to still waiting for one. Absent for hosts that do not distinguish the two. + */ + inFlightSteeringCount?: number; } /** * Parameters for removing a queued item by stable id. @@ -12483,6 +16003,9 @@ export interface QueuePendingItemsResult { */ /** @experimental */ export interface QueueRemoveAtRequest { + /** + * Stable opaque ID of the queued item to remove. + */ id: string; } /** @@ -12519,6 +16042,9 @@ export interface QueueRemoveMostRecentResult { */ /** @experimental */ export interface QueueSendNowRequest { + /** + * Stable opaque ID of the queued item to steer into the live turn. + */ id: string; } /** @@ -12542,6 +16068,9 @@ export interface QueueSendNowResult { */ /** @experimental */ export interface QueueSetDrainPausedRequest { + /** + * Whether queued-lane draining should be paused. + */ paused: boolean; } /** @@ -12577,8 +16106,17 @@ export interface QueueSnapshotResult { */ /** @experimental */ export interface QueueUpdateTextRequest { + /** + * Stable opaque ID of the queued item to edit. + */ id: string; + /** + * Replacement prompt sent to the model. + */ prompt: string; + /** + * Optional replacement prompt displayed to the user. + */ displayPrompt?: string; } /** @@ -12634,15 +16172,13 @@ export interface RegisterExtensionToolsParams { */ sessionId: string; /** - * 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. + * In-process ExtensionLoader handle used only by the CLI and excluded from the public SDK surface. * * @internal * * @internal */ - loader: { - [k: string]: unknown | undefined; - }; + loader: OpaqueInProcessValue; options?: SessionsRegisterExtensionToolsOnSessionOptions; } /** @@ -12654,13 +16190,11 @@ export interface RegisterExtensionToolsParams { /** @experimental */ export interface SessionsRegisterExtensionToolsOnSessionOptions { /** - * In-process `() => boolean` gating callback (CLI-only optimization). Marked internal: replaced by runtime-side enable/disable RPCs in the SDK migration. + * In-process `() => boolean` gating callback used only by the CLI. * * @internal */ - enabled?: { - [k: string]: unknown | undefined; - }; + enabled?: OpaqueInProcessValue; } /** * Handle for releasing the extension tool registration. @@ -12672,15 +16206,13 @@ export interface SessionsRegisterExtensionToolsOnSessionOptions { /** @internal */ export interface RegisterExtensionToolsResult { /** - * In-process unsubscribe function (CLI-only optimization). Marked internal: replaced by an explicit `extensions.unregister` RPC in the SDK migration. + * In-process unsubscribe function used only by the CLI. * * @internal * * @internal */ - unsubscribe: { - [k: string]: unknown | undefined; - }; + unsubscribe: OpaqueInProcessValue; } /** * Opaque handle previously returned by `registerInterest` to release. @@ -12797,13 +16329,11 @@ export interface RemoteControlStatusActive { */ isSteerable: boolean; /** - * 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. + * In-process prompt-manager handle (CLI-only optimization). Marked internal: this field is excluded from the public SDK surface. Retained as an optional compatibility field; native remote control does not populate or consume it. * * @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. * @@ -13008,6 +16538,11 @@ export interface RemoteSessionMetadataValue { * Server-side task state returned by GitHub. */ state?: string; + hostStatus?: RemoteSessionHostStatus; + /** + * Host-supplied human description of what the session is doing right now ("running tests", "waiting for approval"). Optional in the protocol and absent on hosts that do not publish it, so never rely on it -- it enriches `hostStatus`, it does not replace it. + */ + hostActivity?: string; } /** * Repository context for the remote session. @@ -13047,18 +16582,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). - */ - ghAuth?: boolean; - /** - * 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 exporting CARGO_HOME/GOPATH/etc. Default: true (enabled by default; set to false to opt out). + * Whether to auto-grant read access to tool directories discovered on PATH and in toolchain environment variables (GOROOT, JAVA_HOME, VIRTUAL_ENV, and similar), and to common developer-tool caches, config, and toolchains. Writable grants cover scratch caches, the Unix GitHub CLI cache, and Cargo's registry, git store, and lock/tracker files. A relocated CARGO_HOME gets the same narrow split: registry and git are read-write; bin is read-only; the home root, config.toml, and credentials.toml stay ungranted. Set to false to disable every grant listed above; user-installed toolchains and caches then need explicit userPolicy.filesystem readonlyPaths and readwritePaths entries. The working directory (see addCurrentWorkingDirectory), temporary storage, session log paths, and system locations follow their own rules and stay granted. Default: true (enabled by default; set to false to opt out). */ - allowDevToolCaches?: boolean; + allowDevToolAccess?: boolean; } /** * User-managed sandbox policy fragment merged into the auto-discovered base policy. @@ -13125,7 +16653,7 @@ export interface SandboxConfigUserPolicyNetwork { /** @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. + * Proxy URL (e.g. http://proxy.example.com:8080). The port is optional and defaults to the scheme's standard port when omitted; an explicit port must be between 1 and 65535. 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 proxy 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; /** @@ -13173,6 +16701,44 @@ export interface SandboxConfigUserPolicyExperimentalSeatbelt { */ keychainAccess?: boolean; } +/** + * Credential-injection capability flags applied while the sandbox is enabled. For the same capability independent of sandboxing, and matched to the credential's GitHub host, see `shell.credentials`; the two are additive. + * + * 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; +} +/** + * Managed sandbox enforcement state for a session. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SandboxEnforcementStatus". + */ +/** @experimental */ +export interface SandboxEnforcementStatus { + /** + * Whether the effective managed policy requires an available sandbox backend. + */ + required: boolean; + /** + * Whether an enforcement failure has permanently blocked the session. + */ + blocked: boolean; + /** + * The first sandbox enforcement failure that blocked the session. + */ + reason?: string; +} /** * Register an absolute-time scheduled prompt. * @@ -13615,15 +17181,11 @@ export interface SendSystemNotificationRequest { /** * Optional structured notification kind. */ - kind?: { - [k: string]: unknown | undefined; - }; + kind?: JsonValue; /** * Internal delivery options, including passive policy. */ - options?: { - [k: string]: unknown | undefined; - }; + options?: JsonValue; } /** * Agents discovered across user, project, plugin, and remote sources. @@ -13663,6 +17225,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 */ @@ -13723,6 +17289,41 @@ export interface SessionActivity { */ hasActiveWork: boolean; } +/** + * Internal GitHub login parameters. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionAuthLoginRequest". + */ +/** @experimental */ +export interface SessionAuthLoginRequest { + /** + * GitHub host URL + */ + host: string; + /** + * GitHub login + */ + login: string; + /** + * GitHub authentication token + */ + token: string; + /** + * Whether to persist the token after login + */ + persist?: boolean; +} +/** + * Parameters identifying a GitHub authentication to log out. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionAuthLogoutUserRequest". + */ +/** @experimental */ +export interface SessionAuthLogoutUserRequest { + authInfo: AuthInfo; +} /** * Authentication status and account metadata for the session. * @@ -13753,6 +17354,20 @@ export interface SessionAuthStatus { */ copilotPlan?: string; } +/** + * Parameters for switching the session's active authentication. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionAuthSwitchRequest". + */ +/** @experimental */ +export interface SessionAuthSwitchRequest { + authInfo: AuthInfo; + /** + * Optional token paired with the authentication information + */ + token?: string; +} /** * Map of sessionId -> bytes freed by removing the session's workspace directory. * @@ -14107,7 +17722,7 @@ export interface SessionFsSqliteQueryRequest { * Optional named bind parameters */ params?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; } /** @@ -14122,7 +17737,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 @@ -14147,6 +17762,9 @@ export interface SessionFsSqliteQueryResult { /** @experimental */ export interface SessionFsSqliteTransactionError { errorClass: SessionFsSqliteTransactionErrorClass; + /** + * Human-readable transaction failure message. + */ message: string; } /** @@ -14161,6 +17779,9 @@ export interface SessionFsSqliteTransactionRequest { * Target session identifier */ sessionId: string; + /** + * Ordered SQL statements to execute in one transaction. + */ statements: SessionFsSqliteTransactionStatement[]; } /** @@ -14180,7 +17801,7 @@ export interface SessionFsSqliteTransactionStatement { * Optional named bind parameters. */ params?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; } /** @@ -14191,6 +17812,9 @@ export interface SessionFsSqliteTransactionStatement { */ /** @experimental */ export interface SessionFsSqliteTransactionResult { + /** + * Per-statement query results in input order. + */ results: SessionFsSqliteQueryResult[]; error?: SessionFsSqliteTransactionError; } @@ -14303,6 +17927,10 @@ export interface SessionInstalledPlugin { * 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; + /** + * Absolute path of the marketplace directory a live plugin was resolved from. Present only on live, never-persisted records — those synthesized at session start for a directory/local marketplace, whose cache_path points at the real plugin directory on disk rather than a copy under the installed-plugins cache. Its presence is what marks a record as live, and no record carrying it is ever written to the persisted installedPlugins key. + */ + installed_from?: string; } /** * Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref or full commit SHA, and optional subpath. @@ -14316,12 +17944,21 @@ export interface SessionInstalledPluginSourceGitHub { * Constant value. Always "github". */ source: "github"; + /** + * GitHub repository in `owner/repo` form. + */ repo: string; + /** + * Optional Git ref to resolve. + */ ref?: string; /** * Optional full 40-character hexadecimal commit SHA. */ sha?: string; + /** + * Optional repository-relative path to the plugin. + */ path?: string; } /** @@ -14336,12 +17973,21 @@ export interface SessionInstalledPluginSourceUrl { * Constant value. Always "url". */ source: "url"; + /** + * URL of the plugin source. + */ url: string; + /** + * Optional Git ref to resolve. + */ ref?: string; /** * Optional full 40-character hexadecimal commit SHA. */ sha?: string; + /** + * Optional source-relative path to the plugin. + */ path?: string; } /** @@ -14356,6 +18002,9 @@ export interface SessionInstalledPluginSourceLocal { * Constant value. Always "local". */ source: "local"; + /** + * Local filesystem path to the plugin. + */ path: string; } /** @@ -14477,6 +18126,41 @@ export interface SessionLoadDeferredRepoHooksResult { */ 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 { + /** + * When set to `disable`, prevents bypass/allow-all permission modes. `allow-auto-only` blocks full allow-all but permits advisory auto-approval. Any other value is accepted rather than failing the session, but is enforced as `disable`: the key is only present to restrict something, so a mode this runtime cannot interpret fails closed to the most restrictive one it knows. Omit the key entirely to impose no restriction. + */ + disableBypassPermissionsMode?: string; + /** + * 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. + */ + 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 * @@ -14551,7 +18235,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. */ @@ -14560,7 +18244,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; }; } /** @@ -14571,6 +18255,9 @@ export interface SessionModelList { */ /** @experimental */ export interface SessionModelPriceCategory { + /** + * CAPI model identifier. + */ id: string; priceCategory: ModelPickerPriceCategory; } @@ -14621,13 +18308,12 @@ 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. */ @@ -14662,7 +18348,7 @@ export interface SessionOpenOptions { */ 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`). + * 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). Conventional `.github/skills/` and `.github/agents/` definitions under each directory also join the session's project catalogs when their existing subsystem gates are enabled: added-root skills require both `enableConfigDiscovery` and effective `enableSkills`; added-root agents require `enableConfigDiscovery`. Supplying a directory therefore activates configuration from it and should be treated as a trust decision. 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 during session creation and cold resume and is not persisted, so a cold resume must re-supply the directories. */ additionalDirectories?: string[]; workingDirectoryContext?: SessionContext; @@ -14717,11 +18403,21 @@ export interface SessionOpenOptions { */ shellProcessFlags?: string[]; sandboxConfig?: SandboxConfig; + /** + * Origin of the sandbox choice. The runtime uses this only for internal telemetry provenance; managed policy is derived independently. + * + * @internal + */ + sandboxConfigSource?: SandboxConfigSource; /** * Whether interactive shell sessions are logged. */ 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. */ @@ -14730,6 +18426,21 @@ export interface SessionOpenOptions { * Additional directories to search for skills. */ skillDirectories?: string[]; + /** + * Whether skill loading is enabled. When omitted, an SDK skill provider enables skills by default. + */ + enableSkills?: boolean; + /** + * Whether the requesting SDK session has a skill provider. The provider remains ephemeral and is never persisted in session options or history. When enableSkills is false, it remains bound but dormant and receives no callbacks. Cloud, relay, handoff, and raw sessions.open flows reject it because they cannot safely pre-register the callback handler. + * + * @internal + * @experimental + */ + hasSkillProvider?: boolean; + /** + * Built-in skill names to include in this session. When specified, only these runtime-bundled skills are available. Skills from other sources with the same name remain available. + */ + includedBuiltinSkills?: string[]; /** * Skill IDs disabled for this session. */ @@ -14763,7 +18474,7 @@ export interface SessionOpenOptions { */ enableStreaming?: boolean; /** - * 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: enable native model citations for supported Anthropic and OpenAI models, normalized onto the `assistant.message` event. Off by default; may change or be removed while the citations surface is experimental. * * @experimental */ @@ -14849,6 +18560,7 @@ export interface ShellOptions { * and the built-in PowerShell shell uses `-NoProfile -NoLogo`. */ processFlags?: string[]; + credentials?: ShellCredentials; } /** * A host-provided script sourced before each built-in shell command when its shell target matches the active shell. @@ -14864,6 +18576,51 @@ export interface ShellInitScript { path: string; shell: ShellInitScriptShell; } +/** + * Command-scoped GitHub credential injection for the shell commands an agent runs. + * + * Each channel is opt-in and independent, and injection is scoped to the individual command + * spawn: the credential is resolved from the session's *current* authentication at every spawn + * and reaches only spawns whose script actually invokes `git` or `gh`. Because nothing is + * retained between spawns, replacing the session credential (`session.gitHubAuth.setCredentials`) + * changes what the next spawned command presents — which seeding a credential into the runtime + * process's own environment cannot do, since a child's environment is fixed at `exec`. + * + * The credential is matched to the host it authenticates to, so a github.com credential is never + * presented to a GitHub Enterprise host and vice versa. Where a channel cannot express that + * boundary it injects nothing rather than crossing it -- see `gh` below. + * + * This is independent of `sandboxConfig`: it is a decision about which identity the agent + * presents, not about what the agent may touch, and it works on every platform whether or not + * an OS sandboxing backend is available. `sandboxConfig.auth` remains the sandbox-scoped + * spelling and is additive with this one. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ShellCredentials". + */ +/** @experimental */ +export interface ShellCredentials { + /** + * Whether to authenticate the agent's `git` commands as the session's GitHub credential, by + * injecting an `http..extraheader` (plus `insteadOf` rewrites so SSH-spelled remotes for + * that host use the authenticated HTTPS transport). Applied only to a spawn that runs a + * remote-contacting `git` subcommand. Default: false (opt-in). + */ + git?: boolean; + /** + * Whether to authenticate the agent's `gh` commands as the session's GitHub credential, by + * exporting `GH_TOKEN` to a spawn that runs `gh`. Any inherited `gh` credential is removed from + * spawns that do not, so the credential stays command-scoped. + * + * Applies to a github.com credential only. `gh` picks its credential variable from the host a + * command targets rather than the one the credential belongs to, and the command can choose that + * target, so `GH_ENTERPRISE_TOKEN` would offer a single-tenant enterprise credential to every + * other enterprise host. A session whose credential is enterprise-scoped therefore runs `gh` + * unauthenticated; its `git` commands are unaffected, because `http..extraheader` is scoped + * to one host by construction. Default: false (opt-in). + */ + gh?: boolean; +} /** * Content-exclusion policy supplied to `sessions.open` options, with rules, last-updated data, and scope. * @@ -14872,8 +18629,14 @@ export interface ShellInitScript { */ /** @experimental */ export interface SessionOpenOptionsAdditionalContentExclusionPolicy { + /** + * Content-exclusion rules to apply. + */ rules: SessionOpenOptionsAdditionalContentExclusionPolicyRule[]; - last_updated_at: unknown; + /** + * Opaque policy update timestamp supplied by the host. + */ + last_updated_at: JsonValue; scope: SessionOpenOptionsAdditionalContentExclusionPolicyScope; } /** @@ -14884,8 +18647,17 @@ export interface SessionOpenOptionsAdditionalContentExclusionPolicy { */ /** @experimental */ export interface SessionOpenOptionsAdditionalContentExclusionPolicyRule { + /** + * Path patterns covered by this rule. + */ paths: string[]; + /** + * Conditions of which at least one must match. + */ ifAnyMatch?: string[]; + /** + * Conditions none of which may match. + */ ifNoneMatch?: string[]; source: SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource; } @@ -14897,7 +18669,13 @@ export interface SessionOpenOptionsAdditionalContentExclusionPolicyRule { */ /** @experimental */ export interface SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource { + /** + * Name of the policy source. + */ name: string; + /** + * Type of the policy source. + */ type: string; } /** @@ -15018,13 +18796,11 @@ export interface SessionsOpenCloud { owner?: string; options?: SessionOpenOptions; /** - * 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. + * In-process callback invoked when the cloud task is created, before connection. Internal because function references cannot cross the JSON-RPC boundary. * * @internal */ - onTaskCreated?: { - [k: string]: unknown | undefined; - }; + onTaskCreated?: OpaqueInProcessValue; } /** * Parameters for fetching a remote session and handing it off to a new local session. @@ -15046,17 +18822,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. @@ -15078,9 +18850,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. */ @@ -15237,7 +19007,29 @@ export interface SessionsEnrichMetadataRequest { */ /** @experimental */ export interface SessionSetCredentialsParams { - credentials?: AuthInfo; + credentials?: SettableAuthInfo; +} +/** + * Token authentication accepted by session.gitHubAuth.setCredentials. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SettableTokenAuthInfo". + */ +/** @experimental */ +export interface SettableTokenAuthInfo { + /** + * SDK-side token authentication; the host configured the token directly via the SDK. + */ + type: "token"; + /** + * Authentication host. + */ + host: string; + /** + * The token value itself. Treat as a secret. + */ + token: string; + copilotUser?: CopilotUserResponse; } /** * Indicates whether the credential update succeeded. @@ -15264,7 +19056,13 @@ export interface SessionSetCredentialsResult { */ /** @experimental */ export interface SessionSettingsBuiltInToolAvailabilitySnapshot { + /** + * Whether the report-progress tool is available. + */ reportProgress?: boolean; + /** + * Whether the create-pull-request tool is available. + */ createPullRequest?: boolean; } /** @@ -15289,6 +19087,9 @@ export interface SessionSettingsEvaluatePredicateRequest { */ /** @experimental */ export interface SessionSettingsEvaluatePredicateResult { + /** + * Whether the named settings predicate evaluated to enabled. + */ enabled: boolean; } /** @@ -15299,7 +19100,13 @@ export interface SessionSettingsEvaluatePredicateResult { */ /** @experimental */ export interface SessionSettingsJobSnapshot { + /** + * GitHub Actions event type for the job. + */ eventType?: string; + /** + * Whether this is the workflow's trigger job. + */ isTriggerJob?: boolean; builtInToolAvailability?: SessionSettingsBuiltInToolAvailabilitySnapshot; } @@ -15311,9 +19118,21 @@ export interface SessionSettingsJobSnapshot { */ /** @experimental */ export interface SessionSettingsModelSnapshot { + /** + * Selected model identifier. + */ model?: string; + /** + * Default reasoning effort for the selected model. + */ defaultReasoningEffort?: string; + /** + * Agent job identifier for the session. + */ instanceId?: string; + /** + * Agent service callback URL for job and progress updates. + */ callbackUrl?: string; } /** @@ -15324,7 +19143,13 @@ export interface SessionSettingsModelSnapshot { */ /** @experimental */ export interface SessionSettingsOnlineEvaluationSnapshot { + /** + * Whether online evaluation is disabled. + */ disableOnlineEvaluation?: boolean; + /** + * Whether online-evaluation output-file generation is enabled. + */ enableOnlineEvaluationOutputFile?: boolean; } /** @@ -15335,17 +19160,53 @@ export interface SessionSettingsOnlineEvaluationSnapshot { */ /** @experimental */ export interface SessionSettingsRepoSnapshot { + /** + * Repository name. + */ name?: string; + /** + * GitHub repository database ID. + */ id?: number; + /** + * Checked-out repository branch. + */ branch?: string; + /** + * Checked-out commit SHA. + */ commit?: string; + /** + * Whether the repository is writable. + */ readWrite?: boolean; + /** + * Repository owner login. + */ ownerName?: string; + /** + * GitHub repository owner database ID. + */ ownerId?: number; + /** + * GitHub server base URL. + */ serverUrl?: string; + /** + * GitHub server host name. + */ host?: string; + /** + * Protocol used to access the GitHub host. + */ hostProtocol?: string; + /** + * GitHub secret-scanning service URL. + */ secretScanningUrl?: string; + /** + * Number of commits in the pull request. + */ prCommitCount?: number; } /** @@ -15356,9 +19217,21 @@ export interface SessionSettingsRepoSnapshot { */ /** @experimental */ export interface SessionSettingsSnapshot { + /** + * Agent runtime version selector copied from the session settings, such as `latest` or a runtime release identifier. + */ version?: string; + /** + * Name of the SDK client that created the session. + */ clientName?: string; + /** + * Session timeout in milliseconds. + */ timeoutMs?: number; + /** + * Session start time as Unix epoch milliseconds. + */ startTimeMs?: number; repo: SessionSettingsRepoSnapshot; model: SessionSettingsModelSnapshot; @@ -15374,14 +19247,41 @@ export interface SessionSettingsSnapshot { */ /** @experimental */ export interface SessionSettingsValidationSnapshot { + /** + * General validation timeout budget in seconds. + */ timeout?: number; + /** + * Dependabot validation timeout budget in seconds. + */ dependabotTimeout?: number; + /** + * Whether CodeQL validation is enabled. + */ codeqlEnabled?: boolean; + /** + * Whether code-review validation is enabled. + */ codeReviewEnabled?: boolean; + /** + * Model used for code-review validation. + */ codeReviewModel?: string; + /** + * Whether advisory validation is enabled. + */ advisoryEnabled?: boolean; + /** + * Whether secret-scanning validation is enabled. + */ secretScanningEnabled?: boolean; + /** + * Whether the memory-store tool is enabled. + */ memoryStoreEnabled?: boolean; + /** + * Whether the memory-vote tool is enabled. + */ memoryVoteEnabled?: boolean; } /** @@ -15700,6 +19600,28 @@ export interface SessionsPruneOldRequest { */ excludeSessionIds?: string[]; } +/** + * Pagination options for reading an inactive or active local session's persisted event journal. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionsReadPersistedEventsRequest". + */ +/** @experimental */ +export interface SessionsReadPersistedEventsRequest { + /** + * Session ID whose persisted event journal should be read. + */ + sessionId: string; + /** + * Opaque cursor returned by a previous persisted-event read. Omit on the first call. + */ + cursor?: string; + /** + * Maximum number of events to return in this batch (1–1000, default 200). + */ + max?: number; + direction?: EventsReadDirection; +} /** * Session ID whose in-use lock should be released. * @@ -15936,6 +19858,12 @@ export interface SessionUpdateOptionsParams { */ shellProcessFlags?: string[]; sandboxConfig?: SandboxConfig; + /** + * Origin of the sandbox choice. The runtime uses this only for internal telemetry provenance; managed policy is derived independently. + * + * @internal + */ + sandboxConfigSource?: SandboxConfigSource; /** * Whether interactive shell sessions are logged. */ @@ -15949,6 +19877,10 @@ export interface SessionUpdateOptionsParams { * Additional directories to search for skills. */ skillDirectories?: string[]; + /** + * Built-in skill names to include in this session. When specified, only these runtime-bundled skills are available. Skills from other sources with the same name remain available. Set to null to remove the allowlist restriction. + */ + includedBuiltinSkills?: string[] | null; /** * Skill IDs that should be excluded from this session. */ @@ -16060,7 +19992,7 @@ export interface SessionUpdateOptionsParams { */ enableSessionStore?: boolean; /** - * Whether to enable skill directory scanning and loading. Falls back to enableConfigDiscovery when unset. + * Whether skill loading is enabled. Explicit false disables every source, including a bound SDK provider; changing the value invalidates the loaded skill snapshot. When omitted, creation falls back to enableConfigDiscovery unless an SDK skill provider is registered. */ enableSkills?: boolean; contextTier?: OptionsUpdateContextTier; @@ -16203,6 +20135,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 */ @@ -16265,17 +20201,94 @@ export interface SkillDiscoveryPathList { paths: SkillDiscoveryPath[]; } /** - * Skills available to the session, with their enabled state. + * Skills available to the session, with their enabled state. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SkillList". + */ +/** @experimental */ +export interface SkillList { + /** + * Available skills + */ + skills: Skill[]; +} +/** + * Catalog-only metadata for one SDK-provided skill. The complete SKILL.md is fetched separately and lazily. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SkillProviderDescriptor". + */ +/** @experimental */ +export interface SkillProviderDescriptor { + /** + * Invocation and display name. + */ + name: string; + /** + * Description used in skill catalogs without fetching content. + */ + description: string; + /** + * Whether users may invoke the skill directly. Defaults to true. + */ + userInvocable?: boolean; + /** + * Whether model invocation is disabled. Defaults to false. + */ + disableModelInvocation?: boolean; + /** + * Optional freeform argument hint used by slash-command catalogs. + */ + argumentHint?: string; +} +/** + * Catalog metadata returned by an SDK session's skill provider. Catalogs are limited to 1024 descriptors and 1 MiB of aggregate metadata. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SkillProviderListResult". + */ +/** @experimental */ +/** @internal */ +export interface SkillProviderListResult { + /** + * Skill descriptors in provider order. Invocation names must be unique under case-insensitive comparison. + * + * @maxItems 1024 + */ + skills: SkillProviderDescriptor[]; +} +/** + * Identifies one SDK-provided skill by invocation name. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SkillProviderReadRequest". + */ +/** @experimental */ +/** @internal */ +export interface SkillProviderReadRequest { + /** + * Target session identifier + */ + sessionId: string; + /** + * Invocation name of the skill to read. + */ + name: string; +} +/** + * Complete text-only SKILL.md content returned by an SDK session's skill provider. Related files and assets are not supported. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "SkillList". + * via the `definition` "SkillProviderReadResult". */ /** @experimental */ -export interface SkillList { +/** @internal */ +export interface SkillProviderReadResult { /** - * Available skills + * Complete SKILL.md text. The runtime enforces a 1 MiB UTF-8 byte limit. */ - skills: Skill[]; + markdown: string; } /** * Skill names to mark as disabled in global configuration, replacing any previous list. @@ -16290,6 +20303,23 @@ export interface SkillsConfigSetDisabledSkillsRequest { */ disabledSkills: string[]; } +/** + * Adds or removes a single skill from the global disabled list, leaving every other entry untouched. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SkillsConfigSetSkillDisabledRequest". + */ +/** @experimental */ +export interface SkillsConfigSetSkillDisabledRequest { + /** + * Name of the skill to add to or remove from the disabled list + */ + name: string; + /** + * True to disable the skill, false to enable it + */ + disabled: boolean; +} /** * Name of the skill to disable for the session. * @@ -16380,7 +20410,7 @@ export interface SkillsInvokedSkill { */ name: string; /** - * Path to the SKILL.md file + * Path to the SKILL.md file, or an empty string for an SDK-provided skill without a filesystem identity */ path: string; /** @@ -16391,6 +20421,10 @@ export interface SkillsInvokedSkill { * Tools that should be auto-approved when this skill is active, captured at invocation time */ allowedTools?: string[]; + /** + * Whether model invocation was disabled when this skill was invoked + */ + disableModelInvocation?: boolean; /** * Turn number when the skill was invoked */ @@ -16413,6 +20447,39 @@ export interface SkillsLoadDiagnostics { */ errors: string[]; } + +/** @experimental */ +export interface SlashCommandAddTimelineEntryResult { + /** + * Discriminator for an add-timeline-entry result. + */ + kind: "add-timeline-entry"; + entry: SlashCommandTimelineEntry; + /** + * Optional text the host should prefill into the input editor. + */ + prefillInput?: string; + /** + * Whether command execution changed persisted runtime settings. + */ + runtimeSettingsChanged?: boolean; +} + +/** @experimental */ +export interface SlashCommandTimelineEntry { + /** + * Timeline entry presentation type. + */ + type: string; + /** + * Text displayed for the timeline entry. + */ + text: string; + /** + * Optional URL associated with the timeline entry. + */ + url?: string; +} /** * Slash-command invocation result that submits an agent prompt, with display prompt, optional mode, optional user-facing notice, and settings-change flag. * @@ -16459,6 +20526,7 @@ export interface SlashCommandCompletedResult { * Optional user-facing message describing the completed command */ message?: string; + mode?: SessionMode; /** * True when the invocation mutated user runtime settings; consumers caching settings should refresh */ @@ -16543,6 +20611,95 @@ export interface SlashCommandSelectSubcommandOption { */ group?: string; } + +/** @experimental */ +export interface SlashCommandShowDialogResult { + /** + * Discriminator for a show-dialog result. + */ + kind: "show-dialog"; + dialog: SlashCommandModelPickerDialog; + /** + * Whether command execution changed persisted runtime settings. + */ + runtimeSettingsChanged?: boolean; +} + +/** @experimental */ +export interface SlashCommandModelPickerDialog { + /** + * Discriminator for a model-picker dialog. + */ + kind: "model-picker"; + /** + * Model that should be enabled before it can be selected. + */ + modelToEnable?: string; + /** + * Settings scope the picker should modify. + */ + scope?: string; + /** + * Model-selection target represented by the picker. + */ + target?: string; +} + +/** @experimental */ +export interface SlashCommandSetModelResult { + /** + * Discriminator for a set-model result. + */ + kind: "set-model"; + /** + * Model selected by the command. + */ + model: string; + /** + * Settings scope modified by the command. + */ + scope?: string; + /** + * User-facing warning produced while selecting the model. + */ + warning?: string; + /** + * Reasoning effort selected for the model. + */ + reasoningEffort?: string; + /** + * User-settings snapshot to restore if the host cancels the model switch. + */ + revertOnCancel?: {}; + /** + * Repository settings scope modified by the command. + */ + repoScope?: string; + /** + * Whether command execution changed persisted runtime settings. + */ + runtimeSettingsChanged?: boolean; +} + +/** @experimental */ +export interface SlashCommandSetPlanModelResult { + /** + * Discriminator for a set-plan-model result. + */ + kind: "set-plan-model"; + /** + * Dedicated model selected for plan mode. + */ + planModel?: string; + /** + * User-facing confirmation message for the plan-model selection. + */ + message: string; + /** + * Whether command execution changed persisted runtime settings. + */ + runtimeSettingsChanged?: boolean; +} /** * Subagent model, reasoning effort, and context tier settings * @@ -16555,6 +20712,7 @@ export interface SubagentSettingsEntry { * Model override for matching subagents */ model?: string; + modelPolicy?: AgentModelPolicy; /** * Reasoning effort override for matching subagents */ @@ -16581,6 +20739,10 @@ export interface TaskAgentInfo { * Tool call ID associated with this agent task */ toolCallId: string; + /** + * Friendly, non-unique name intended for display + */ + displayName?: string; /** * Short description of the task */ @@ -16678,6 +20840,35 @@ export interface TaskProgressLine { */ timestamp: string; } + +/** @experimental */ +export interface TaskCompletionDecision { + outcome: TaskCompletionOutcome; + /** + * Rationale for the completion decision, when one is available. + */ + reason?: string; + /** + * Whether the rationale was derived from completion-reviewer output. + */ + reviewerDerived?: boolean; + /** + * Information-flow metadata captured from the completion reviewer. + */ + reviewerResultMeta?: JsonValue; + /** + * Active autopilot objective evaluated by the completion reviewer. + */ + objectiveId?: number; + /** + * Whether completion was accepted after the reviewer-rejection budget was exhausted. + */ + completionRejectionBudgetExhausted?: boolean; + /** + * Objective eligibility token captured when the decision was evaluated. + */ + completionEligibilityToken?: number; +} /** * Tracked shell task metadata, including ID, command, status, timing, attachment/execution mode, log path, and PID. * @@ -16947,7 +21138,7 @@ export interface TasksStartAgentRequest { */ prompt: string; /** - * Short name for the agent, used to generate a human-readable ID + * Friendly, non-unique name used when displaying the agent */ name: string; /** @@ -17019,7 +21210,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 @@ -17039,6 +21230,201 @@ export interface ToolList { */ tools: Tool[]; } +/** + * Expanded canonical result returned by a session tool. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ToolResultExpanded". + */ +/** @experimental */ +export interface ToolResultExpanded { + /** + * Text result returned to the model. + */ + textResultForLlm: string; + resultType: ToolResultType; + /** + * Base64-encoded binary results returned to the model. + */ + binaryResultsForLlm?: ExternalToolTextResultForLlmBinaryResultsForLlm[]; + /** + * Detailed log content available for session display. + */ + sessionLog?: string; + /** + * Error message for an unsuccessful execution. + */ + error?: string; + /** + * Tool-specific telemetry payload. + */ + toolTelemetry?: JsonValue; + /** + * Whether large-output post-processing should be skipped. + */ + skipLargeOutputProcessing?: boolean; + /** + * Messages to inject after the tool result. + */ + newMessages?: ToolResultNewMessage[]; + /** + * Structured content blocks returned to the model. + */ + contents?: ExternalToolTextResultForLlmContent[]; + /** + * Deferred tool names made available by this result. + */ + toolReferences?: string[]; + /** + * Sources returned by the tool that the model may cite. + */ + citableSources?: JsonValue[]; + /** + * Skill invocation metadata produced by the tool. + */ + skillInvocation?: JsonValue; + /** + * Whether post-tool-use failure hooks have already processed this result. + */ + postToolUseFailureHooksProcessed?: boolean; + /** + * Optional UI resource produced by the tool. + */ + uiResource?: JsonValue; + /** + * Metadata propagated with the tool result, including information-flow labels. + */ + mcpMeta?: { + [k: string]: JsonValue | undefined; + }; + /** + * Structured result content in addition to the model-facing text. + */ + structuredContent?: JsonValue; + taskCompletionDecision?: TaskCompletionDecision; +} +/** + * A message injected by a tool result. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ToolResultNewMessage". + */ +/** @experimental */ +export interface ToolResultNewMessage { + /** + * Message content to inject after the tool result. + */ + content: string; + /** + * Source attributed to the injected message. + */ + source: string; +} +/** + * A tool name and arguments to execute through the session's native invocation pipeline. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ToolsExecuteRequest". + */ +/** @experimental */ +export interface ToolsExecuteRequest { + /** + * Name of the currently offered tool to execute. + */ + name: string; + /** + * Arguments supplied to the tool. + */ + arguments: JsonValue; + /** + * Optional identifier used to correlate this invocation with its tool call. + */ + toolCallId?: string; +} +/** + * Options controlling how Rust-owned built-in tool descriptors are materialized. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ToolsGetBuiltinDescriptorsRequest". + */ +/** @experimental */ +export interface ToolsGetBuiltinDescriptorsRequest { + /** + * Whether descriptors should favor fewer user-intervention prompts. + */ + reduceUserIntervention?: boolean; + /** + * Whether tool descriptors should include authoring metadata. + */ + includeAuthor?: boolean; + /** + * Whether semantic skill lookup is available. + */ + skillEmbeddingEnabled?: boolean; + shellConfig?: ToolsShellDescriptorConfig; + /** + * Whether the configured shell supports PowerShell 7 syntax. + */ + shellSupportsPowerShell7Syntax?: boolean; + /** + * Default shell timeout in milliseconds. + */ + shellTimeoutMs?: number; + /** + * Whether background task completion notifications are enabled. + */ + backgroundTaskNotificationsEnabled?: boolean; +} +/** + * Shell-specific names and description lines used to materialize built-in shell tool descriptors. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ToolsShellDescriptorConfig". + */ +/** @experimental */ +export interface ToolsShellDescriptorConfig { + /** + * Stable shell type identifier. + */ + shellType: string; + /** + * Human-readable shell name. + */ + displayName: string; + /** + * Tool name used to start shell commands. + */ + shellToolName: string; + /** + * Tool name used to read shell output. + */ + readShellToolName: string; + /** + * Tool name used to stop shell commands. + */ + stopShellToolName: string; + /** + * Tool name used to list active shells. + */ + listShellsToolName: string; + /** + * Additional model-facing shell description lines. + */ + descriptionLines: string[]; +} +/** + * Rust-owned built-in tool descriptors for the session. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ToolsGetBuiltinDescriptorsResult". + */ +/** @experimental */ +export interface ToolsGetBuiltinDescriptorsResult { + /** + * Built-in tool descriptors materialized for the session. + */ + tools: BuiltinToolDescriptor[]; +} /** * Current lightweight tool metadata snapshot for the session. * @@ -17064,14 +21450,49 @@ export interface ToolsInitializeAndValidateResult {} * Optional model identifier whose tool overrides should be applied to the listing. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "ToolsListRequest". + * via the `definition` "ToolsListRequest". + */ +/** @experimental */ +export interface ToolsListRequest { + /** + * Optional model ID — when provided, the returned tool list reflects model-specific overrides + */ + model?: string; +} +/** + * Complete externally implemented tool list for the calling connection. An empty list removes every tool previously supplied by that connection. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ToolsSetRequest". + */ +/** @experimental */ +export interface ToolsSetRequest { + /** + * Complete replacement list for the calling connection. + */ + tools: ProtocolExternalToolDefinition[]; +} +/** + * Empty result after replacing the calling connection's externally implemented tools. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ToolsSetResult". + */ +/** @experimental */ +export interface ToolsSetResult {} +/** + * Task-completion tool arguments and final result used to build a label-safe session event payload. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ToolsTaskCompleteEventDataRequest". */ /** @experimental */ -export interface ToolsListRequest { +export interface ToolsTaskCompleteEventDataRequest { /** - * Optional model ID — when provided, the returned tool list reflects model-specific overrides + * Arguments supplied to the completed task_complete tool call. */ - model?: string; + toolArgs: JsonValue; + finalResult: ToolResultExpanded; } /** * Empty result after applying subagent settings @@ -17204,11 +21625,20 @@ export interface UIElicitationArrayEnumFieldItems { */ /** @experimental */ export interface UIElicitationRequest { + mode?: McpElicitationFormMode; /** * Message describing what information is needed from the user */ message: string; requestedSchema: UIElicitationSchema; + /** + * MCP request metadata. + */ + _meta?: { + [k: string]: unknown | undefined; + }; + task?: McpTaskMetadata; + [k: string]: unknown | undefined; } /** * JSON Schema describing the form fields to present to the user @@ -17411,6 +21841,13 @@ export interface UIElicitationSchemaPropertyNumber { export interface UIElicitationResponse { action: UIElicitationResponseAction; content?: UIElicitationResponseContent; + /** + * MCP response metadata. + */ + _meta?: { + [k: string]: unknown | undefined; + }; + [k: string]: unknown | undefined; } /** * The form values submitted by the user (present when action is 'accept') @@ -17448,24 +21885,20 @@ export interface UIEphemeralQueryRequest { */ question: string; /** - * 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 streaming callback `(text) => void` invoked with each token as the model emits it. Internal and excluded from the public SDK surface. * * @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. + * In-process `AbortSignal` forwarded to the model client to cancel an in-flight request. Internal and excluded from the public SDK surface. * * @internal */ - abortSignal?: { - [k: string]: unknown | undefined; - }; + abortSignal?: OpaqueInProcessValue; } /** - * Transient answer generated from current conversation context. + * Completed transient query. Ordered chunks and the terminal outcome are also delivered through `ui.ephemeral_query` session events while it runs. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "UIEphemeralQueryResult". @@ -17473,7 +21906,7 @@ export interface UIEphemeralQueryRequest { /** @experimental */ export interface UIEphemeralQueryResult { /** - * Full assistant response text. + * Answer returned by the model */ answer: string; } @@ -17738,6 +22171,12 @@ export interface UsageGetMetricsResult { modelMetrics: { [k: string]: UsageMetricsModelMetric | undefined; }; + /** + * Per-agent usage metrics, keyed by agent instance identifier. The main conversation uses the stable key `main`. + */ + agentMetrics?: { + [k: string]: UsageMetricsAgentMetric | undefined; + }; /** * Currently active model identifier */ @@ -17873,6 +22312,37 @@ export interface UsageMetricsModelMetricTokenDetail { */ tokenCount: number; } +/** + * Usage attributed to one agent instance, including its identity, API duration, AI units, and per-model breakdown. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UsageMetricsAgentMetric". + */ +/** @experimental */ +export interface UsageMetricsAgentMetric { + /** + * Configured agent name, when this is a subagent + */ + agentName?: string; + /** + * Human-readable label for this subagent invocation, copied from the originating `subagent.started` event. For task-tool subagents this is the invocation's task description rather than the agent's configured display name, so group by `agentName` for stable per-agent labels. + */ + agentDisplayName?: string; + /** + * Time spent in model API calls by this agent, in milliseconds + */ + totalApiDurationMs: number; + /** + * Accumulated nano-AI units cost for this agent + */ + totalNanoAiu: number; + /** + * Per-model usage for this agent, keyed by model identifier + */ + modelMetrics: { + [k: string]: UsageMetricsModelMetric | undefined; + }; +} /** * Result of a user-requested shell command. * @@ -17913,15 +22383,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. */ @@ -17953,9 +22419,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. @@ -18091,7 +22555,13 @@ export interface WorkspacesAddSummaryRequest { */ /** @experimental */ export interface WorkspacesAddSummaryResult { + /** + * Metadata for the persisted summary. + */ summary?: {}; + /** + * Refreshed metadata for the containing workspace. + */ workspace?: {}; [k: string]: unknown | undefined; } @@ -18184,9 +22654,7 @@ export interface WorkspacesEnsureRequest { /** * Opaque workspace context supplied by the session host. */ - context?: { - [k: string]: unknown | undefined; - }; + context?: JsonValue; } /** * Current workspace metadata for the session, including its absolute filesystem path when available. @@ -18200,22 +22668,70 @@ export interface WorkspacesGetWorkspaceResult { * Current workspace metadata, or null if not available */ workspace: { + /** + * Stable workspace identifier. + */ id: string; + /** + * Current working directory associated with the workspace. + */ cwd?: string; + /** + * Git repository root associated with the workspace. + */ git_root?: string; + /** + * Repository identifier associated with the workspace. + */ repository?: string; host_type?: WorkspacesWorkspaceDetailsHostType; + /** + * Current Git branch. + */ branch?: string; + /** + * Workspace display name. + */ name?: string; + /** + * Name of the client that created the workspace. + */ client_name?: string; + /** + * Whether the workspace name was explicitly chosen by the user. + */ user_named?: boolean; + /** + * Number of persisted summaries in the workspace. + */ summary_count?: number; + /** + * Timestamp when the workspace was created. + */ created_at?: string; + /** + * Timestamp when the workspace was last updated. + */ updated_at?: string; + /** + * Whether the workspace session can be steered remotely. + */ remote_steerable?: boolean; + /** + * Mission Control task identifier associated with the workspace. + */ mc_task_id?: string; + /** + * Mission Control session identifier associated with the workspace. + */ mc_session_id?: string; + /** + * Most recent Mission Control event identifier observed for the workspace. + */ mc_last_event_id?: string; + /** + * Whether the per-session Chronicle upgrade prompt was dismissed for the workspace. + */ chronicle_sync_dismissed?: boolean; } | null; /** @@ -18377,9 +22893,7 @@ export interface WorkspacesUpdateMetadataRequest { /** * Opaque workspace context supplied by the session host. */ - context?: { - [k: string]: unknown | undefined; - }; + context?: JsonValue; /** * Optional workspace display name override. */ @@ -18439,7 +22953,7 @@ export interface SessionAgentListRequest { */ /** @experimental */ export interface SessionMcpAppsCallToolResult { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; } /** @experimental */ @@ -18517,6 +23031,19 @@ export interface SessionLimitPredictionPredictRequest { modelId?: string; clientType?: SessionLimitPredictionClientType; } +/** + * Identifies the target session. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SkillProviderListRequest". + */ +/** @experimental */ +export interface SkillProviderListRequest { + /** + * Target session identifier + */ + sessionId: string; +} /** * Identifies the target session. * @@ -18546,11 +23073,23 @@ export function createServerRpc(connection: MessageConnection) { ping: async (params: PingRequest): Promise => connection.sendRequest("ping", params), /** @experimental */ + hooks: { + /** + * Discovers hook actions enabled under server-side discovery settings from user, repository, plugin, and managed-policy sources. + * + * @param params Optional project paths and host-exclusion behavior for server-scoped hook discovery. + * + * @returns Server-discovered hook actions and partial-load diagnostics from user, repository, plugin, and managed-policy sources. Concrete sessions may include additional session-specific hook sources. + */ + discover: async (params: HooksDiscoverRequest): Promise => + connection.sendRequest("hooks.discover", params), + }, + /** @experimental */ models: { /** * Lists Copilot models available to the authenticated user. * - * @param params Optional GitHub token used to list models for a specific user instead of the global auth context. + * @param params Optional opaque account selection or compatibility GitHub token used to list models. * * @returns List of Copilot models available to the resolved user, including capabilities and billing metadata. */ @@ -18579,9 +23118,9 @@ export function createServerRpc(connection: MessageConnection) { /** @experimental */ account: { /** - * Gets Copilot quota usage for the authenticated user or supplied GitHub token. + * Gets Copilot quota usage for the current or opaquely selected authenticated user. * - * @param params Optional GitHub token used to look up quota for a specific user instead of the global auth context. + * @param params Optional opaque account selection or compatibility GitHub token used to look up quota. * * @returns Quota usage snapshots for the resolved user, keyed by quota type. */ @@ -18602,9 +23141,9 @@ export function createServerRpc(connection: MessageConnection) { getAllUsers: async (): Promise => connection.sendRequest("account.getAllUsers", {}), /** - * Stores authentication credentials after successful login (e.g., device code flow). + * Validates and stores authentication credentials. When login is omitted, resolves the authenticated user from the token before persistence. * - * @param params Credentials to store after successful authentication + * @param params Credentials to validate and store. Omit login to resolve the authenticated user from the token. * * @returns Result of a successful login; throws on failure */ @@ -18693,6 +23232,15 @@ export function createServerRpc(connection: MessageConnection) { */ discover: async (params: McpDiscoverRequest): Promise => connection.sendRequest("mcp.discover", params), + /** + * Requests a side-effect-free MCP install plan from a catalog candidate handle or a caller-supplied card. This host-implemented server method is available through SDK/TUI hosts; standalone and C-ABI runtimes whose host does not implement server-method dispatch return JSON-RPC MethodNotFound. A runtime with planning available returns a normalised plan and opaque single-use plan handle; a runtime without it returns the typed planning-unavailable result. A completed plan reports resource identity, provenance, eligible transport choices, the user-scope target, required typed values and secret placeholders, the policy result, the configuration changes installing would make, and whether a reload would be needed. Planning never writes configuration, stores a secret, or reloads MCP servers, so abandoning a plan needs no call and leaves nothing behind. + * + * @param params A side-effect-free request for an MCP install plan. Computing a plan never writes configuration, stores a secret, or reloads MCP servers. + * + * @returns Outcome of an mcp.planInstall call: either a normalised plan, or one typed refusal. Nothing is written in either case. + */ + planInstall: async (params: McpPlanInstallRequest): Promise => + connection.sendRequest("mcp.planInstall", params), }, /** @experimental */ extensions: { @@ -18718,6 +23266,25 @@ export function createServerRpc(connection: MessageConnection) { 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 uses its built-in extension launcher. + * + * @experimental + */ + registerExtensionLaunchProvider: async (): Promise => + connection.sendRequest("registerExtensionLaunchProvider", {}), + /** @experimental */ + catalog: { + /** + * Requests a bounded catalog search. This host-implemented server method is available through SDK/TUI hosts; standalone and C-ABI runtimes whose host does not implement server-method dispatch return JSON-RPC MethodNotFound. A runtime with search available returns inert candidate summaries, each with an opaque single-use handle scoped to this runtime instance; a runtime without it returns the typed search-unavailable result. Public authorities may be searched anonymously, while an authority that requires credentials yields the typed authentication-required result. All returned text, URLs, and package metadata are untrusted external data and can never trigger instructions, tools, or installation. Read-only: nothing is installed, configured, or persisted. + * + * @param params A bounded catalog search. Both the query length and the result count are capped by the schema so a caller cannot request an unbounded scan. + * + * @returns Outcome of a catalog.search call: either bounded inert candidates, or one typed refusal. Never a partial success. + */ + search: async (params: CatalogSearchRequest): Promise => + connection.sendRequest("catalog.search", params), + }, /** @experimental */ plugins: { /** @@ -18774,6 +23341,16 @@ export function createServerRpc(connection: MessageConnection) { disable: async (params: PluginsDisableRequest): Promise => connection.sendRequest("plugins.disable", params), /** @experimental */ + builtin: { + /** + * Replaces this server's trusted built-in plugin directories while no sessions are active. + * + * @param params Trusted built-in plugin directories to use for this runtime process. + */ + set: async (params: PluginsBuiltinSetRequest): Promise => + connection.sendRequest("plugins.builtin.set", params), + }, + /** @experimental */ marketplaces: { /** * Lists all registered marketplaces (defaults + user-added). @@ -18831,6 +23408,13 @@ export function createServerRpc(connection: MessageConnection) { */ setDisabledSkills: async (params: SkillsConfigSetDisabledSkillsRequest): Promise => connection.sendRequest("skills.config.setDisabledSkills", params), + /** + * Atomically adds or removes one skill from the disabled list. + * + * @param params Adds or removes a single skill from the global disabled list, leaving every other entry untouched. + */ + setSkillDisabled: async (params: SkillsConfigSetSkillDisabledRequest): Promise => + connection.sendRequest("skills.config.setSkillDisabled", params), }, /** * Discovers skills across global and project sources. @@ -18931,6 +23515,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. @@ -19016,6 +23610,15 @@ export function createServerRpc(connection: MessageConnection) { */ list: async (params: SessionsListRequest): Promise => connection.sendRequest("sessions.list", params), + /** + * Reads a page of durable events directly from a local session's persisted journal without creating, resuming, or activating the session. The initial backward read uses a bounded tail scan for fast first paint; cursor continuations preserve the session event-log paging semantics. Persisted events may omit payloads that are reconstructed only for an active session. + * + * @param params Pagination options for reading an inactive or active local session's persisted event journal. + * + * @returns Batch of session events returned by a read, with cursor and continuation metadata. + */ + readPersistedEvents: async (params: SessionsReadPersistedEventsRequest): Promise => + connection.sendRequest("sessions.readPersistedEvents", params), /** * Finds the local session bound to a GitHub task ID, if any. * @@ -19209,7 +23812,7 @@ export function createInternalServerRpc(connection: MessageConnection) { /** * 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. * - * @param params Parameters for the `server.connect` handshake: an optional connection token and optional connection-level opt-ins (e.g. GitHub telemetry forwarding). + * @param params Connection-level opt-ins for the `server.connect` handshake. Transport authentication is consumed by the native protocol boundary before dispatch. * * @returns Handshake result reporting the server's protocol version and package version on success. * @@ -19323,6 +23926,16 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin */ sendMessages: async (params: SendMessagesRequest): Promise => connection.sendRequest("session.sendMessages", { sessionId, ...params }), + /** @experimental */ + sandbox: { + /** + * Returns whether managed policy requires sandbox enforcement and whether an enforcement failure has permanently blocked the session. + * + * @returns Managed sandbox enforcement state for a session. + */ + getEnforcementStatus: async (): Promise => + connection.sendRequest("session.sandbox.getEnforcementStatus", { sessionId }), + }, /** * Aborts the current agent turn. * @@ -19471,10 +24084,12 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin /** * Lists durable factory runs for this session in creation order. * - * @returns Factory runs in durable creation order. + * @param params Parameters for paging factory runs. + * + * @returns A page of factory runs in durable creation order. */ - listRuns: async (): Promise => - connection.sendRequest("session.factory.listRuns", { sessionId }), + listRuns: async (params: FactoryListRunsRequest): Promise => + connection.sendRequest("session.factory.listRuns", { sessionId, ...params }), /** * Gets durable and live observability detail for one factory run. * @@ -19592,8 +24207,10 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin * Sets the current agent interaction mode. * * @param params Agent interaction mode to apply to the session. + * + * @returns Outcome of a session mode change, including any model switch it triggered and follow-up the host must perform. */ - set: async (params: ModeSetRequest): Promise => + set: async (params: ModeSetRequest): Promise => connection.sendRequest("session.mode.set", { sessionId, ...params }), }, /** @experimental */ @@ -20051,6 +24668,13 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin */ reload: async (): Promise => connection.sendRequest("session.mcp.reload", { sessionId }), + /** + * Releases any turns waiting on an in-flight MCP load without cancelling the load, letting the agent proceed while MCP servers finish connecting in the background. No-op when no MCP load is in flight or waiting turns were already released. + * + * @returns Result of moving in-flight MCP loading to the background. + */ + moveLoadingToBackground: async (): Promise => + connection.sendRequest("session.mcp.moveLoadingToBackground", { sessionId }), /** * Runs an MCP sampling inference on behalf of an MCP server. * @@ -20142,6 +24766,15 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin */ login: async (params: McpOauthLoginRequest): Promise => connection.sendRequest("session.mcp.oauth.login", { sessionId, ...params }), + /** + * Passively probes a configured remote MCP server to classify whether OAuth is required or a cached/override token is accepted. Does not start OAuth, emit pending OAuth requests, or mutate MCP connection state. + * + * @param params Remote MCP server name for a passive OAuth status probe. + * + * @returns Passive MCP OAuth probe result. `authenticated` means the server accepted the probe request while an OAuth-origin access token was attached; it does not prove the server required or independently validated that token. The probe does not make a second unauthenticated request. Failed is an expected probe-domain outcome; JSON-RPC errors are reserved for API-call failures. + */ + probe: async (params: McpOauthProbeRequest): Promise => + connection.sendRequest("session.mcp.oauth.probe", { sessionId, ...params }), /** * Responds to a pending MCP OAuth authorization request by its request id. * @@ -20346,6 +24979,33 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin }, /** @experimental */ tools: { + /** + * Executes one tool from the session's currently offered tool set through the native invocation pipeline. + * + * @param params A tool name and arguments to execute through the session's native invocation pipeline. + * + * @returns Canonical result returned by a session tool. + */ + execute: async (params: ToolsExecuteRequest): Promise => + connection.sendRequest("session.tools.execute", { sessionId, ...params }), + /** + * Returns the Rust-owned built-in tool descriptors used to construct the session's offered tool set. + * + * @param params Options controlling how Rust-owned built-in tool descriptors are materialized. + * + * @returns Rust-owned built-in tool descriptors for the session. + */ + getBuiltinDescriptors: async (params: ToolsGetBuiltinDescriptorsRequest): Promise => + connection.sendRequest("session.tools.getBuiltinDescriptors", { sessionId, ...params }), + /** + * Projects a completed task_complete tool call into its label-safe session event payload. + * + * @param params Task-completion tool arguments and final result used to build a label-safe session event payload. + * + * @returns Task completion notification with summary from the agent + */ + taskCompleteEventData: async (params: ToolsTaskCompleteEventDataRequest): Promise => + connection.sendRequest("session.tools.taskCompleteEventData", { sessionId, ...params }), /** * Provides the result for a pending external tool call. * @@ -20369,6 +25029,15 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin */ getCurrentMetadata: async (): Promise => connection.sendRequest("session.tools.getCurrentMetadata", { sessionId }), + /** + * Atomically replaces the complete externally implemented tool list supplied by the calling connection. Built-in, MCP/plugin, extension-discovered, subagent, and tools supplied by other connections remain unchanged. + * + * @param params Complete externally implemented tool list for the calling connection. An empty list removes every tool previously supplied by that connection. + * + * @returns Empty result after replacing the calling connection's externally implemented tools. + */ + set: async (params: ToolsSetRequest): Promise => + connection.sendRequest("session.tools.set", { sessionId, ...params }), /** * Updates the current session's live subagent settings after user settings change. The persisted user settings remain the source of truth for future sessions. * @@ -20460,7 +25129,7 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin * * @param params Transient question to answer without adding it to conversation history. * - * @returns Transient answer generated from current conversation context. + * @returns Completed transient query. Ordered chunks and the terminal outcome are also delivered through `ui.ephemeral_query` session events while it runs. */ ephemeralQuery: async (params: UIEphemeralQueryRequest): Promise => connection.sendRequest("session.ui.ephemeralQuery", { sessionId, ...params }), @@ -20581,21 +25250,21 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin setApproveAll: async (params: PermissionsSetApproveAllRequest): Promise => connection.sendRequest("session.permissions.setApproveAll", { sessionId, ...params }), /** - * 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. + * Sets the permission mode for the session. `manual` follows the normal approval flow, `assisted` attaches LLM safety recommendations, and `allow-all` automatically approves permission requests. The result returns the authoritative post-mutation mode so callers can update local state without racing the `session.permissions_changed` notification. * - * @param params Allow-all mode to apply for the session. + * @param params Permission mode to apply for the session. * - * @returns Indicates whether the operation succeeded and reports the post-mutation state. + * @returns Indicates whether the requested permission mode was applied and reports the authoritative post-mutation mode. */ - setAllowAll: async (params: PermissionsSetAllowAllRequest): Promise => - connection.sendRequest("session.permissions.setAllowAll", { sessionId, ...params }), + setMode: async (params: PermissionsSetModeRequest): Promise => + connection.sendRequest("session.permissions.setMode", { sessionId, ...params }), /** - * Returns the current allow-all permission mode for the session. + * Returns the current permission mode for the session. * - * @returns Current allow-all permission mode. + * @returns Current permission mode. */ - getAllowAll: async (): Promise => - connection.sendRequest("session.permissions.getAllowAll", { sessionId }), + getMode: async (): Promise => + connection.sendRequest("session.permissions.getMode", { sessionId }), /** * Adds or removes session-scoped or location-scoped permission rules. * @@ -20642,7 +25311,7 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin list: async (): Promise => connection.sendRequest("session.permissions.paths.list", { sessionId }), /** - * Adds a directory to the session's allow-list. + * Adds a directory to the session's allow-list and activates conventional skill and agent definitions under it. * * @param params Directory path to add to the session's allowed directories. * @@ -21182,6 +25851,122 @@ export function createInternalSessionRpc(connection: MessageConnection, sessionI sendSystemNotification: async (params: SendSystemNotificationRequest): Promise => connection.sendRequest("session.sendSystemNotification", { sessionId, ...params }), /** @experimental */ + gitHubAuth: { + /** + * Gets the current authentication information for internal session hosts. + * + * @returns Current authentication information, or null when no authentication is active. + */ + getCurrentAuthInfo: async (): Promise => + connection.sendRequest("session.gitHubAuth.getCurrentAuthInfo", { sessionId }), + /** + * Gets all authentication accounts available to the internal session host. + * + * @returns Authentication accounts available to the internal session host. + */ + getAllAuthAvailable: async (): Promise => + connection.sendRequest("session.gitHubAuth.getAllAuthAvailable", { sessionId }), + /** + * Refreshes Copilot account metadata for the current authentication. + * + * @returns Current authentication information, or null when no authentication is active. + */ + refreshCopilotUser: async (): Promise => + connection.sendRequest("session.gitHubAuth.refreshCopilotUser", { sessionId }), + /** + * Logs in a GitHub user through the internal session host. + * + * @param params Internal GitHub login parameters. + * + * @returns Authentication credentials accepted only at native protocol ingress. Runtime outputs use credential-free `AuthIdentity` metadata. + */ + login: async (params: SessionAuthLoginRequest): Promise => + connection.sendRequest("session.gitHubAuth.login", { sessionId, ...params }), + /** + * Switches the session to another available authentication. + * + * @param params Parameters for switching the session's active authentication. + */ + switchToAuth: async (params: SessionAuthSwitchRequest): Promise => + connection.sendRequest("session.gitHubAuth.switchToAuth", { sessionId, ...params }), + /** + * Logs out the session's current GitHub authentication. + * + * @returns Whether the current authentication was logged out. + */ + logout: async (): Promise => + connection.sendRequest("session.gitHubAuth.logout", { sessionId }), + /** + * Logs out a specific GitHub authentication. + * + * @param params Parameters identifying a GitHub authentication to log out. + * + * @returns Whether the requested authentication was logged out. + */ + logoutUser: async (params: SessionAuthLogoutUserRequest): Promise => + connection.sendRequest("session.gitHubAuth.logoutUser", { sessionId, ...params }), + /** + * Gets validation errors from the most recent authentication attempt. + * + * @returns Validation errors from the most recent authentication attempt. + */ + lastAuthErrors: async (): Promise => + connection.sendRequest("session.gitHubAuth.lastAuthErrors", { sessionId }), + }, + /** @experimental */ + canvas: { + /** @experimental */ + provider: { + /** + * Registers an internal canvas provider connection and its contributions. + * + * @param params Internal canvas provider registration parameters. + */ + register: async (params: CanvasProviderRegisterRequest): Promise => + connection.sendRequest("session.canvas.provider.register", { sessionId, ...params }), + /** + * Unregisters an internal canvas provider connection. + * + * @param params Internal canvas provider unregistration parameters. + */ + unregister: async (params: CanvasProviderUnregisterRequest): Promise => + connection.sendRequest("session.canvas.provider.unregister", { sessionId, ...params }), + }, + }, + /** @experimental */ + factory: { + /** + * Internal tool-originated factory invocation. + * + * @param params Internal parameters for invoking a registered factory from a tool. + * + * @returns Complete current or terminal factory run envelope. + */ + runFromTool: async (params: FactoryToolRunRequest): Promise => + connection.sendRequest("session.factory.runFromTool", { sessionId, ...params }), + /** + * Internal tool-originated factory resume. + * + * @param params Internal parameters for resuming a factory run from a tool. + * + * @returns Resolved persisted factory identity and resumed run envelope. + */ + resumeFromTool: async (params: FactoryToolResumeRequest): Promise => + connection.sendRequest("session.factory.resumeFromTool", { sessionId, ...params }), + }, + /** @experimental */ + model: { + /** + * Resolves and applies organization-managed and repository model overlays. + * + * @param params Managed, repository, and CLI model overrides to overlay onto the session at startup. + * + * @returns The model identifier active on the session after the switch. + */ + applyStartupOverlay: async (params: ModelApplyStartupOverlayRequest): Promise => + connection.sendRequest("session.model.applyStartupOverlay", { sessionId, ...params }), + }, + /** @experimental */ mcp: { /** * Reloads MCP server connections for the session with an explicit host-provided configuration. @@ -21195,7 +25980,7 @@ export function createInternalSessionRpc(connection: MessageConnection, sessionI /** * Configures the built-in GitHub MCP server for the session's current auth context. * - * @param params Opaque auth info used to configure GitHub MCP. + * @param params Credential-free authentication identity used to configure GitHub MCP. * * @returns Result of configuring GitHub MCP. */ @@ -21217,6 +26002,18 @@ export function createInternalSessionRpc(connection: MessageConnection, sessionI connection.sendRequest("session.mcp.unregisterExternalClient", { sessionId, ...params }), }, /** @experimental */ + commands: { + /** + * Finalizes persistence associated with a client-applied slash-command effect. + * + * @param params The pending slash-command invocation effect to finalize, plus whether the host applied or cancelled it. + * + * @returns Whether finalizing the invocation effect succeeded, and the failure reason when it did not. + */ + finalizeInvocationEffect: async (params: CommandsFinalizeInvocationEffectRequest): Promise => + connection.sendRequest("session.commands.finalizeInvocationEffect", { sessionId, ...params }), + }, + /** @experimental */ settings: { /** * Returns a redacted snapshot of session runtime settings, with secrets and raw feature flags excluded. Internal: the runtime settings shape is a runtime-internal surface and is deliberately kept out of the public SDK, because consumers should not depend on the runtime's internal settings layout. It remains callable in-process and is expected to be reworked as the runtime internals are consolidated. @@ -21646,6 +26443,19 @@ export function registerClientSessionApiHandlers( }); } +/** Handler for `extensionLaunchProvider` client global API methods. */ +/** @experimental */ +export interface ExtensionLaunchProviderHandler { + /** + * 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 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(params: ExtensionLaunchProviderResolveRequest): Promise; +} + /** Handler for `llmInference` client global API methods. */ /** @experimental */ export interface LlmInferenceHandler { @@ -21678,10 +26488,25 @@ export interface GitHubTelemetryHandler { event(params: GitHubTelemetryNotification): Promise; } +/** Handler for `gitHubToken` client global API methods. */ +/** @experimental */ +export interface GitHubTokenHandler { + /** + * Asks the SDK client to mint a GitHub access token for a session whose configuration supplied a GitHub token provider. The runtime acquires the initial token during bootstrap and refreshes it during expiry preflight when one hour or less remains. + * + * @param params Asks the SDK client to acquire a GitHub access token from an opaque callback registration. + * + * @returns SDK host response to a GitHub credential request. + */ + getToken(params: GitHubTokenAcquireRequest): Promise; +} + /** All client global API handler groups. */ export interface ClientGlobalApiHandlers { + extensionLaunchProvider?: ExtensionLaunchProviderHandler; llmInference?: LlmInferenceHandler; gitHubTelemetry?: GitHubTelemetryHandler; + gitHubToken?: GitHubTokenHandler; } /** @@ -21695,6 +26520,11 @@ export function registerClientGlobalApiHandlers( connection: MessageConnection, handlers: ClientGlobalApiHandlers, ): void { + 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; if (!handler) throw new Error("No llmInference client-global handler registered"); @@ -21710,4 +26540,9 @@ export function registerClientGlobalApiHandlers( if (!handler) return; await handler.event(params); }); + connection.onRequest("gitHubToken.getToken", async (params: GitHubTokenAcquireRequest) => { + const handler = handlers.gitHubToken; + if (!handler) throw new Error("No gitHubToken client-global handler registered"); + return handler.getToken(params); + }); } diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts index 1dad4ab166..7d734bf491 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. */ @@ -21,6 +24,7 @@ export type SessionEvent = | WarningEvent | ModelChangeEvent | ModeChangedEvent + | ModeNoticeDeliveredEvent | SessionLimitsChangedEvent | PermissionsChangedEvent | PlanChangedEvent @@ -37,10 +41,19 @@ export type SessionEvent = | CompactionStartEvent | CompactionCompleteEvent | TaskCompleteEvent + | CompletionReceiptEvent + | FusionRouteStartedEvent + | FusionRouteFailedEvent + | FusionResolvedEvent + | FusionCompletedEvent | UserMessageEvent | PendingMessagesModifiedEvent | AssistantTurnStartEvent | AssistantIntentEvent + | AssistantFusionPhaseStartedEvent + | AssistantFusionPhaseActivityEvent + | AssistantFusionPhaseCompletedEvent + | AssistantFusionPhaseFailedEvent | AssistantServerToolProgressEvent | AssistantReasoningEvent | AssistantReasoningDeltaEvent @@ -53,6 +66,7 @@ export type SessionEvent = | AssistantIdleEvent | AssistantUsageEvent | ModelCallFailureEvent + | ModelCallFinishedEvent | AbortEvent | ToolUserRequestedEvent | ToolExecutionStartEvent @@ -62,6 +76,7 @@ export type SessionEvent = | ToolSearchActivatedEvent | SkillInvokedEvent | SubagentStartedEvent + | SubagentConfiguredEvent | SubagentCompletedEvent | SubagentFailedEvent | SubagentSelectedEvent @@ -85,6 +100,7 @@ export type SessionEvent = | McpHeadersRefreshRequiredEvent | McpHeadersRefreshCompletedEvent | CustomNotificationEvent + | UIEphemeralQueryEvent | ExternalToolRequestedEvent | ExternalToolCompletedEvent | CommandQueuedEvent @@ -104,6 +120,8 @@ export type SessionEvent = | ToolsUpdatedEvent | BackgroundTasksChangedEvent | FactoryRunUpdatedEvent + | FactoryRunStartedEvent + | FactoryRunSettledEvent | SkillsLoadedEvent | CustomAgentsUpdatedEvent | McpServersLoadedEvent @@ -120,6 +138,16 @@ export type SessionEvent = | CanvasRemovedEvent | ExtensionsAttachmentsPushedEvent | McpAppToolCallCompleteEvent; +/** + * Routing preference used when the session model is `auto`. + */ +export type AutoTier = + /** Optimize for efficiency. */ + | "efficiency" + /** Balance efficiency and intelligence. */ + | "balance" + /** Optimize for intelligence. */ + | "intelligence"; /** * Hosting platform type of the repository (github or ado) */ @@ -156,6 +184,16 @@ export type Verbosity = | "medium" /** A more detailed response was requested. */ | "high"; +/** + * The session mode the agent is operating in + */ +export type SessionMode = + /** 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"; /** * 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. */ @@ -187,26 +225,42 @@ export type AutopilotObjectiveChangedStatus = /** Objective was completed by the agent. */ | "completed"; /** - * The session mode the agent is operating in - */ -export type SessionMode = - /** 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"; + * Origin of an effective session model change. + */ +export type ModelChangeSource = + /** The user selected a model directly with `/model `. */ + | "model_command" + /** The user selected the model with `/settings`. */ + | "settings_command" + /** The user selected the model with the `/config` alias. */ + | "config_command" + /** The user selected the model in the model picker, including the picker opened by bare `/model`. */ + | "model_picker" + /** Organization-managed settings selected the model. */ + | "managed_settings" + /** Repository settings selected the model. */ + | "repo_settings" + /** Startup model resolution selected the model. */ + | "startup" + /** Selecting an agent selected its configured model. */ + | "agent" + /** Entering, leaving, or reconfiguring plan mode selected the model. */ + | "plan_mode" + /** The runtime selected the model automatically, such as rate-limit recovery or refusal fallback. */ + | "automatic" + /** An SDK or RPC caller selected the model. */ + | "sdk"; /** - * Allow-all mode for the session. + * Permission mode for the session. */ /** @experimental */ -export type PermissionAllowAllMode = +export type PermissionMode = /** Permission requests follow the normal approval flow. */ - | "off" + | "manual" + /** Permission requests include an LLM safety recommendation; clients may automatically approve requests judged acceptable. */ + | "assisted" /** Tool, path, and URL permission requests are automatically approved. */ - | "on" - /** 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. */ - | "auto"; + | "allow-all"; /** * The type of operation performed on the plan file */ @@ -265,6 +319,87 @@ export type TaskCompletionOutcome = | "continue" /** Completion cannot proceed without intervention; the active objective is paused when one is identified. */ | "blocked"; +/** + * Structured terminal status from a tool completion event. + */ +export type CompletionReceiptToolStatus = + /** The tool completed successfully. */ + | "success" + /** The tool failed without a more specific structured status. */ + | "failure" + /** The tool exceeded its time budget. */ + | "timeout" + /** The user rejected the tool call. */ + | "rejected" + /** The permissions service denied the tool call. */ + | "denied"; +/** + * Runtime reason the completion decision was accepted. + */ +export type CompletionReceiptStopReason = + /** The model reached a natural terminal response. */ + | "natural" + /** A terminal tool ended the interaction. */ + | "terminal_tool" + /** The configured agentStop continuation limit was reached. */ + | "agent_stop_block_limit"; +/** + * Kind of turn for which HydraFusion routing is running. + */ +/** @experimental */ +export type FusionTurnKind = + /** A user-message turn. */ + | "user" + /** A conversation-compaction turn. */ + | "compaction"; +/** + * Server-recommended routing behavior for a later HydraFusion turn. + */ +/** @experimental */ +export type FusionFollowUpAction = + /** Reuse the durable primary model without routing. */ + | "reuse_primary" + /** Request a new routing decision. */ + | "reroute"; +/** + * Validated HydraFusion execution pattern. + */ +/** @experimental */ +export type FusionPattern = + /** Run one primary solver phase. */ + | "single" + /** Run a primary phase, a judge, and an optional repair. */ + | "cascade" + /** Run a primary draft, a read-only critique, and a revision. */ + | "critique"; +/** + * HydraFusion phase kind. + */ +/** @experimental */ +export type FusionPhaseKind = + /** Primary solver phase. */ + | "primary" + /** Read-only cascade judge phase. */ + | "judge" + /** Cascade repair phase. */ + | "repair" + /** Initial critique-pattern draft phase. */ + | "draft" + /** Read-only critique phase. */ + | "critic" + /** Critique-pattern revision phase. */ + | "revision" + /** Follow-up phase continuing from the resolved model. */ + | "follow_up"; +/** + * Conversation scope in which a HydraFusion phase executes. + */ +/** @experimental */ +export type FusionConversationScope = + /** Canonical root conversation history. */ + | "root" + /** Isolated read-only review history that does not enter the root conversation. */ + | "review"; /** * The agent mode that was active when this message was sent */ @@ -324,6 +459,40 @@ export type UserMessageDelivery = | "steering" /** Enqueued while the agent was busy; processed as its own run afterward. */ | "queued"; +/** + * Content-safe activity observed while a HydraFusion phase is running. + */ +/** @experimental */ +export type FusionPhaseActivityKind = + /** The provider produced additional private output bytes. */ + | "model_output" + /** A tool began executing inside the phase. */ + | "tool_started" + /** A tool finished executing inside the phase. */ + | "tool_completed"; +/** + * How a durable phase checkpoint contributes its exact message to canonical root history. + */ +/** @experimental */ +/** @internal */ +export type FusionProjectionMode = + /** Append the exact root message immediately. */ + | "append" + /** Hold a terminal message outside canonical history until the final commit selects it. */ + | "staged" + /** Do not project the checkpoint into root history. */ + | "none"; +/** + * Durable outcome status of a HydraFusion phase. + */ +/** @experimental */ +export type FusionPhaseStatus = + /** The phase completed successfully. */ + | "succeeded" + /** The phase failed. */ + | "failed" + /** The phase was cancelled. */ + | "cancelled"; /** * Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. */ @@ -348,6 +517,10 @@ export type CitationProvider = */ /** @experimental */ export type CitationLocation = CitationLocationChar | CitationLocationPage | CitationLocationBlock; +/** + * Hosted program caller type + */ +export type AssistantMessageToolRequestCallerType = "program"; /** * API endpoint used for this model call, matching CAPI supported_endpoints vocabulary */ @@ -360,6 +533,14 @@ export type AssistantUsageApiEndpoint = | "/responses" /** WebSocket Responses API endpoint. */ | "ws:/responses"; +/** + * Transport used for a successful model call + */ +export type AssistantUsageTransport = + /** HTTP transport, including SSE streams. */ + | "http" + /** WebSocket transport. */ + | "websocket"; /** * For HTTP 400 failures only: whether the response carried a structured CAPI error envelope (structured_error, a deterministic validation failure) or no error body (bodyless, the transient gateway/proxy signature). Absent for non-400 failures. */ @@ -394,6 +575,18 @@ export type ModelCallFailureTransport = | "http" /** WebSocket transport. */ | "websocket"; +/** + * Final outcome of one logical model dispatch after response acceptance processing + */ +export type ModelCallFinishedOutcome = + /** The provider response was accepted for continued agent processing. */ + | "success" + /** The dispatch ended with a provider or transport error. */ + | "error" + /** The dispatch was cancelled before an accepted response was produced. */ + | "cancelled" + /** The provider response was rejected during post-response acceptance processing. */ + | "rejected"; /** * Finite reason code describing why the current turn was aborted */ @@ -546,7 +739,15 @@ export type PermissionRequest = | PermissionRequestHook | PermissionRequestExtensionManagement | PermissionRequestFactory - | PermissionRequestExtensionPermissionAccess; + | PermissionRequestExtensionPermissionAccess + | PermissionRequestExtensionEnvAccess; +/** + * Advisory recommendation the runtime attaches to a permission request whose origin it can vouch for by construction. Unlike the auto-approval judge this does not depend on auto mode and does not evaluate what the tool call does; its absence simply means the runtime has no opinion and the request follows the host's normal approval flow. + */ +/** @experimental */ +export type PermissionRecommendation = + /** The runtime vouches for the request's origin and recommends approving it without prompting. The host still owns the decision and may deny it; deny rules, managed policy, and the auto-approval safety judge all outrank this recommendation. */ + "approve"; /** * Whether this is a store or vote memory operation */ @@ -555,6 +756,34 @@ export type PermissionRequestMemoryAction = | "store" /** Vote on an existing memory. */ | "vote"; +/** + * Why the assisted-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 AssistedApprovalJudgeFailureReason = + /** 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 assisted-approval safety judge for a permission request. Present only in assisted mode; its absence means the judge did not evaluate the request. + */ +/** @experimental */ +export type AssistedApprovalRecommendation = + /** The judge evaluated the request and recommends automatically approving it. */ + | "approve" + /** The judge evaluated the request and does not recommend automatically approving it; explicit approval is required. Whether that means prompting, denying, or something else is the consumer's decision. */ + | "requireApproval" + /** Assisted mode is enabled, but this request category is never automatically approvable (for example, sandbox-bypass requests), so the judge was not consulted. */ + | "excluded" + /** The judge was consulted but did not return a usable recommendation, so the request requires explicit approval. */ + | "error"; /** * Vote direction (vote only) */ @@ -563,6 +792,14 @@ export type PermissionRequestMemoryDirection = | "upvote" /** Vote that the memory is incorrect or outdated. */ | "downvote"; +/** + * Scope of a stored memory. + */ +export type PermissionRequestMemoryScope = + /** Store the memory for the current repository. */ + | "repository" + /** Store the memory for the current user. */ + | "user"; /** * Operation gated by a factory permission request. */ @@ -586,35 +823,8 @@ export type PermissionPromptRequest = | 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). - */ -/** @experimental */ -export type AutoApprovalRecommendation = - /** The judge evaluated the request and recommends automatically approving it. */ - | "approve" - /** The judge evaluated the request and does not recommend auto-approving it; explicit approval is required. Whether that means prompting, denying, or something else is the consumer's decision. */ - | "requireApproval" - /** Auto mode is enabled, but this request category is never auto-approvable (for example, sandbox-bypass requests), so the judge was not consulted. */ - | "excluded" - /** The judge was consulted but did not return a usable recommendation, so the request requires explicit approval. */ - | "error"; + | PermissionPromptRequestExtensionPermissionAccess + | PermissionPromptRequestExtensionEnvAccess; /** * Underlying permission kind that needs path approval */ @@ -650,7 +860,8 @@ export type UserToolSessionApproval = | UserToolSessionApprovalCustomTool | UserToolSessionApprovalExtensionManagement | UserToolSessionApprovalFactory - | UserToolSessionApprovalExtensionPermissionAccess; + | UserToolSessionApprovalExtensionPermissionAccess + | UserToolSessionApprovalExtensionEnvAccess; /** * Elicitation mode; "form" for structured input, "url" for browser-based. Defaults to "form" when absent. */ @@ -669,6 +880,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 */ @@ -709,6 +924,25 @@ export type McpHeadersRefreshCompletedOutcome = | "none" /** No response arrived within the bounded window. */ | "timeout"; +/** + * Source-defined JSON payload for the custom notification + */ +export type CustomNotificationPayload = JsonValue; +/** + * Lifecycle phase for a Rust-owned ephemeral query stream. + */ +/** @experimental */ +export type UIEphemeralQueryPhase = + /** The ephemeral query stream has begun. */ + | "started" + /** A partial result chunk was produced by the stream. */ + | "chunk" + /** The ephemeral query stream finished successfully. */ + | "completed" + /** The ephemeral query stream ended with an error. */ + | "failed" + /** The ephemeral query stream was cancelled before completing. */ + | "aborted"; /** * The user's auto-mode-switch choice */ @@ -742,14 +976,20 @@ 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" + /** A policy helper registered by device or server policy contributed. Device registration takes priority when present. */ + | "policyHelper" + /** More than one channel contributed. Ordinary keys resolve device over server over policy helper 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) @@ -761,16 +1001,18 @@ export type ManagedSettingsEnforcedAction = * For a `bypass_permissions_blocked` action, which permission-escalation primitive was refused */ export type ManagedSettingsEnforcedEscalation = - /** Full allow-all ("/allow-all on") permissions — auto-approving tools, paths, and URLs. */ + /** Full allow-all permissions — automatically approving tools, paths, and URLs. */ | "allow_all" - /** Auto-approval of all tool permission requests. */ + /** Automatic approval of all tool permission requests. */ | "approve_all" - /** Advisory auto-approval ("/allow-all auto") mode — keeps normal prompt paths and adds LLM-advised approval, distinct from full allow-all. */ - | "auto_approval" + /** Assisted mode — keeps normal prompt paths and adds an LLM recommendation, distinct from allow-all. */ + | "assisted_approval" /** Unrestricted filesystem access outside the session's allowed directories. */ | "unrestricted_paths" /** Unrestricted URL fetch access. */ - | "unrestricted_urls"; + | "unrestricted_urls" + /** A server-wide MCP "Always Allow" (or `--allow-tool `) blanket that would auto-approve every tool from an MCP server. Capped to per-tool approval; each tool still prompts. */ + | "server_wide_mcp_approval"; /** * Exit plan mode action */ @@ -784,7 +1026,19 @@ export type ExitPlanModeAction = /** Exit plan mode and continue with parallel autonomous workers. */ | "autopilot_fleet"; /** - * Source location type (e.g., project, personal-copilot, plugin, builtin) + * Terminal status a factory run committed. A settled run is never `pending` or `running`, so those two members of the run-status domain are deliberately absent. + */ +export type FactoryRunSettledStatus = + /** The factory body resolved and its result was committed. */ + | "completed" + /** The run was stopped by a limit, an approval refusal or another policy decision. */ + | "halted" + /** The run was cancelled by its caller or by session disposal. */ + | "cancelled" + /** The run failed, with `failureType` carrying the class when it has one. */ + | "error"; +/** + * Source location type (e.g., project, personal-copilot, plugin, builtin, sdk) */ export type SkillSource = /** Skill defined in the current project's skill directories. */ @@ -800,7 +1054,17 @@ export type SkillSource = /** Skill loaded from a configured custom skill directory. */ | "custom" /** Skill bundled with the runtime. */ - | "builtin"; + | "builtin" + /** Pathless skill supplied lazily by an SDK skill provider. */ + | "sdk"; +/** + * Whether configured models are advisory preferences or required constraints + */ +export type AgentModelPolicy = + /** Treat the authored models as advisory preferences that callers may override. */ + | "preferred" + /** Require subagent execution to use one of the authored models. */ + | "required"; /** * Configuration source: user, workspace, plugin, or builtin */ @@ -906,6 +1170,7 @@ export interface StartData { * Whether the session was already in use by another client at start time */ alreadyInUse?: boolean; + autoTier?: AutoTier; context?: WorkingDirectoryContext; /** * Context tier selected at session creation time for models with tiered context pricing; null when no tier is selected (e.g., non-tiered model) @@ -1058,6 +1323,7 @@ export interface ResumeData { * Whether the session was already in use by another client at resume time */ alreadyInUse?: boolean; + autoTier?: AutoTier; context?: WorkingDirectoryContext; /** * Context tier currently selected at resume time; null when no tier is active @@ -1250,6 +1516,7 @@ export interface IdleData { * True when the preceding agentic loop was cancelled via abort signal */ aborted?: boolean; + mode?: SessionMode; } /** * Session event "session.title_changed". Session title change payload containing the new display title @@ -1644,6 +1911,7 @@ export interface ModelChangeData { */ reasoningEffort?: string | null; reasoningSummary?: ReasoningSummary; + source?: ModelChangeSource; verbosity?: Verbosity; } /** @@ -1683,6 +1951,46 @@ export interface ModeChangedData { newMode: SessionMode; previousMode: SessionMode; } +/** + * Session event "session.mode_notice_delivered". Records that a mode transition notice reached the model so cache-stable mode tools can remain offered across resume. + */ +export interface ModeNoticeDeliveredEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: ModeNoticeDeliveredData; + /** + * 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.mode_notice_delivered". + */ + type: "session.mode_notice_delivered"; +} +/** + * Records that a mode transition notice reached the model so cache-stable mode tools can remain offered across resume. + */ +export interface ModeNoticeDeliveredData { + /** + * Model-visible transition notice persisted for a mid-turn delivery + */ + content?: string; + mode: SessionMode; +} /** * Session event "session.session_limits_changed". Session limits update details. Null clears the limits. */ @@ -1723,8 +2031,9 @@ export interface SessionLimitsChangedData { sessionLimits: SessionLimitsConfig | null; } /** - * Session event "session.permissions_changed". Permissions change details carrying the aggregate allow-all transition. + * Session event "session.permissions_changed". Permission-mode transition details. */ +/** @experimental */ export interface PermissionsChangedEvent { /** * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. @@ -1753,29 +2062,28 @@ export interface PermissionsChangedEvent { type: "session.permissions_changed"; } /** - * Permissions change details carrying the aggregate allow-all transition. + * Permission-mode transition details. */ +/** @experimental */ export interface PermissionsChangedData { /** - * Allow-all mode after the change + * Explicit LLM judge model override used by assisted mode; omitted when the provider default applies * * @experimental */ - allowAllPermissionMode?: PermissionAllowAllMode; - /** - * Aggregate allow-all flag after the change - */ - allowAllPermissions: boolean; + assistedApprovalModel?: string; /** - * Allow-all mode before the change + * Permission mode after the change * * @experimental */ - previousAllowAllPermissionMode?: PermissionAllowAllMode; + mode: PermissionMode; /** - * Aggregate allow-all flag before the change + * Permission mode before the change + * + * @experimental */ - previousAllowAllPermissions: boolean; + previousMode: PermissionMode; } /** * Session event "session.plan_changed". Plan file operation details indicating what changed @@ -2105,6 +2413,12 @@ export interface ShutdownEvent { * Session termination metrics including usage statistics, code changes, and shutdown reason */ export interface ShutdownData { + /** + * Per-agent usage breakdown, keyed by agent instance identifier. The main conversation uses the stable key `main`. + */ + agentMetrics?: { + [k: string]: ShutdownAgentMetric | undefined; + }; codeChanges: ShutdownCodeChanges; /** * Non-system message token count at shutdown @@ -2169,21 +2483,31 @@ export interface ShutdownData { totalPremiumRequests?: number; } /** - * Aggregate code change metrics for the session + * Usage attributed to one agent instance at session shutdown. */ -export interface ShutdownCodeChanges { +export interface ShutdownAgentMetric { /** - * List of file paths that were modified during the session + * Human-readable label for this subagent invocation, copied from the originating `subagent.started` event. For task-tool subagents this is the invocation's task description rather than the agent's configured display name, so group by `agentName` for stable per-agent labels. */ - filesModified: string[]; + agentDisplayName?: string; /** - * Total number of lines added during the session + * Configured agent name, when this is a subagent */ - linesAdded: number; + agentName?: string; /** - * Total number of lines removed during the session + * Per-model usage for this agent, keyed by model identifier */ - linesRemoved: number; + modelMetrics: { + [k: string]: ShutdownModelMetric | undefined; + }; + /** + * Time spent in model API calls by this agent, in milliseconds + */ + totalApiDurationMs: number; + /** + * Accumulated nano-AI units cost for this agent + */ + totalNanoAiu: number; } /** * Per-model shutdown metrics with request counts, token usage, nano-AI units, and token details. @@ -2255,6 +2579,23 @@ export interface ShutdownModelMetricUsage { */ reasoningTokens?: number; } +/** + * Aggregate code change metrics for the session + */ +export interface ShutdownCodeChanges { + /** + * List of file paths that were modified during the session + */ + filesModified: string[]; + /** + * Total number of lines added during the session + */ + linesAdded: number; + /** + * Total number of lines removed during the session + */ + linesRemoved: number; +} /** * A session-wide shutdown token-type entry storing the accumulated token count. */ @@ -2304,6 +2645,12 @@ export interface UsageCheckpointData { * @internal */ modelCacheState?: UsageCheckpointModelCacheState[]; + /** + * Internal per-conversation prompt-cache-break detector baselines restored on resume + * + * @internal + */ + promptCacheBreakState?: JsonValue[]; /** * Session-wide accumulated nano-AI units cost at checkpoint time */ @@ -2565,6 +2912,10 @@ export interface CompactionCompleteEvent { * Conversation compaction results including success status, metrics, and optional error details */ export interface CompactionCompleteData { + /** + * Canonical model identifier used for model-specific behavior when replaying compaction + */ + behaviorModelId?: string; /** * Checkpoint snapshot number created for recovery */ @@ -2765,14 +3116,15 @@ export interface TaskCompleteData { summary?: string; } /** - * Session event "user.message". Payload of `user.message` with displayed and model-transformed content, attachments, source/delivery metadata, mode, and telemetry IDs. + * Session event "session.completion_receipt". Behavior-neutral record of structured runtime facts present when an agent completion decision is accepted. */ -export interface UserMessageEvent { +/** @experimental */ +export interface CompletionReceiptEvent { /** * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. */ agentId?: string; - data: UserMessageData; + data: CompletionReceiptData; /** * When true, the event is transient and not persisted to the session event log on disk */ @@ -2790,59 +3142,538 @@ export interface UserMessageEvent { */ timestamp: string; /** - * Type discriminator. Always "user.message". + * Type discriminator. Always "session.completion_receipt". */ - type: "user.message"; + type: "session.completion_receipt"; } /** - * Payload of `user.message` with displayed and model-transformed content, attachments, source/delivery metadata, mode, and telemetry IDs. + * Behavior-neutral record of structured runtime facts present when an agent completion decision is accepted. */ -export interface UserMessageData { - agentMode?: UserMessageAgentMode; - /** - * Files, selections, or GitHub references attached to the message - */ - attachments?: Attachment[]; - /** - * The user's message text as displayed in the timeline - */ - content: string; - delivery?: UserMessageDelivery; +/** @experimental */ +export interface CompletionReceiptData { /** - * CAPI interaction ID for correlating this user message with its turn + * One-based accepted completion receipt ordinal in the durable session history. */ - interactionId?: string; + attempt: number; + eventRange: CompletionReceiptEventRange; /** - * True when this user message was auto-injected by autopilot's continuation loop rather than typed by the user; used to distinguish autopilot-driven turns in telemetry. + * Number of failed structured tool completions in the covered range. */ - isAutopilotContinuation?: boolean; + failedToolCount: number; + finalTool?: CompletionReceiptFinalTool; /** - * 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 + * Version of the completion receipt payload. */ - nativeDocumentPathFallbackPaths?: string[]; + schemaVersion: number; /** - * Parent agent task ID for background telemetry correlated to this user turn + * Identifier of the assistant turn-end event that supplied the accepted completion boundary. This is the receipt's idempotency key, and always equals eventRange.endEventId. */ - parentAgentTaskId?: string; + sourceEventId: string; + stopReason: CompletionReceiptStopReason; /** - * 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) + * Number of successful structured tool completions in the covered range. */ - source?: string; + successfulToolCount: number; +} +/** + * Inclusive durable event range summarized by a completion receipt. + */ +export interface CompletionReceiptEventRange { /** - * Normalized document MIME types that were sent natively instead of through tagged_files XML + * Identifier of the assistant turn-end event that ends the covered exchange. Always equals the receipt's sourceEventId, so either field is a valid join key. */ - supportedNativeDocumentMimeTypes?: string[]; + endEventId: string; /** - * Transformed version of the message sent to the model, with XML wrapping, timestamps, and other augmentations for prompt caching + * Identifier of the user message that starts the covered exchange. */ - transformedContent?: string; + startEventId: string; } /** - * File attachment + * Final structured tool completion in the covered event range. */ -export interface AttachmentFile { +export interface CompletionReceiptFinalTool { /** - * Internal: content-addressed id of the session.binary_asset event holding this attachment's model-facing bytes (e.g. "sha256:..."). Absent externally. + * Process exit code from a structured shell result, when available. + */ + exitCode?: number; + status: CompletionReceiptToolStatus; + /** + * Unique identifier of the completed tool call. + */ + toolCallId: string; + /** + * Tool name from the matching tool execution start event, when available. + */ + toolName?: string; +} +/** + * Session event "session.fusion_route_started". Experimental transient signal that HydraFusion routing has started for an eligible turn. + */ +/** @experimental */ +export interface FusionRouteStartedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: FusionRouteStartedData; + /** + * 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 "session.fusion_route_started". + */ + type: "session.fusion_route_started"; +} +/** + * Experimental transient signal that HydraFusion routing has started for an eligible turn. + */ +/** @experimental */ +export interface FusionRouteStartedData { + /** + * Identifier for this routing attempt before a durable Fusion turn exists. + */ + attemptId: string; + /** + * HydraFusion routing policy requested for the turn. + */ + policy?: string; + /** + * Synthetic HydraFusion model selected for the session. + */ + syntheticModel?: string; + turnKind: FusionTurnKind; +} +/** + * Session event "session.fusion_route_failed". Experimental durable HydraFusion routing failure and the deterministic concrete fallback selected for the turn. + */ +/** @experimental */ +export interface FusionRouteFailedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: FusionRouteFailedData; + /** + * 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.fusion_route_failed". + */ + type: "session.fusion_route_failed"; +} +/** + * Experimental durable HydraFusion routing failure and the deterministic concrete fallback selected for the turn. + */ +/** @experimental */ +export interface FusionRouteFailedData { + /** + * Identifier of the routing attempt that failed. + */ + attemptId: string; + /** + * Provider or validation error detail, when available. + */ + errorMessage?: string; + /** + * Concrete model selected as the deterministic fallback. + */ + fallbackModel: string; + /** + * HydraFusion routing policy requested for the turn. + */ + policy: string; + /** + * Stable machine-readable reason for the routing failure. + */ + reason: string; + /** + * Elapsed routing time in milliseconds before the failure. + */ + routingLatencyMs?: number; + /** + * Synthetic HydraFusion model selected for the session. + */ + syntheticModel: string; +} +/** + * Session event "session.fusion_resolved". Experimental durable validated HydraFusion route and turn policy. + */ +/** @experimental */ +export interface FusionResolvedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: FusionResolvedData; + /** + * 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.fusion_resolved". + */ + type: "session.fusion_resolved"; +} +/** + * Experimental durable validated HydraFusion route and turn policy. + */ +/** @experimental */ +export interface FusionResolvedData { + /** + * Version of the validated HydraFusion event contract. + */ + contractVersion: number; + /** + * Concrete model used when the planned primary model cannot execute. + */ + fallbackModel: string; + followUp?: FusionFollowUpRecommendation; + /** + * Concrete model recommended for eligible follow-up turns. + */ + followUpModel: string; + /** + * Stable identifier for the resolved HydraFusion turn. + */ + fusionId: string; + /** + * Version of the executable model universe used for selection. + */ + modelUniverseVersion?: string; + pattern: FusionPattern; + /** + * Presentation-neutral phase plan for clients that render workflow progress. + * + * @experimental + */ + phasePlan?: FusionPhasePlanStep[]; + /** + * Version of the validated execution-plan format. + */ + planVersion?: string; + /** + * HydraFusion routing policy used to resolve the plan. + */ + policy: string; + /** + * Version of the local routing policy. + */ + policyVersion?: string; + /** + * Concrete model selected for the primary solver phase. + */ + primaryModel: string; + /** + * Router implementation that supplied the plan. + */ + routeSource?: string; + /** + * Elapsed time in milliseconds required to resolve and validate the route. + */ + routingLatencyMs?: number; + /** + * Identifier of the local policy rule that matched. + */ + ruleId?: string; + /** + * Zero-based index of the local policy rule that matched. + */ + ruleIndex?: number; + /** + * Human-readable name of the local policy rule that matched. + */ + ruleName?: string; + scores?: FusionScores; + /** + * Concrete model selected for the review or judge phase, when required. + */ + secondaryModel: string | null; + /** + * Synthetic HydraFusion model selected for the session. + */ + syntheticModel: string; + /** + * Identifier of the session turn associated with the route. + */ + turnId: string; +} +/** + * Durable server recommendation for subsequent HydraFusion turns. + */ +/** @experimental */ +export interface FusionFollowUpRecommendation { + compactionTurn: FusionFollowUpAction; + userTurn: FusionFollowUpAction; +} +/** + * Presentation-neutral phase planned for a HydraFusion turn. + */ +/** @experimental */ +export interface FusionPhasePlanStep { + /** + * Whether the phase executes only when an earlier phase requests it. + */ + conditional: boolean; + kind: FusionPhaseKind; + /** + * Semantic role assigned to the phase. + */ + role: string; + scope: FusionConversationScope; +} +/** + * Validated HydraFusion routing capability scores. + */ +/** @experimental */ +export interface FusionScores { + /** + * Code-generation capability score returned by the authenticated router. + */ + codeGen: number; + /** + * Debugging capability score returned by the authenticated router. + */ + debugging: number; + /** + * Reasoning capability score returned by the authenticated router. + */ + reasoning: number; + /** + * Tool-use capability score returned by the authenticated router. + */ + toolUse: number; +} +/** + * Session event "session.fusion_completed". Experimental durable aggregate outcome of a HydraFusion turn. + */ +/** @experimental */ +export interface FusionCompletedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: FusionCompletedData; + /** + * 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.fusion_completed". + */ + type: "session.fusion_completed"; +} +/** + * Experimental durable aggregate outcome of a HydraFusion turn. + */ +/** @experimental */ +export interface FusionCompletedData { + /** + * Total cached input tokens reported across all phases. + */ + cachedTokens: number; + /** + * Total tokens written to prompt cache across all phases. + */ + cacheWriteTokens?: number; + /** + * Idempotency identifier for the authoritative final commit. + */ + commitId: string; + /** + * Reason the turn used a degraded route, when applicable. + */ + degradedReason: string | null; + /** + * Total elapsed execution time for the HydraFusion turn in milliseconds. + */ + durationMs: number; + /** + * Concrete model that supplied the authoritative final content. + */ + finalSourceModel: string | null; + /** + * Phase whose output supplied the authoritative final content. + */ + finalSourcePhaseId: string | null; + /** + * Concrete model recommended for eligible follow-up turns. + */ + followUpModel: string; + /** + * Stable identifier for the completed HydraFusion turn. + */ + fusionId: string; + /** + * Total input tokens consumed across all phases. + */ + inputTokens: number; + /** + * Stable aggregate outcome of the HydraFusion turn. + */ + outcome: string; + /** + * Total output tokens produced across all phases. + */ + outputTokens: number; + pattern: FusionPattern; + /** + * Number of concrete phases attempted by the turn. + */ + phaseCount: number; + /** + * Total concrete model requests made across all phases. + */ + requestCount: number; + /** + * Synthetic HydraFusion model selected for the session. + */ + syntheticModel: string; + /** + * Total normalized AI-unit cost reported across all phases, in nano-AIU. + */ + totalNanoAiu: number; + /** + * Identifier of the session turn associated with the completion. + */ + turnId: string; +} +/** + * Session event "user.message". Payload of `user.message` with displayed and model-transformed content, attachments, source/delivery metadata, mode, and telemetry IDs. + */ +export interface UserMessageEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: UserMessageData; + /** + * 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 "user.message". + */ + type: "user.message"; +} +/** + * Payload of `user.message` with displayed and model-transformed content, attachments, source/delivery metadata, mode, and telemetry IDs. + */ +export interface UserMessageData { + agentMode?: UserMessageAgentMode; + /** + * Files, selections, or GitHub references attached to the message + */ + attachments?: Attachment[]; + /** + * The user's message text as displayed in the timeline + */ + content: string; + delivery?: UserMessageDelivery; + /** + * CAPI interaction ID for correlating this user message with its turn + */ + interactionId?: string; + /** + * True when this user message was auto-injected by autopilot's continuation loop rather than typed by the user; used to distinguish autopilot-driven turns in telemetry. + */ + isAutopilotContinuation?: boolean; + /** + * Stable identity of the logical user message, matching the ID returned by send and retained by pending queue snapshots + */ + messageId?: string; + /** + * 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 + */ + nativeDocumentPathFallbackPaths?: string[]; + /** + * Parent agent task ID for background telemetry correlated to this user turn + */ + parentAgentTaskId?: string; + /** + * 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; + /** + * Normalized document MIME types that were sent natively instead of through tagged_files XML + */ + supportedNativeDocumentMimeTypes?: string[]; + /** + * Transformed version of the message sent to the model, with XML wrapping, timestamps, and other augmentations for prompt caching + */ + transformedContent?: string; + /** + * The agent-loop turn ID that consumed this message; absent when no agent-loop turn consumed it + */ + turnId?: string; +} +/** + * File attachment + */ +export interface AttachmentFile { + /** + * Internal: content-addressed id of the session.binary_asset event holding this attachment's model-facing bytes (e.g. "sha256:..."). Absent externally. */ assetId?: string; /** @@ -3140,156 +3971,392 @@ export interface AttachmentGitHubTreeComparison { */ type: "github_tree_comparison"; /** - * URL to the comparison on GitHub + * URL to the comparison on GitHub + */ + url: string; +} +/** + * One side of a tree comparison (head or base) + */ +export interface AttachmentGitHubTreeComparisonSide { + repo: GitHubRepoRef; + /** + * Git revision (branch, tag, or commit SHA) + */ + revision: string; +} +/** + * Generic GitHub URL reference. + */ +export interface AttachmentGitHubUrl { + /** + * Attachment type discriminator + */ + type: "github_url"; + /** + * URL to the GitHub resource + */ + url: string; +} +/** + * Pointer to a file in a GitHub repository at a specific ref. + */ +export interface AttachmentGitHubFile { + /** + * Repository-relative path to the file + */ + path: string; + /** + * Git ref the file is read at (branch, tag, or commit SHA) + */ + ref: string; + repo: GitHubRepoRef; + /** + * Attachment type discriminator + */ + type: "github_file"; + /** + * URL to the file on GitHub + */ + url: string; +} +/** + * Pointer to a line range inside a file in a GitHub repository. + */ +export interface AttachmentGitHubSnippet { + lineRange: AttachmentFileLineRange; + /** + * Repository-relative path to the file + */ + path: string; + /** + * Git ref the file is read at (branch, tag, or commit SHA) + */ + ref: string; + repo: GitHubRepoRef; + /** + * Attachment type discriminator + */ + type: "github_snippet"; + /** + * URL to the snippet on GitHub (with line anchor) + */ + url: string; +} +/** + * Blob attachment with inline base64-encoded data + */ +export interface AttachmentBlob { + /** + * Internal: content-addressed id of the session.binary_asset event holding this attachment's model-facing bytes (e.g. "sha256:..."). Absent externally. + */ + assetId?: string; + /** + * Internal: decoded byte length of the attachment's model-facing bytes. Absent externally. + */ + byteLength?: number; + /** + * Base64-encoded content. Present on input and for external consumers; replaced by an internal `assetId` reference in persisted events when interned to a content-addressed asset. + */ + data?: string; + /** + * User-facing display name for the attachment + */ + displayName?: string; + /** + * MIME type of the inline data + */ + mimeType: string; + omittedReason?: OmittedBinaryOmittedReason; + /** + * Attachment type discriminator + */ + type: "blob"; +} +/** + * Structured context contributed by an extension. Composer pills displayed in the host are forwarded back through session.send.attachments, then rendered into the model prompt as an XML block. + */ +export interface AttachmentExtensionContext { + /** + * Provider-local canvas identifier when the push was bound to a canvas instance + */ + canvasId?: string; + /** + * ISO 8601 timestamp captured by the runtime when the push was accepted + */ + capturedAt: string; + /** + * Owning extension identifier. Runtime-derived from the caller's connection when produced via session.extensions.sendAttachmentsToMessage; preserved verbatim on subsequent transports. + */ + extensionId: string; + /** + * Open canvas instance identifier when the push was bound to a canvas instance + */ + instanceId?: string; + /** + * Caller-supplied JSON payload + */ + payload?: JsonValue; + /** + * Human-readable composer pill label + */ + title: string; + /** + * Attachment type discriminator + */ + type: "extension_context"; +} +/** + * Session event "pending_messages.modified". Empty payload; the event signals that the pending message queue has changed + */ +export interface PendingMessagesModifiedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: PendingMessagesModifiedData; + /** + * 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 "pending_messages.modified". + */ + type: "pending_messages.modified"; +} +/** + * Empty payload; the event signals that the pending message queue has changed + */ +export interface PendingMessagesModifiedData {} +/** + * Session event "assistant.turn_start". Turn initialization metadata including identifier and interaction tracking + */ +export interface AssistantTurnStartEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: AssistantTurnStartData; + /** + * 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 "assistant.turn_start". + */ + type: "assistant.turn_start"; +} +/** + * Turn initialization metadata including identifier and interaction tracking + */ +export interface AssistantTurnStartData { + /** + * CAPI interaction ID for correlating this turn with upstream telemetry + */ + interactionId?: string; + /** + * Model identifier used for this turn, when known + */ + model?: string; + /** + * Identifier for this turn within the agentic loop, typically a stringified turn number + */ + turnId: string; +} +/** + * Session event "assistant.intent". Agent intent description for current activity or plan + */ +export interface AssistantIntentEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: AssistantIntentData; + /** + * 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.intent". */ - url: string; + type: "assistant.intent"; } /** - * One side of a tree comparison (head or base) + * Agent intent description for current activity or plan */ -export interface AttachmentGitHubTreeComparisonSide { - repo: GitHubRepoRef; +export interface AssistantIntentData { /** - * Git revision (branch, tag, or commit SHA) + * Short description of what the agent is currently doing or planning to do */ - revision: string; + intent: string; } /** - * Generic GitHub URL reference. + * Session event "assistant.fusion_phase_started". Experimental transient HydraFusion phase/model/role signal. */ -export interface AttachmentGitHubUrl { +/** @experimental */ +export interface AssistantFusionPhaseStartedEvent { /** - * Attachment type discriminator + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. */ - type: "github_url"; + agentId?: string; + data: FusionPhaseStartedData; /** - * URL to the GitHub resource + * Always true for events that are transient and not persisted to the session event log on disk. */ - url: string; -} -/** - * Pointer to a file in a GitHub repository at a specific ref. - */ -export interface AttachmentGitHubFile { + ephemeral: true; /** - * Repository-relative path to the file + * Unique event identifier (UUID v4), generated when the event is emitted */ - path: string; + id: string; /** - * Git ref the file is read at (branch, tag, or commit SHA) + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. */ - ref: string; - repo: GitHubRepoRef; + parentId: string | null; /** - * Attachment type discriminator + * ISO 8601 timestamp when the event was created */ - type: "github_file"; + timestamp: string; /** - * URL to the file on GitHub + * Type discriminator. Always "assistant.fusion_phase_started". */ - url: string; + type: "assistant.fusion_phase_started"; } /** - * Pointer to a line range inside a file in a GitHub repository. + * Experimental transient HydraFusion phase/model/role signal. */ -export interface AttachmentGitHubSnippet { - lineRange: AttachmentFileLineRange; +/** @experimental */ +export interface FusionPhaseStartedData { + conversationScope: FusionConversationScope; /** - * Repository-relative path to the file + * Identifier of the HydraFusion turn containing the phase. */ - path: string; + fusionId: string; /** - * Git ref the file is read at (branch, tag, or commit SHA) + * Concrete model executing the phase. */ - ref: string; - repo: GitHubRepoRef; + model: string; + pattern: FusionPattern; /** - * Attachment type discriminator + * Stable identifier for the concrete phase. */ - type: "github_snippet"; + phaseId: string; + phaseKind: FusionPhaseKind; /** - * URL to the snippet on GitHub (with line anchor) + * Semantic role assigned to the phase. */ - url: string; + role: string; } /** - * Blob attachment with inline base64-encoded data + * Session event "assistant.fusion_phase_activity". Experimental content-safe activity signal for a running HydraFusion phase. */ -export interface AttachmentBlob { +/** @experimental */ +export interface AssistantFusionPhaseActivityEvent { /** - * Internal: content-addressed id of the session.binary_asset event holding this attachment's model-facing bytes (e.g. "sha256:..."). Absent externally. + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. */ - assetId?: string; + agentId?: string; + data: FusionPhaseActivityData; /** - * Internal: decoded byte length of the attachment's model-facing bytes. Absent externally. + * Always true for events that are transient and not persisted to the session event log on disk. */ - byteLength?: number; + ephemeral: true; /** - * Base64-encoded content. Present on input and for external consumers; replaced by an internal `assetId` reference in persisted events when interned to a content-addressed asset. + * Unique event identifier (UUID v4), generated when the event is emitted */ - data?: string; + id: string; /** - * User-facing display name for the attachment + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. */ - displayName?: string; + parentId: string | null; /** - * MIME type of the inline data + * ISO 8601 timestamp when the event was created */ - mimeType: string; - omittedReason?: OmittedBinaryOmittedReason; + timestamp: string; /** - * Attachment type discriminator + * Type discriminator. Always "assistant.fusion_phase_activity". */ - type: "blob"; + type: "assistant.fusion_phase_activity"; } /** - * Structured context contributed by an extension. Composer pills displayed in the host are forwarded back through session.send.attachments, then rendered into the model prompt as an XML block. + * Experimental content-safe activity signal for a running HydraFusion phase. */ -export interface AttachmentExtensionContext { - /** - * Provider-local canvas identifier when the push was bound to a canvas instance - */ - canvasId?: string; - /** - * ISO 8601 timestamp captured by the runtime when the push was accepted - */ - capturedAt: string; +/** @experimental */ +export interface FusionPhaseActivityData { + activity: FusionPhaseActivityKind; + conversationScope: FusionConversationScope; /** - * Owning extension identifier. Runtime-derived from the caller's connection when produced via session.extensions.sendAttachmentsToMessage; preserved verbatim on subsequent transports. + * Identifier of the HydraFusion turn containing the phase. */ - extensionId: string; + fusionId: string; + pattern: FusionPattern; /** - * Open canvas instance identifier when the push was bound to a canvas instance + * Stable identifier for the concrete phase. */ - instanceId?: string; + phaseId: string; + phaseKind: FusionPhaseKind; /** - * Caller-supplied JSON payload + * Semantic role assigned to the phase. */ - payload?: { - [k: string]: unknown | undefined; - }; + role: string; /** - * Human-readable composer pill label + * Opaque hashed correlation token for matching tool-started and tool-completed activity within this Fusion activity stream. It is not the tool call identifier exposed by tool lifecycle events. */ - title: string; + toolCallId?: string; /** - * Attachment type discriminator + * Cumulative private response bytes observed for this model call. The event never includes response text. */ - type: "extension_context"; + totalResponseSizeBytes?: number; } /** - * Session event "pending_messages.modified". Empty payload; the event signals that the pending message queue has changed + * Session event "assistant.fusion_phase_completed". Experimental durable HydraFusion phase output and lossless replay checkpoint. */ -export interface PendingMessagesModifiedEvent { +/** @experimental */ +export interface AssistantFusionPhaseCompletedEvent { /** * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. */ agentId?: string; - data: PendingMessagesModifiedData; + data: FusionPhaseCompletedData; /** - * Always true for events that are transient and not persisted to the session event log on disk. + * When true, the event is transient and not persisted to the session event log on disk */ - ephemeral: true; + ephemeral?: boolean; /** * Unique event identifier (UUID v4), generated when the event is emitted */ @@ -3303,74 +4370,122 @@ export interface PendingMessagesModifiedEvent { */ timestamp: string; /** - * Type discriminator. Always "pending_messages.modified". + * Type discriminator. Always "assistant.fusion_phase_completed". */ - type: "pending_messages.modified"; + type: "assistant.fusion_phase_completed"; } /** - * Empty payload; the event signals that the pending message queue has changed - */ -export interface PendingMessagesModifiedData {} -/** - * Session event "assistant.turn_start". Turn initialization metadata including identifier and interaction tracking + * Experimental durable HydraFusion phase output and lossless replay checkpoint. */ -export interface AssistantTurnStartEvent { +/** @experimental */ +export interface FusionPhaseCompletedData { /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + * Provider-normalized textual output produced by the phase. */ - agentId?: string; - data: AssistantTurnStartData; + content: string; + conversationScope: FusionConversationScope; /** - * When true, the event is transient and not persisted to the session event log on disk + * Elapsed execution time for the phase in milliseconds. */ - ephemeral?: boolean; + durationMs: number; /** - * Unique event identifier (UUID v4), generated when the event is emitted + * Identifier of the HydraFusion turn containing the phase. */ - id: string; + fusionId: string; /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + * Concrete model that executed the phase. */ - parentId: string | null; + model: string; /** - * ISO 8601 timestamp when the event was created + * Stable identifier for the completed phase. */ - timestamp: string; + phaseId: string; + phaseKind: FusionPhaseKind; /** - * Type discriminator. Always "assistant.turn_start". + * Exact provider-normalized message used to reconstruct canonical model history. + * + * @internal */ - type: "assistant.turn_start"; + projectionMessage?: JsonValue; + /** + * Projection action for the exact internal message. + * + * @internal + */ + projectionMode?: FusionProjectionMode; + /** + * Semantic role assigned to the completed phase. + */ + role: string; + /** + * Terminal request held outside canonical state until selected by the final commit. + * + * @internal + */ + stagedTerminal?: FusionStagedTerminal; + status: FusionPhaseStatus; + usage: FusionPhaseUsage; + /** + * Structured judge or critic verdict, when the phase produces one. + */ + verdict: string | null; } /** - * Turn initialization metadata including identifier and interaction tracking + * Internal durable terminal request staged by a HydraFusion phase until an idempotent final commit selects it. */ -export interface AssistantTurnStartData { +/** @experimental */ +/** @internal */ +export interface FusionStagedTerminal { + arguments: string; + assistantMessage: JsonValue; + phaseId: string; + toolCallId: string; + toolName: string; +} +/** + * Aggregate concrete-model usage for one HydraFusion phase. + */ +/** @experimental */ +export interface FusionPhaseUsage { /** - * CAPI interaction ID for correlating this turn with upstream telemetry + * Total cached input tokens reported for the phase. */ - interactionId?: string; + cachedTokens: number; /** - * Model identifier used for this turn, when known + * Total tokens written to prompt cache during the phase. */ - model?: string; + cacheWriteTokens?: number; /** - * Identifier for this turn within the agentic loop, typically a stringified turn number + * Total input tokens consumed by the phase. */ - turnId: string; + inputTokens: number; + /** + * Total output tokens produced by the phase. + */ + outputTokens: number; + /** + * Number of concrete model requests made by the phase. + */ + requestCount: number; + /** + * Total normalized AI-unit cost reported for the phase, in nano-AIU. + */ + totalNanoAiu: number; } /** - * Session event "assistant.intent". Agent intent description for current activity or plan + * Session event "assistant.fusion_phase_failed". Experimental durable typed HydraFusion phase failure and degradation transition. */ -export interface AssistantIntentEvent { +/** @experimental */ +export interface AssistantFusionPhaseFailedEvent { /** * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. */ agentId?: string; - data: AssistantIntentData; + data: FusionPhaseFailedData; /** - * Always true for events that are transient and not persisted to the session event log on disk. + * When true, the event is transient and not persisted to the session event log on disk */ - ephemeral: true; + ephemeral?: boolean; /** * Unique event identifier (UUID v4), generated when the event is emitted */ @@ -3384,18 +4499,51 @@ export interface AssistantIntentEvent { */ timestamp: string; /** - * Type discriminator. Always "assistant.intent". + * Type discriminator. Always "assistant.fusion_phase_failed". */ - type: "assistant.intent"; + type: "assistant.fusion_phase_failed"; } /** - * Agent intent description for current activity or plan + * Experimental durable typed HydraFusion phase failure and degradation transition. */ -export interface AssistantIntentData { +/** @experimental */ +export interface FusionPhaseFailedData { + conversationScope: FusionConversationScope; /** - * Short description of what the agent is currently doing or planning to do + * Identifier of the fallback phase used to continue the turn after degradation. */ - intent: string; + degradedToPhaseId?: string; + /** + * Elapsed execution time before the phase failed, in milliseconds. + */ + durationMs: number; + /** + * Provider or execution error detail, when available. + */ + errorMessage?: string; + /** + * Identifier of the HydraFusion turn containing the phase. + */ + fusionId: string; + /** + * Concrete model that attempted the phase. + */ + model: string; + /** + * Stable identifier for the failed phase. + */ + phaseId: string; + phaseKind: FusionPhaseKind; + /** + * Stable machine-readable reason for the phase failure. + */ + reason: string; + /** + * Semantic role assigned to the failed phase. + */ + role: string; + status: FusionPhaseStatus; + usage: FusionPhaseUsage; } /** * Session event "assistant.server_tool_progress". Live progress signal for a provider-hosted server tool (e.g. hosted web search) while it runs, before the finalized serverTools envelope lands on the terminal assistant.message @@ -3486,6 +4634,9 @@ export interface AssistantReasoningData { * Unique identifier for this reasoning block */ reasoningId: string; + /** + * Per-request treatment/eligibility signal returned by the Copilot API in the `X-GitHub-Copilot-Request-TE` response header for the associated model call; `false` when the header was absent or unparseable. + */ rte?: boolean; } /** @@ -3682,6 +4833,12 @@ export interface AssistantMessageData { * Encrypted reasoning content from OpenAI models. Session-bound and stripped on resume. */ encryptedContent?: string; + /** + * Experimental HydraFusion source attribution for this ordinary authoritative assistant message. + * + * @experimental + */ + fusion?: FusionAttribution; /** * CAPI interaction ID for correlating this message with upstream telemetry */ @@ -3707,6 +4864,7 @@ export interface AssistantMessageData { * Generation phase for phased-output models (e.g., thinking vs. response phases) */ phase?: string; + reasoningBlocks?: AssistantMessageReasoningBlocks; /** * Opaque/encrypted extended thinking data from Anthropic models. Session-bound and stripped on resume. */ @@ -3723,6 +4881,9 @@ export interface AssistantMessageData { * GitHub request tracing ID (x-github-request-id header) for correlating with server-side logs */ requestId?: string; + /** + * Per-request treatment/eligibility signal returned by the Copilot API in the `X-GitHub-Copilot-Request-TE` response header for the associated model call; `false` when the header was absent or unparseable. + */ rte?: boolean; serverTools?: AssistantMessageServerTools; /** @@ -3806,9 +4967,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). */ @@ -3866,20 +5025,99 @@ export interface CitationLocationBlock { /** * Citation location type discriminator */ - type: "block"; + type: "block"; +} +/** + * Experimental attribution linking an ordinary event to the HydraFusion turn, phase, and concrete source that produced it. + */ +/** @experimental */ +export interface FusionAttribution { + /** + * Idempotency identifier for the authoritative commit, when the event belongs to the selected output. + */ + commitId?: string; + /** + * Conversation scope in which the concrete phase executed. + */ + conversationScope?: string; + /** + * Stable identifier for the HydraFusion turn that produced the event. + */ + fusionId: string; + /** + * HydraFusion orchestration pattern selected for the turn. + */ + pattern: string; + /** + * Identifier of the concrete phase that produced the event. + */ + phaseId?: string; + /** + * Kind of concrete phase that produced the event. + */ + phaseKind?: string; + /** + * HydraFusion routing policy used for the turn. + */ + policy: string; + /** + * Semantic role assigned to the concrete phase. + */ + role?: string; + /** + * Concrete model that produced the attributed event. + */ + sourceModel?: string; + /** + * Phase whose output supplied the authoritative content, when different from the executing phase. + */ + sourcePhaseId?: string; + /** + * Synthetic HydraFusion model selected for the session. + */ + syntheticModel: string; +} +/** + * Neutral provider-tagged reasoning content blocks preserved verbatim for round-tripping + */ +/** @experimental */ +export interface AssistantMessageReasoningBlocks { + /** + * Provider-native reasoning items or content blocks preserved verbatim, in order. A single response can carry several, and provider signatures or identifiers may depend on their exact content and ordering. + */ + blocks?: JsonValue[]; + /** + * Model provider that produced these reasoning blocks. + */ + provider: string; } /** * Neutral provider-tagged server-side tool-use payload (tool search, advisor) for verbatim round-tripping */ /** @experimental */ export interface AssistantMessageServerTools { + /** + * Advisor model identifier associated with the server-tool payload. + */ advisorModel?: string; + /** + * Provider function-call namespaces keyed by function-call identifier. + */ functionCallNamespaces?: { [k: string]: string | undefined; }; - items?: unknown[]; + /** + * Provider-native server-tool call and output items preserved verbatim for replay. + */ + items?: JsonValue[]; + /** + * Model provider that produced this server-tool payload. + */ provider: string; - rawContentBlocks?: unknown[]; + /** + * Raw provider content blocks retained for verbatim round-tripping. + */ + rawContentBlocks?: JsonValue[]; } /** * A tool invocation request from the assistant @@ -3888,9 +5126,8 @@ export interface AssistantMessageToolRequest { /** * Arguments to pass to the tool, format depends on the tool */ - arguments?: { - [k: string]: unknown | undefined; - }; + arguments?: JsonValue; + caller?: AssistantMessageToolRequestCaller; /** * Resolved intention summary describing what this specific call does */ @@ -3917,6 +5154,16 @@ export interface AssistantMessageToolRequest { toolTitle?: string; type?: AssistantMessageToolRequestType; } +/** + * Hosted program that requested this client tool call + */ +export interface AssistantMessageToolRequestCaller { + /** + * Provider-assigned identifier for the hosted caller. + */ + callerId: string; + type: AssistantMessageToolRequestCallerType; +} /** * Session event "assistant.message_start". Streaming assistant message start metadata */ @@ -4124,6 +5371,10 @@ export interface AssistantUsageEvent { * LLM API call usage metrics including tokens, costs, quotas, and billing information */ export interface AssistantUsageData { + /** + * Number of accepted speculative prediction tokens + */ + acceptedPredictionTokens?: number; /** * Completion ID from the model provider (e.g., chatcmpl-abc123) */ @@ -4135,6 +5386,12 @@ export interface AssistantUsageData { * @internal */ availableToolCount?: number; + /** + * Whether the provider reported prompt-cache usage details for this call + * + * @internal + */ + cacheDetailsReported?: boolean; /** * Updated prompt-cache expiration for this model call. Present only when the call establishes or refreshes known cache state. */ @@ -4143,6 +5400,12 @@ export interface AssistantUsageData { * Number of tokens read from prompt cache */ cacheReadTokens?: number; + /** + * Effective prompt-cache lifetime in seconds for this call + * + * @internal + */ + cacheTtlSeconds?: number; /** * Number of tokens written to prompt cache */ @@ -4166,6 +5429,18 @@ export interface AssistantUsageData { * 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". */ finishReason?: string; + /** + * How the prompt-cache frontier was determined for this call + * + * @internal + */ + frontierSource?: string; + /** + * Experimental HydraFusion attribution for this concrete model call's usage. + * + * @experimental + */ + fusion?: FusionAttribution; /** * What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls */ @@ -4182,6 +5457,22 @@ export interface AssistantUsageData { * Average inter-token latency in milliseconds. Only available for streaming requests */ interTokenLatencyMs?: number; + /** + * Whether Auto mode was selected for this model call + */ + isAuto?: boolean; + /** + * Whether this model call used a bring-your-own-key provider + */ + isByok?: boolean; + /** + * Requested maximum output tokens used for this model call + */ + maxOutputTokens?: number; + /** + * Effective maximum prompt-token limit used for this model call + */ + maxPromptTokens?: number; /** * Model identifier used for this API call */ @@ -4196,6 +5487,10 @@ export interface AssistantUsageData { * Number of output tokens produced */ outputTokens?: number; + /** + * Time to first observable model output in milliseconds. Includes text, reasoning, and tool-call output; only available for streaming requests that produce observable output. + */ + outputTtftMs?: number; /** * @deprecated * Parent tool call ID when this usage originates from a sub-agent @@ -4217,10 +5512,18 @@ export interface AssistantUsageData { * Reasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh", "max") */ reasoningEffort?: string; + reasoningSummary?: ReasoningSummary; /** * Number of output tokens used for reasoning (e.g., chain-of-thought) */ reasoningTokens?: number; + /** + * Number of rejected speculative prediction tokens + */ + rejectedPredictionTokens?: number; + /** + * Per-request treatment/eligibility signal returned by the Copilot API in the `X-GitHub-Copilot-Request-TE` response header for the associated model call; `false` when the header was absent or unparseable. + */ rte?: boolean; /** * Copilot service request ID (x-copilot-service-request-id header) for CAPI log correlation @@ -4244,6 +5547,7 @@ export interface AssistantUsageData { * @internal */ toolTokenCount?: number; + transport?: AssistantUsageTransport; } /** * Per-request cost and usage data from the CAPI copilot_usage response field @@ -4410,10 +5714,20 @@ export interface ModelCallFailureData { */ errorType?: string; failureKind?: ModelCallFailureKind; + /** + * Experimental HydraFusion attribution for this failed concrete model call. + * + * @experimental + */ + fusion?: FusionAttribution; /** * What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls */ initiator?: string; + /** + * Authoritative interaction classification for the failed call, matching `assistant.usage.interactionType` (for example `conversation-agent`, `conversation-subagent`, or `conversation-sampling`). Absent when the producer cannot classify the interaction. + */ + interactionType?: string; /** * Whether the session selected Auto mode for the failed call */ @@ -4451,6 +5765,9 @@ export interface ModelCallFailureData { */ reasoningEffort?: string; requestFingerprint?: ModelCallFailureRequestFingerprint; + /** + * Per-request treatment/eligibility signal returned by the Copilot API in the `X-GitHub-Copilot-Request-TE` response header for the associated model call; `false` when the header was absent or unparseable. + */ rte?: boolean; /** * Copilot service request ID (x-copilot-service-request-id header) for CAPI log correlation @@ -4496,6 +5813,62 @@ export interface ModelCallFailureRequestFingerprint { */ toolResultMessageCount: number; } +/** + * Session event "model.call_finished". Final lifecycle outcome for one logical model dispatch. A logical dispatch may include internal reconnect or fallback work, so event count is not provider HTTP-request count. + */ +export interface ModelCallFinishedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: ModelCallFinishedData; + /** + * 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_finished". + */ + type: "model.call_finished"; +} +/** + * Final lifecycle outcome for one logical model dispatch. A logical dispatch may include internal reconnect or fallback work, so event count is not provider HTTP-request count. + */ +export interface ModelCallFinishedData { + /** + * Whether an accepted successful response requested the exact name and command semantics of a built-in file edit tool, including an external tool explicitly replacing that built-in name. Absent when the logical dispatch did not produce an accepted response. + */ + containsBuiltInFileEditRequest?: boolean; + /** + * Monotonic elapsed time spent in the logical model dispatch, including any internal transport reconnect or fallback and excluding orchestrator retry backoff, tool execution, confirmations, and post-response processing + */ + dispatchDurationMs: number; + /** + * Version of the built-in file-edit semantic classifier used for this event + */ + editClassifierVersion: number; + /** + * Identifier of the user interaction that owns the model dispatch, matching assistant.turn_start.interactionId when available + */ + interactionId?: string; + outcome: ModelCallFinishedOutcome; + /** + * Agent-loop iteration within the interaction that initiated the model dispatch + */ + turnId: string; +} /** * Session event "abort". Turn abort information including the reason for termination */ @@ -4569,9 +5942,7 @@ export interface ToolUserRequestedData { /** * Arguments for the tool invocation */ - arguments?: { - [k: string]: unknown | undefined; - }; + arguments?: JsonValue; /** * Unique identifier for this tool call */ @@ -4618,13 +5989,17 @@ 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 */ displayVerbatim?: boolean; + /** + * Experimental HydraFusion attribution for this tool execution. + * + * @experimental + */ + fusion?: FusionAttribution; /** * Name of the MCP server hosting this tool, when the tool is an MCP tool */ @@ -4642,6 +6017,9 @@ export interface ToolExecutionStartData { * Tool call ID of the parent tool invocation when this event originates from a sub-agent */ parentToolCallId?: string; + /** + * Per-request treatment/eligibility signal returned by the Copilot API in the `X-GitHub-Copilot-Request-TE` response header for the associated model call; `false` when the header was absent or unparseable. + */ rte?: boolean; shellToolInfo?: ToolExecutionStartShellToolInfo; /** @@ -4831,6 +6209,12 @@ export interface ToolExecutionCompleteEvent { */ export interface ToolExecutionCompleteData { error?: ToolExecutionCompleteError; + /** + * Experimental HydraFusion attribution for this tool completion. + * + * @experimental + */ + fusion?: FusionAttribution; /** * CAPI interaction ID for correlating this tool execution with upstream telemetry */ @@ -4844,9 +6228,7 @@ export interface ToolExecutionCompleteData { * * @experimental */ - mcpMeta?: { - [k: string]: unknown | undefined; - }; + mcpMeta?: JsonValue; /** * Model identifier that generated this tool call */ @@ -4857,6 +6239,9 @@ export interface ToolExecutionCompleteData { */ parentToolCallId?: string; result?: ToolExecutionCompleteResult; + /** + * Per-request treatment/eligibility signal returned by the Copilot API in the `X-GitHub-Copilot-Request-TE` response header for the associated model call; `false` when the header was absent or unparseable. + */ rte?: boolean; /** * Whether this tool execution ran inside a sandbox container @@ -4875,7 +6260,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 @@ -4928,15 +6313,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; } /** @@ -4955,7 +6336,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 @@ -4980,7 +6361,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 @@ -5010,7 +6391,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 @@ -5091,6 +6472,10 @@ export interface ToolExecutionCompleteContentShellExit { * Exit code from the completed shell command */ exitCode: number; + /** + * Path reported in the shell session's filesystem namespace when shell output exceeded the configured large-output threshold. + */ + outputFilePath?: string; /** * Output associated with this shell command, if available. May be partial, truncated, or a preview; not guaranteed to be full output. */ @@ -5274,17 +6659,35 @@ export interface ToolExecutionCompleteUIResourceMeta { */ export interface ToolExecutionCompleteUIResourceMetaUI { csp?: ToolExecutionCompleteUIResourceMetaUICsp; + /** + * Optional dedicated origin for the rendered MCP Apps UI resource. + */ domain?: string; permissions?: ToolExecutionCompleteUIResourceMetaUIPermissions; + /** + * Whether the host should render a border around the MCP Apps UI resource. + */ prefersBorder?: boolean; } /** * CSP domain allowlists for an MCP Apps UI resource, including connect, resource, frame, and base URI domains. */ export interface ToolExecutionCompleteUIResourceMetaUICsp { + /** + * Domains the UI resource may use as document base URIs. + */ baseUriDomains?: string[]; + /** + * Domains the UI resource may connect to. + */ connectDomains?: string[]; + /** + * Domains the UI resource may embed as nested frames. + */ frameDomains?: string[]; + /** + * Domains from which the UI resource may load scripts, styles, images, and other resources. + */ resourceDomains?: string[]; } /** @@ -5434,6 +6837,10 @@ export interface SkillInvokedData { * Description of the skill from its SKILL.md frontmatter */ description?: string; + /** + * Whether model invocation is disabled for this skill + */ + disableModelInvocation?: boolean; /** * Model identifier active when the skill was invoked, when known */ @@ -5443,7 +6850,7 @@ export interface SkillInvokedData { */ name: string; /** - * File path to the SKILL.md definition + * File path to the SKILL.md definition, or an empty string for an SDK-provided skill without a filesystem identity */ path: string; /** @@ -5455,7 +6862,7 @@ export interface SkillInvokedData { */ pluginVersion?: string; /** - * Source identifier for where the skill was discovered. Known values include: project (workspace skill), inherited (parent-directory skill), personal-copilot (~/.copilot/skills), personal-agents (~/.agents/skills), custom (configured directory), plugin (installed plugin), builtin (bundled runtime skill), and remote (org/enterprise skill) + * Source identifier for where the skill was discovered. Known values include: project (workspace skill), inherited (parent-directory skill), personal-copilot (~/.copilot/skills), personal-agents (~/.agents/skills), custom (configured directory), plugin (installed plugin), builtin (bundled runtime skill), remote (org/enterprise skill), and sdk (SDK-provided skill) */ source?: string; trigger?: SkillInvokedTrigger; @@ -5506,15 +6913,86 @@ export interface SubagentStartedData { * Internal name of the sub-agent */ agentName: string; + /** + * Type of the sub-agent selected at spawn time. + */ + agentType?: string; + /** + * Whether the sub-agent runs synchronously or in the background. + */ + executionMode?: string; + /** + * Root id of the factory run that spawned this sub-agent, when it was spawned by one. + */ + factoryRunId?: string; /** * Model the sub-agent will run with, when known at start. */ model?: string; + /** + * Task-registry ID of the spawning sub-agent. Absent when the root session spawned this child. + */ + parentId?: string; + /** + * Whether this sub-agent can be resumed. Currently always false. + */ + resumable?: boolean; /** * Tool call ID of the parent tool invocation that spawned this sub-agent */ toolCallId: string; } +/** + * Session event "subagent.configured". Resolved runtime configuration for a configured sub-agent + */ +export interface SubagentConfiguredEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: SubagentConfiguredData; + /** + * 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 "subagent.configured". + */ + type: "subagent.configured"; +} +/** + * Resolved runtime configuration for a configured sub-agent + */ +export interface SubagentConfiguredData { + /** + * Resolved context tier, when configured for the model + */ + contextTier?: string; + /** + * Resolved model the sub-agent will run with + */ + model: string; + /** + * Whether the sub-agent accepts follow-up turns + */ + multiTurn: boolean; + /** + * Resolved reasoning effort, when configured for the model + */ + reasoningEffort?: string; +} /** * Session event "subagent.completed". Sub-agent completion details for successful execution */ @@ -5557,14 +7035,42 @@ 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; + /** + * Whether the first model actually dispatched matched the user's configured preference + */ + configuredModelMatchesActual?: boolean; + /** + * Concrete model the user configured for this sub-agent via `/subagents`, when present + */ + configuredModelPreference?: string; /** * Wall-clock duration of the sub-agent execution in milliseconds */ durationMs?: number; + /** + * Whether the explicit task-call model matched the user's configured preference + */ + explicitModelMatchesPreference?: boolean; + /** + * Explicit model supplied by the parent agent on the task call, when present + */ + explicitModelOverride?: string; + /** + * First model for which the sub-agent started an inference request, when one was dispatched + */ + firstDispatchedModel?: string; /** * Model used by the sub-agent */ model?: string; + /** + * Why an explicit task-call model did not become the effective model + */ + modelOverrideReason?: string; /** * Tool call ID of the parent tool invocation that spawned this sub-agent */ @@ -5620,6 +7126,14 @@ export interface SubagentFailedData { * Internal name of the sub-agent */ agentName: string; + /** + * Whether the first model actually dispatched matched the user's configured preference + */ + configuredModelMatchesActual?: boolean; + /** + * Concrete model the user configured for this sub-agent via `/subagents`, when present + */ + configuredModelPreference?: string; /** * Wall-clock duration of the sub-agent execution in milliseconds */ @@ -5628,10 +7142,26 @@ export interface SubagentFailedData { * Error message describing why the sub-agent failed */ error: string; + /** + * Whether the explicit task-call model matched the user's configured preference + */ + explicitModelMatchesPreference?: boolean; + /** + * Explicit model supplied by the parent agent on the task call, when present + */ + explicitModelOverride?: string; + /** + * First model for which the sub-agent started an inference request, when one was dispatched + */ + firstDispatchedModel?: string; /** * Model selected for the sub-agent, when known */ model?: string; + /** + * Why an explicit task-call model did not become the effective model + */ + modelOverrideReason?: string; /** * Tool call ID of the parent tool invocation that spawned this sub-agent */ @@ -5769,11 +7299,13 @@ export interface HookStartData { */ hookType: string; /** - * Input data passed to the hook + * Input data passed to the hook. For postToolUse hooks the retained copy served by session.eventLog.read (and by a resumed session) elides the tool result's inline `contents`/`uiResource` and replaces an over-long `textResultForLlm` with a `[copilot:elided ...]` marker, to keep a multi-megabyte payload out of the durable event log; the live subscription stream still delivers the full value. Read the adjacent tool.execution_complete event for the tool result itself. */ - input?: { - [k: string]: unknown | undefined; - }; + input?: JsonValue; + /** + * Tool call ID of the parent tool invocation when this event originates from a sub-agent + */ + parentToolCallId?: string; } /** * Session event "hook.end". Hook invocation completion details including output, success status, and error information @@ -5821,9 +7353,11 @@ export interface HookEndData { /** * Output data produced by the hook */ - output?: { - [k: string]: unknown | undefined; - }; + output?: JsonValue; + /** + * Tool call ID of the parent tool invocation when this event originates from a sub-agent + */ + parentToolCallId?: string; /** * Whether the hook completed successfully */ @@ -5944,7 +7478,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 @@ -6013,7 +7547,7 @@ export interface SystemMessageMetadata { * Template variables used when constructing the prompt */ variables?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; } /** @@ -6061,7 +7595,7 @@ export interface SystemNotificationData { */ export interface SystemNotificationAgentCompleted { /** - * Unique identifier of the background agent + * Unique task identifier */ agentId: string; /** @@ -6072,6 +7606,10 @@ export interface SystemNotificationAgentCompleted { * Human-readable description of the agent task */ description?: string; + /** + * Friendly, non-unique name intended for display + */ + displayName?: string; /** * The full prompt given to the background agent */ @@ -6087,7 +7625,7 @@ export interface SystemNotificationAgentCompleted { */ export interface SystemNotificationAgentIdle { /** - * Unique identifier of the background agent + * Unique task identifier */ agentId: string; /** @@ -6098,6 +7636,10 @@ export interface SystemNotificationAgentIdle { * Human-readable description of the agent task */ description?: string; + /** + * Friendly, non-unique name intended for display + */ + displayName?: string; /** * Type discriminator. Always "agent_idle". */ @@ -6218,9 +7760,7 @@ export interface SystemNotificationFactoryCompleted { /** * Machine-readable terminal failure details, when present. */ - failure?: { - [k: string]: unknown | undefined; - }; + failure?: JsonValue; /** * Bounded prompt-safe preview of the completed result. */ @@ -6246,9 +7786,7 @@ export interface SystemNotificationUnclassified { /** * Opaque metadata supplied by the external host, when present. */ - metadata?: { - [k: string]: unknown | undefined; - }; + metadata?: JsonValue; /** * Type discriminator. Always "unclassified". */ @@ -6288,6 +7826,7 @@ export interface PermissionRequestedEvent { * Permission request notification requiring client approval with request details */ export interface PermissionRequestedData { + agentMode?: SessionMode; permissionRequest: PermissionRequest; promptRequest?: PermissionPromptRequest; /** @@ -6301,9 +7840,7 @@ export interface PermissionRequestedData { /** * Neutral risk metadata supplied by the tool host. Consumers may display this value but must not use it to bypass the permission decision. */ - riskAssessment?: { - [k: string]: unknown | undefined; - }; + riskAssessment?: JsonValue; } /** * Shell command permission request @@ -6486,13 +8023,17 @@ export interface PermissionRequestMcp { /** * Arguments to pass to the MCP tool */ - args?: { - [k: string]: unknown | undefined; - }; + args?: JsonValue; /** * Permission kind discriminator */ kind: "mcp"; + /** + * Advisory runtime permission recommendation. The SDK host remains responsible for deciding the request and may reject it. + * + * @experimental + */ + permissionRecommendation?: PermissionRecommendation; /** * Whether this MCP tool is read-only (no side effects) */ @@ -6556,6 +8097,12 @@ export interface PermissionRequestUrl { */ export interface PermissionRequestMemory { action?: PermissionRequestMemoryAction; + /** + * Assisted-approval judge information for this request; present only in assisted mode. + * + * @experimental + */ + assistedApproval?: PermissionAssistedApproval; /** * Source references for the stored fact (store only) */ @@ -6573,6 +8120,11 @@ export interface PermissionRequestMemory { * Reason for the vote (vote only) */ reason?: string; + /** + * Repository name with owner associated with the stored memory (store only) + */ + repoNwo?: string; + scope?: PermissionRequestMemoryScope; /** * Topic or subject of the memory (store only) */ @@ -6580,7 +8132,23 @@ export interface PermissionRequestMemory { /** * Tool call ID that triggered this permission request */ - toolCallId?: string; + toolCallId?: string; +} +/** + * Assisted-approval judge information attached to a permission request. Present only in assisted mode; its absence means the judge did not evaluate the request. The `recommendation` conveys the judge's disposition for this request. + */ +/** @experimental */ +export interface PermissionAssistedApproval { + failureReason?: AssistedApprovalJudgeFailureReason; + /** + * 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. + */ + reason?: string; + recommendation: AssistedApprovalRecommendation; } /** * Custom tool invocation permission request @@ -6589,13 +8157,15 @@ export interface PermissionRequestCustomTool { /** * Arguments to pass to the custom tool */ - args?: { - [k: string]: unknown | undefined; - }; + args?: JsonValue; /** * Permission kind discriminator */ kind: "custom-tool"; + /** + * Whether the tool declared that permission may be skipped unless a deny rule matches + */ + skipPermission?: boolean; /** * Tool call ID that triggered this permission request */ @@ -6624,9 +8194,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 */ @@ -6669,9 +8237,21 @@ export interface PermissionRequestFactory { * Whether this factory is eligible for persistent approval */ canPersistApproval: boolean; + /** + * Factory-declared AI-credit limit before any run/resume caller override is applied. + */ declaredMaxAiCredits?: number; + /** + * Factory-declared concurrent-subagent limit before any run/resume caller override is applied. + */ declaredMaxConcurrentSubagents?: number; + /** + * Factory-declared total-subagent limit before any run/resume caller override is applied. + */ declaredMaxTotalSubagents?: number; + /** + * Factory-declared active-time limit in seconds before any run/resume caller override is applied. + */ declaredTimeoutSeconds?: number; /** * Factory description @@ -6745,16 +8325,39 @@ export interface PermissionRequestExtensionPermissionAccess { */ toolCallId?: string; } +/** + * Extension sensitive environment variable access request + */ +export interface PermissionRequestExtensionEnvAccess { + /** + * Names of the sensitive environment variables the extension is requesting. Values never appear here. + * + * @minItems 1 + */ + environmentVariables: [string, ...string[]]; + /** + * Name of the extension requesting environment variable access + */ + extensionName: string; + /** + * Permission kind discriminator + */ + kind: "extension-env-access"; + /** + * Tool call ID that triggered this permission request + */ + toolCallId?: string; +} /** * Shell command permission prompt */ export interface PermissionPromptRequestCommands { /** - * Auto-approval judge information for this request; present only when auto mode is enabled. + * Assisted-approval judge information for this request; present only in assisted mode. * * @experimental */ - autoApproval?: PermissionAutoApproval; + assistedApproval?: PermissionAssistedApproval; /** * Whether the UI can offer session-wide approval for this command pattern */ @@ -6788,32 +8391,16 @@ export interface PermissionPromptRequestCommands { */ warning?: string; } -/** - * 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 */ -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. - */ - reason?: string; - recommendation: AutoApprovalRecommendation; -} /** * File write permission prompt */ export interface PermissionPromptRequestWrite { /** - * Auto-approval judge information for this request; present only when auto mode is enabled. + * Assisted-approval judge information for this request; present only in assisted mode. * * @experimental */ - autoApproval?: PermissionAutoApproval; + assistedApproval?: PermissionAssistedApproval; /** * Whether the UI can offer session-wide approval for file write operations */ @@ -6852,11 +8439,11 @@ export interface PermissionPromptRequestWrite { */ export interface PermissionPromptRequestRead { /** - * Auto-approval judge information for this request; present only when auto mode is enabled. + * Assisted-approval judge information for this request; present only in assisted mode. * * @experimental */ - autoApproval?: PermissionAutoApproval; + assistedApproval?: PermissionAssistedApproval; /** * Human-readable description of why the file is being read */ @@ -6885,19 +8472,27 @@ 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. + * Assisted-approval judge information for this request; present only in assisted mode. * * @experimental */ - autoApproval?: PermissionAutoApproval; + assistedApproval?: PermissionAssistedApproval; + /** + * Whether the host may offer a server-wide "approve all tools from this server" blanket. Absent is treated as true; the runtime sends false when managed policy disables bypass-permissions mode, which forbids the server-wide escalation while still allowing per-tool approval. + */ + canOfferServerWideApproval?: boolean; /** * Prompt kind discriminator */ kind: "mcp"; + /** + * Advisory runtime permission recommendation. The host remains responsible for deciding the request and may reject it. + * + * @experimental + */ + permissionRecommendation?: PermissionRecommendation; /** * Name of the MCP server providing the tool */ @@ -6920,11 +8515,11 @@ export interface PermissionPromptRequestMcp { */ export interface PermissionPromptRequestUrl { /** - * Auto-approval judge information for this request; present only when auto mode is enabled. + * Assisted-approval judge information for this request; present only in assisted mode. * * @experimental */ - autoApproval?: PermissionAutoApproval; + assistedApproval?: PermissionAssistedApproval; /** * Human-readable description of why the URL is being accessed */ @@ -6964,11 +8559,11 @@ export interface PermissionPromptRequestUrl { export interface PermissionPromptRequestMemory { action?: PermissionRequestMemoryAction; /** - * Auto-approval judge information for this request; present only when auto mode is enabled. + * Assisted-approval judge information for this request; present only in assisted mode. * * @experimental */ - autoApproval?: PermissionAutoApproval; + assistedApproval?: PermissionAssistedApproval; /** * Source references for the stored fact (store only) */ @@ -7002,15 +8597,13 @@ 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. + * Assisted-approval judge information for this request; present only in assisted mode. * * @experimental */ - autoApproval?: PermissionAutoApproval; + assistedApproval?: PermissionAssistedApproval; /** * Prompt kind discriminator */ @@ -7034,11 +8627,11 @@ export interface PermissionPromptRequestCustomTool { export interface PermissionPromptRequestPath { accessKind: PermissionPromptRequestPathAccessKind; /** - * Auto-approval judge information for this request; present only when auto mode is enabled. + * Assisted-approval judge information for this request; present only in assisted mode. * * @experimental */ - autoApproval?: PermissionAutoApproval; + assistedApproval?: PermissionAssistedApproval; /** * Prompt kind discriminator */ @@ -7057,11 +8650,11 @@ export interface PermissionPromptRequestPath { */ export interface PermissionPromptRequestHook { /** - * Auto-approval judge information for this request; present only when auto mode is enabled. + * Assisted-approval judge information for this request; present only in assisted mode. * * @experimental */ - autoApproval?: PermissionAutoApproval; + assistedApproval?: PermissionAssistedApproval; /** * Optional message from the hook explaining why confirmation is needed */ @@ -7073,9 +8666,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 */ @@ -7090,11 +8681,11 @@ export interface PermissionPromptRequestHook { */ export interface PermissionPromptRequestExtensionManagement { /** - * Auto-approval judge information for this request; present only when auto mode is enabled. + * Assisted-approval judge information for this request; present only in assisted mode. * * @experimental */ - autoApproval?: PermissionAutoApproval; + assistedApproval?: PermissionAssistedApproval; /** * Name of the extension being managed */ @@ -7121,18 +8712,30 @@ export interface PermissionPromptRequestFactory { */ approvalKey: string; /** - * Auto-approval judge information for this request; present only when auto mode is enabled. + * Assisted-approval judge information for this request; present only in assisted mode. * * @experimental */ - autoApproval?: PermissionAutoApproval; + assistedApproval?: PermissionAssistedApproval; /** * Whether this factory is eligible for persistent approval */ canPersistApproval: boolean; + /** + * Factory-declared AI-credit limit before any run/resume caller override is applied. + */ declaredMaxAiCredits?: number; + /** + * Factory-declared concurrent-subagent limit before any run/resume caller override is applied. + */ declaredMaxConcurrentSubagents?: number; + /** + * Factory-declared total-subagent limit before any run/resume caller override is applied. + */ declaredMaxTotalSubagents?: number; + /** + * Factory-declared active-time limit in seconds before any run/resume caller override is applied. + */ declaredTimeoutSeconds?: number; /** * Factory description @@ -7181,11 +8784,11 @@ export interface PermissionPromptRequestFactory { */ export interface PermissionPromptRequestExtensionPermissionAccess { /** - * Auto-approval judge information for this request; present only when auto mode is enabled. + * Assisted-approval judge information for this request; present only in assisted mode. * * @experimental */ - autoApproval?: PermissionAutoApproval; + assistedApproval?: PermissionAssistedApproval; /** * Capabilities the extension is requesting */ @@ -7203,6 +8806,35 @@ export interface PermissionPromptRequestExtensionPermissionAccess { */ toolCallId?: string; } +/** + * Extension sensitive environment variable access prompt + */ +export interface PermissionPromptRequestExtensionEnvAccess { + /** + * Assisted-approval judge information for this request; present only in assisted mode. + * + * @experimental + */ + assistedApproval?: PermissionAssistedApproval; + /** + * Names of the sensitive environment variables the extension is requesting. Values never appear here. + * + * @minItems 1 + */ + environmentVariables: [string, ...string[]]; + /** + * Name of the extension requesting environment variable access + */ + extensionName: string; + /** + * Prompt kind discriminator + */ + kind: "extension-env-access"; + /** + * Tool call ID that triggered this permission request + */ + toolCallId?: string; +} /** * Session event "permission.completed". Permission request completion notification signaling UI dismissal */ @@ -7255,6 +8887,10 @@ export interface PermissionApproved { * The permission request was approved */ kind: "approved"; + /** + * Whether a managed approval policy already handled this request + */ + managedApprovalHandled?: boolean; } /** * Permission response variant that approves a request and remembers the provided approval for the rest of the session. @@ -7265,6 +8901,10 @@ export interface PermissionApprovedForSession { * Approved and remembered for the rest of the session */ kind: "approved-for-session"; + /** + * Whether a managed approval policy already handled this request + */ + managedApprovalHandled?: boolean; } /** * Session-scoped tool-approval rule for specific shell command identifiers. @@ -7375,6 +9015,25 @@ export interface UserToolSessionApprovalExtensionPermissionAccess { */ kind: "extension-permission-access"; } +/** + * Session-scoped tool-approval rule for an extension's access to sensitive environment variables, keyed by extension name and the exact set of variable names. + */ +export interface UserToolSessionApprovalExtensionEnvAccess { + /** + * Names of the sensitive environment variables this approval covers. Values are never persisted. + * + * @minItems 1 + */ + environmentVariables: [string, ...string[]]; + /** + * Extension name + */ + extensionName: string; + /** + * Extension environment access approval kind + */ + kind: "extension-env-access"; +} /** * Permission response variant that approves a request and persists the provided approval to a project location key. */ @@ -7388,6 +9047,10 @@ export interface PermissionApprovedForLocation { * The location key (git root or cwd) to persist the approval to */ locationKey: string; + /** + * Whether a managed approval policy already handled this request + */ + managedApprovalHandled?: boolean; } /** * Permission response variant indicating the request was cancelled before use, with an optional reason. @@ -7655,7 +9318,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 @@ -7712,12 +9375,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 */ @@ -7755,9 +9412,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() */ @@ -8106,18 +9761,66 @@ 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 */ export interface CustomNotificationSubject { [k: string]: string | undefined; } +/** + * Session event "ui.ephemeral_query". Ordered output and terminal state for a transient query that does not modify conversation history. + */ +/** @experimental */ +export interface UIEphemeralQueryEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: UIEphemeralQueryData; + /** + * 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 "ui.ephemeral_query". + */ + type: "ui.ephemeral_query"; +} +/** + * Ordered output and terminal state for a transient query that does not modify conversation history. + */ +/** @experimental */ +export interface UIEphemeralQueryData { + /** + * Full response text, present for the `completed` phase. + */ + answer?: string; + /** + * Ordered text delta, present for the `chunk` phase. + */ + chunk?: string; + /** + * Model or transport failure message, present for the `failed` phase. + */ + error?: string; + phase: UIEphemeralQueryPhase; + /** + * Runtime-minted query identifier. + */ + requestId: string; +} /** * Session event "external_tool.requested". External tool invocation request for client-side tool execution */ @@ -8155,9 +9858,11 @@ export interface ExternalToolRequestedData { /** * Arguments to pass to the external tool */ - arguments?: { - [k: string]: unknown | undefined; - }; + arguments?: JsonValue; + /** + * Stable provider identity captured with an extension-owned tool definition; hosts use it to route the request to the same provider that was offered to the model + */ + providerId?: string | null; /** * Unique identifier for this request; used to respond via session.respondToExternalTool() */ @@ -8644,7 +10349,7 @@ export interface AutoModeResolvedData { 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, then the policy helper, per ordinary key, while permissions compose restrictively across device, server, policy-helper, 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 { @@ -8675,7 +10380,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, then the policy helper, per ordinary key, while permissions compose restrictively across device, server, policy-helper, 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 { @@ -8684,7 +10389,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; /** @@ -8696,9 +10405,17 @@ export interface ManagedSettingsResolvedData { */ managedKeys: string[]; /** - * Whether server and device each supplied a permission allowlist, so enforcement intersects them and the flattened settings payload omits `permissions.allow`. + * 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 policy-helper managed-settings layer was present. The policy helper is the weakest channel: it fills keys no enterprise source set and can never replace one. + */ + policyHelperManaged?: boolean; + /** + * Whether the effective sandbox policy forces the sandbox on *only* because managed policy could not be determined, rather than because the policy requires it. Lets clients tell a user whose `--no-sandbox` was overridden that the sandbox stayed on as a fail-closed fallback, instead of attributing it to an administrator who set no such policy. + */ + sandboxEnabledByUndeterminedPolicy?: boolean; /** * Whether the server (account/org) managed-settings layer was present */ @@ -8706,9 +10423,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; } /** @@ -8905,6 +10620,10 @@ export interface ExitPlanModeRequestedData { * Available actions the user can take */ actions: ExitPlanModeAction[]; + /** + * Model the session had selected when the plan was authored, when one is known + */ + model?: string; /** * Full content of the plan file */ @@ -9084,7 +10803,117 @@ export interface FactoryRunUpdatedData { * Monotonic revision now available for the run. */ revision: number; + /** + * Factory run identifier. + */ + runId: string; +} +/** + * Session event "factory.run_started". Ephemeral signal that a factory run attempt began executing. + */ +/** @experimental */ +export interface FactoryRunStartedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: FactoryRunStartedData; + /** + * 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_started". + */ + type: "factory.run_started"; +} +/** + * Ephemeral signal that a factory run attempt began executing. + */ +/** @experimental */ +export interface FactoryRunStartedData { + /** + * Attempt number this start committed; a resumed run increments it. + */ + attempt: number; + /** + * Name of the factory this run executes. Low cardinality by construction. + */ + factoryName: string; + /** + * Identifier of the factory run that started. + */ + runId: string; +} +/** + * Session event "factory.run_settled". Ephemeral signal that a factory run reached a terminal status. + */ +/** @experimental */ +export interface FactoryRunSettledEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: FactoryRunSettledData; + /** + * 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_settled". + */ + type: "factory.run_settled"; +} +/** + * Ephemeral signal that a factory run reached a terminal status. + */ +/** @experimental */ +export interface FactoryRunSettledData { + /** + * AI credits this run consumed, in nano-AIU. + */ + consumedNanoAiu: number; + /** + * Subagents this run consumed against its limits. + */ + consumedSubagents: number; + /** + * Active milliseconds accumulated across every attempt of this run. + */ + elapsedMs: number; + /** + * Typed failure class recorded on the run, when it failed with one (e.g. `factory_limit_reached`). + */ + failureType?: string; + /** + * Identifier of the factory run that settled. + */ runId: string; + status: FactoryRunSettledStatus; } /** * Session event "session.skills_loaded". Payload of `session.skills_loaded` listing resolved skill metadata. @@ -9133,6 +10962,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 */ @@ -9203,7 +11036,7 @@ export interface CustomAgentsUpdatedData { warnings: string[]; } /** - * A single loaded custom agent in `session.custom_agents_updated`, with identity, source, tools, invocability, and model override. + * A single loaded custom agent in `session.custom_agents_updated`, with identity, source, tools, invocability, and authored model configuration. */ export interface CustomAgentsUpdatedAgent { /** @@ -9222,6 +11055,11 @@ export interface CustomAgentsUpdatedAgent { * Model override for this agent, if set */ model?: string; + modelPolicy?: AgentModelPolicy; + /** + * Authored model ids in priority order, if configured + */ + models?: string[]; /** * Internal name of the agent */ @@ -9554,9 +11392,7 @@ export interface CanvasOpenedData { /** * Input supplied when the instance was opened */ - input?: { - [k: string]: unknown | undefined; - }; + input?: JsonValue; /** * Stable caller-supplied canvas instance identifier */ @@ -9651,9 +11487,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. @@ -9667,9 +11501,7 @@ export interface CanvasRegistryChangedCanvasAction { /** * JSON Schema for action input */ - inputSchema?: { - [k: string]: unknown | undefined; - }; + inputSchema?: JsonValue; /** * Action name */ @@ -9820,9 +11652,7 @@ export interface CanvasRecordedData { /** * Input supplied when the instance was opened */ - input?: { - [k: string]: unknown | undefined; - }; + input?: JsonValue; /** * Stable caller-supplied canvas instance identifier */ @@ -9958,7 +11788,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 @@ -9969,7 +11799,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 f915a8707e..bf6f2195f9 100644 --- a/nodejs/src/index.ts +++ b/nodejs/src/index.ts @@ -9,7 +9,7 @@ */ export { CopilotClient } from "./client.js"; -export { RuntimeConnection } from "./types.js"; +export { DisableBypassPermissionsModes, RuntimeConnection } from "./types.js"; export { BuiltInTools, ToolSet } from "./toolSet.js"; export { CopilotSession, type AssistantMessageEvent } from "./session.js"; export { defineFactory, FactoryResumeError, isFactoryRunTerminal } from "./factory.js"; @@ -27,6 +27,7 @@ export { export { defineTool, approveAll, + createAttributedPermissionResult, convertMcpCallToolResult, createSessionFsAdapter, CopilotRequestHandler, @@ -41,16 +42,18 @@ export { // consumers can import them directly from "@github/copilot-sdk" instead of // reaching into the package's internal dist layout. See issue #1156. // -// Five 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`), // `PermissionRequestedData`/`PermissionRequestedEvent` (also re-exported below -// from `./types.js`), and `AssistantMessageEvent` (re-exported above from -// `./session.js`). Per the ECMAScript module spec, the explicit named re-exports +// 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 five identifiers is preserved unchanged. +// surface for those six identifiers is preserved unchanged. export type * from "./generated/session-events.js"; export type { + AskUserVariant, CommandContext, CommandDefinition, CommandHandler, @@ -66,6 +69,7 @@ export type { UserPromptTransformedHandler, UserPromptTransformedHookInput, UserPromptTransformedHookOutput, + CopilotClientInfo, CopilotClientMode, CopilotClientOptions, CopilotExpAssignmentResponse, @@ -95,6 +99,11 @@ export type { GitHubTelemetryNotification, GitHubTelemetryEvent, GitHubTelemetryClientInfo, + GitHubTokenAcquireReason, + GitHubTokenAcquireResult, + GitHubTokenProvider, + GitHubTokenProviderArgs, + GitHubTokenProviderResult, InfiniteSessionConfig, LargeToolOutputConfig, MemoryConfiguration, @@ -107,9 +116,12 @@ export type { DefaultAgentConfig, BearerTokenProvider, MessageOptions, + ManagedSettings, + ManagedSettingsPermissions, ModelBilling, ModelBillingTokenPrices, ModelBillingTokenPricesLongContext, + AutoTier, CapiSessionOptions, ModelCapabilities, ModelCapabilitiesOverride, @@ -121,6 +133,12 @@ export type { PermissionRequestedData, PermissionRequestedEvent, PermissionRequestResult, + AttributedPermissionResult, + PermissionDecisionContext, + PermissionDecisionOutcome, + PermissionDecisionSource, + PermissionDecisionSurface, + PermissionResponseCapability, ProviderConfig, ProviderModelConfig, ProviderTokenArgs, @@ -194,6 +212,8 @@ export type { FactoryRunResult, FactoryRunStatus, FactoryRunSummary, + FactoryListRunsOptions, + FactoryRunsPage, FactoryRunDetail, FactoryProgressPage, FactoryProgressLine, diff --git a/nodejs/src/runtimeArtifacts.ts b/nodejs/src/runtimeArtifacts.ts new file mode 100644 index 0000000000..d33b72c9da --- /dev/null +++ b/nodejs/src/runtimeArtifacts.ts @@ -0,0 +1,295 @@ +import { + chmodSync, + copyFileSync, + existsSync, + lstatSync, + mkdirSync, + mkdtempSync, + readdirSync, + renameSync, + rmSync, + statSync, +} from "node:fs"; +import { createHash } from "node:crypto"; +import { createRequire } from "node:module"; +import { homedir } from "node:os"; +import { dirname, join, relative, sep } from "node:path"; +import { COPILOT_CLI_USE_NPM_PACKAGE } from "./cliVersion.js"; + +export interface RuntimeArtifactSources { + packageRoot: string; + platform: string; +} + +export interface EnsureRuntimeBundleOptions { + cacheRoot?: string; + packageSearchPaths?: string[]; + platform?: string; +} + +const require = createRequire(typeof __filename === "string" ? __filename : import.meta.url); +export const RUNTIME_PLATFORMS = [ + "darwin-arm64", + "darwin-x64", + "linux-arm64", + "linux-x64", + "linuxmusl-arm64", + "linuxmusl-x64", + "win32-arm64", + "win32-x64", +] as const; + +const EXCLUDED_TOP_LEVEL = new Set([ + "app.js", + "assets", + "changelog.json", + "copilot", + "copilot.exe", + "foundry-local-sdk", + "index.js", + "LICENSE.md", + "napi-oop-runtime", + "npm-loader.js", + "package.json", + "pvrecorder", + "queries", + "README.md", + "sea-loader.js", + "webview", +]); + +interface RuntimeAsset { + source: string; + relativePath: string; +} + +export function validateFile(path: string, label: string): void { + if (!existsSync(path)) { + throw new Error(`${label} not found at ${path}.`); + } + if (statSync(path).size === 0) { + throw new Error(`${label} at ${path} is empty.`); + } +} + +function validateRuntimeBundle(wrapper: string, runtimeNode: string): void { + validateFile(wrapper, "Copilot runtime wrapper"); + validateFile(runtimeNode, "Copilot runtime.node"); +} + +function isExcluded(relativePath: string): boolean { + const parts = relativePath.split(sep); + const topLevel = parts[0]; + const fileName = parts.at(-1) ?? ""; + return ( + EXCLUDED_TOP_LEVEL.has(topLevel) || + /^tree-sitter.*\.wasm$/.test(topLevel) || + /^voice-.*\.js$/.test(topLevel) || + fileName === "cli-native.node" || + parts.includes("mediaremote-adapter") || + fileName.startsWith("copilot-runtime-bin") + ); +} + +function collectRuntimeAssets(sources: RuntimeArtifactSources): RuntimeAsset[] { + const assets: RuntimeAsset[] = []; + const visit = (directory: string): void => { + for (const entry of readdirSync(directory, { withFileTypes: true })) { + const source = join(directory, entry.name); + const sourceRelative = relative(sources.packageRoot, source); + if (isExcluded(sourceRelative)) { + continue; + } + if (entry.isDirectory()) { + visit(source); + continue; + } + if (!entry.isFile() && !entry.isSymbolicLink()) { + continue; + } + + const parts = sourceRelative.split(sep); + if (parts[0] === "prebuilds") { + if (parts[1] !== sources.platform || parts.length < 3) { + continue; + } + } + assets.push({ source, relativePath: sourceRelative }); + } + }; + visit(sources.packageRoot); + return assets.sort((left, right) => left.relativePath.localeCompare(right.relativePath)); +} + +function sourceFingerprint(assets: RuntimeAsset[]): string { + const hash = createHash("sha256"); + for (const asset of assets) { + const stat = lstatSync(asset.source); + hash.update(asset.relativePath).update("\0"); + hash.update(`${stat.size}:${stat.mtimeMs}`).update("\0"); + } + return hash.digest("hex").slice(0, 20); +} + +function makeExecutable(path: string): void { + if (process.platform === "win32") { + return; + } + const mode = statSync(path).mode; + if ((mode & 0o111) === 0) { + chmodSync(path, mode | 0o111); + } +} + +export function defaultRuntimeCacheRoot( + platform = process.platform, + home = homedir(), + environment: NodeJS.ProcessEnv = process.env +): string { + const cacheDirectory = + platform === "win32" + ? (environment.LOCALAPPDATA ?? join(home, "AppData", "Local")) + : platform === "darwin" + ? join(home, "Library", "Caches") + : (environment.XDG_CACHE_HOME ?? join(home, ".cache")); + return join(cacheDirectory, "github-copilot-sdk", "runtime"); +} + +export function materializeRuntimeBundle( + sources: RuntimeArtifactSources, + cacheRoot = defaultRuntimeCacheRoot(), + cacheKey = `${sources.platform}-${sourceFingerprint(collectRuntimeAssets(sources))}` +): string { + const assets = collectRuntimeAssets(sources); + const wrapperName = sources.platform.startsWith("win32") + ? "copilot-runtime.exe" + : "copilot-runtime"; + const prebuildDir = join("prebuilds", sources.platform); + const wrapperRelative = join(prebuildDir, wrapperName); + const runtimeNodeRelative = join(prebuildDir, "runtime.node"); + const sourceWrapper = assets.find((asset) => asset.relativePath === wrapperRelative)?.source; + const sourceRuntimeNode = assets.find( + (asset) => asset.relativePath === runtimeNodeRelative + )?.source; + validateRuntimeBundle(sourceWrapper ?? "", sourceRuntimeNode ?? ""); + + const installDir = join(cacheRoot, cacheKey); + const installedWrapper = join(installDir, wrapperRelative); + const installedRuntimeNode = join(installDir, runtimeNodeRelative); + if (existsSync(installDir)) { + validateRuntimeBundle(installedWrapper, installedRuntimeNode); + makeExecutable(installedWrapper); + return installedWrapper; + } + + mkdirSync(cacheRoot, { recursive: true }); + const stagingDir = mkdtempSync(join(cacheRoot, ".runtime-")); + try { + for (const asset of assets) { + const destination = join(stagingDir, asset.relativePath); + mkdirSync(dirname(destination), { recursive: true }); + copyFileSync(asset.source, destination); + } + const stagedWrapper = join(stagingDir, wrapperRelative); + makeExecutable(stagedWrapper); + renameSync(stagingDir, installDir); + } catch (error) { + if (!existsSync(installDir)) { + throw error; + } + validateRuntimeBundle(installedWrapper, installedRuntimeNode); + } finally { + rmSync(stagingDir, { recursive: true, force: true }); + } + + return installedWrapper; +} + +function isMusl(): boolean { + if (process.platform !== "linux") { + return false; + } + const report = process.report?.getReport() as + | { header?: { glibcVersionRuntime?: string } } + | undefined; + return report?.header?.glibcVersionRuntime === undefined; +} + +export function getRuntimePlatform( + platform = process.platform, + arch = process.arch, + musl = isMusl() +): string { + if (arch !== "x64" && arch !== "arm64") { + throw new Error(`Unsupported Copilot CLI architecture: ${arch}.`); + } + if (platform === "linux") { + return `${musl ? "linuxmusl" : "linux"}-${arch}`; + } + if (platform === "darwin" || platform === "win32") { + return `${platform}-${arch}`; + } + throw new Error(`Unsupported Copilot CLI platform: ${platform}-${arch}.`); +} + +export function getRuntimeReleaseAssetName(version: string, platform: string): string { + return `github-copilot-${version}-${platform}.tgz`; +} + +export function getRuntimePackageName(platform: string): string { + return `@github/copilot-sdk-${platform}`; +} + +export function resolvePackageRoot( + packageName: string, + searchPaths = require.resolve.paths(packageName) ?? [] +): string | undefined { + return searchPaths + .map((base) => join(base, ...packageName.split("/"))) + .find((candidate) => existsSync(join(candidate, "package.json"))); +} + +export async function ensureRuntimeBundle( + version: string, + options: EnsureRuntimeBundleOptions = {} +): Promise { + const platform = options.platform ?? getRuntimePlatform(); + // lgtm[js/trivial-conditional] This generated constant is true for internal canary builds. + if (COPILOT_CLI_USE_NPM_PACKAGE) { + const packageName = `@github/copilot-${platform}`; + const packageRoot = resolvePackageRoot(packageName, options.packageSearchPaths); + if (!packageRoot) { + throw new Error(`Could not resolve ${packageName} for Copilot CLI ${version}.`); + } + validateFile( + join(packageRoot, "prebuilds", platform, "runtime.node"), + "Copilot runtime.node" + ); + return materializeRuntimeBundle( + { packageRoot, platform }, + options.cacheRoot, + `${version}-${platform}` + ); + } + + const packageName = getRuntimePackageName(platform); + const packageRoot = resolvePackageRoot(packageName, options.packageSearchPaths); + if (!packageRoot) { + throw new Error( + `Could not resolve ${packageName}. Reinstall @github/copilot-sdk so its platform package is installed.` + ); + } + const wrapperName = platform.startsWith("win32") ? "copilot-runtime.exe" : "copilot-runtime"; + const prebuildDir = join(packageRoot, "prebuilds", platform); + const wrapper = join(prebuildDir, wrapperName); + try { + validateRuntimeBundle(wrapper, join(prebuildDir, "runtime.node")); + } catch (error) { + throw new Error( + `${packageName} is missing required Copilot CLI runtime files. Reinstall @github/copilot-sdk.`, + { cause: error } + ); + } + makeExecutable(wrapper); + return wrapper; +} diff --git a/nodejs/src/session.ts b/nodejs/src/session.ts index ed575a5154..eb4c6b7561 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"; @@ -16,14 +17,12 @@ import type { CurrentToolMetadata, McpOauthPendingRequestResponse, FactoryLogLine, - FactoryRunRequest, - FactoryExecuteResult, - FactoryJournalPutRequest, 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, @@ -43,6 +42,7 @@ import type { McpAuthRequest, PermissionHandler, PermissionRequest, + PermissionRequestResult, ContextTier, ReasoningEffort, ReasoningSummary, @@ -66,11 +66,14 @@ import type { UserInputResponse, } from "./types.js"; import { + FACTORY_AGENT_OPTION_KEYS, getFactoryDefinition, FactoryResumeError, isFactoryRunTerminal, type FactoryResumeErrorCode, + type FactoryListRunsOptions, type FactoryRunResult, + type FactoryAgentOptions, type RunOptions, type SessionFactoryApi, type FactoryContext, @@ -84,11 +87,35 @@ function isFactoryResumeErrorCode(value: unknown): value is FactoryResumeErrorCo value === "not_found" || value === "non_resumable" || value === "already_active" || - value === "reapproval_declined" || - value === "no_approval_provider" + 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. * This deserializes the numeric Unix-ms `timestamp` field on BaseHookInput @@ -255,7 +282,10 @@ class FactoryProgressBuffer { const lines = this.pending.splice(0); await this.flushTail; if (this.flushFailed) { - throw this.flushError; + console.warn( + "Ignoring a background factory progress flush failure after the factory body settled", + this.flushError + ); } if (lines.length > 0) { try { @@ -288,24 +318,6 @@ class FactoryProgressBuffer { } } -/** - * Reconcile the generated envelope with the public one. - * - * The two are identical at runtime. They differ only in how `result` is typed: - * the runtime returns any JSON value, but the schema models the field as an - * opaque node, which the generator renders as an object. {@link FactoryRunResult} - * corrects that for the factory surface without changing `x-opaque-json` - * handling for any other consumer, so the boundary needs a cast rather than a - * conversion. - * - * Delete this along with the {@link FactoryRunResult} override once the schema - * distinguishes opaque JSON values from opaque in-process values — - * github/copilot-agent-runtime#14122. - */ -function toPublicFactoryRunResult(envelope: WireFactoryRunResult): FactoryRunResult { - return envelope as FactoryRunResult; -} - async function awaitFactoryOperation( operation: () => Promise, signal: AbortSignal @@ -427,6 +439,7 @@ export class CopilotSession { private _capabilities: SessionCapabilities = {}; private openCanvasInstances: OpenCanvasInstance[] = []; private disconnected = false; + private onDisconnected?: () => void; /** @internal Client session API handlers, populated by CopilotClient during create/resume. */ clientSessionApis: ClientSessionApiHandlers = {}; @@ -442,6 +455,7 @@ export class CopilotSession { nameOrHandle: string | FactoryHandle, options?: RunOptions ): Promise => { + throwIfFactoryExecutionIsActive(); const name = typeof nameOrHandle === "string" ? nameOrHandle @@ -449,26 +463,31 @@ export class CopilotSession { if (options?.resumeFromRunId !== undefined) { return this.factory.resume(options.resumeFromRunId, { limits: options.limits, + notifyOnComplete: options.notifyOnComplete, + logPhaseNames: options.logPhaseNames, }); } const envelope = await this.rpc.factory.run({ name, - args: (options?.args === undefined - ? {} - : options.args) as FactoryRunRequest["args"], + args: options?.args === undefined ? {} : options.args, options: { limits: options?.limits, + notifyOnComplete: options?.notifyOnComplete, + logPhaseNames: options?.logPhaseNames, }, }); 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, + notifyOnComplete: options?.notifyOnComplete, + logPhaseNames: options?.logPhaseNames, }); } catch (error) { if ( @@ -485,13 +504,16 @@ export class CopilotSession { } return this.settleFactoryRun(response.run); }) as SessionFactoryApi["resume"], - getRun: async (runId) => toPublicFactoryRunResult(await this.rpc.factory.getRun({ runId })), + getRun: async (runId) => this.rpc.factory.getRun({ runId }), waitForRun: (runId, options) => this.waitForFactoryRun(runId, options?.signal), - listRuns: async () => (await this.rpc.factory.listRuns()).runs, + listRuns: (async (options?: FactoryListRunsOptions) => { + const page = await this.rpc.factory.listRuns(options ?? {}); + return options === undefined ? page.runs : page; + }) as SessionFactoryApi["listRuns"], getRunDetail: (runId) => this.rpc.factory.getRunDetail({ runId }), getRunProgress: (runId, options = {}) => this.rpc.factory.getRunProgress({ runId, ...options }), - cancel: async (runId) => toPublicFactoryRunResult(await this.rpc.factory.cancel({ runId })), + cancel: async (runId) => this.rpc.factory.cancel({ runId }), }; /** @@ -503,7 +525,7 @@ export class CopilotSession { */ private settleFactoryRun(envelope: WireFactoryRunResult): Promise { if (isFactoryRunTerminal(envelope.status)) { - return Promise.resolve(toPublicFactoryRunResult(envelope)); + return Promise.resolve(envelope); } return this.waitForFactoryRun(envelope.runId); } @@ -562,7 +584,7 @@ export class CopilotSession { rereadRequested = false; const envelope = await this.rpc.factory.getRun({ runId }); if (isFactoryRunTerminal(envelope.status)) { - finish(() => resolve(toPublicFactoryRunResult(envelope))); + finish(() => resolve(envelope)); return; } } while (rereadRequested && !settled); @@ -606,11 +628,16 @@ export class CopilotSession { private connection: MessageConnection, private _workspacePath?: string, traceContextProvider?: TraceContextProvider, - options?: { mcpAuthHandler?: McpAuthHandler; managedSettingsEnabled?: boolean } + options?: { + mcpAuthHandler?: McpAuthHandler; + managedSettingsEnabled?: boolean; + onDisconnected?: () => void; + } ) { this.traceContextProvider = traceContextProvider; this.mcpAuthHandler = options?.mcpAuthHandler; this.managedSettingsEnabled = options?.managedSettingsEnabled === true; + this.onDisconnected = options?.onDisconnected; } /** @@ -747,7 +774,7 @@ export class CopilotSession { const unsubscribe = this.on((event) => { if (event.type === "assistant.message") { lastAssistantMessage = event; - } else if (event.type === "session.idle") { + } else if (event.type === "session.idle" && event.data.mode !== "autopilot") { resolveOutcome({ kind: "idle" }); } else if (event.type === "session.error") { const error = new Error(event.data.message); @@ -787,7 +814,11 @@ export class CopilotSession { /** @internal */ _markDisconnected(): void { + if (this.disconnected) { + return; + } this.disconnected = true; + this._runOnDisconnected(); this.eventHandlers.clear(); this.typedEventHandlers.clear(); this.toolHandlers.clear(); @@ -808,6 +839,17 @@ export class CopilotSession { this.transformCallbacks?.clear(); } + /** @internal */ + _runOnDisconnected(): void { + this.onDisconnected?.(); + this.onDisconnected = undefined; + } + + /** @internal */ + _setOnDisconnected(callback: () => void): void { + this.onDisconnected = callback; + } + /** * Subscribes to events from this session. * @@ -1124,17 +1166,26 @@ 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 }); + await this.rpc.permissions.handlePendingPermissionRequest( + decisionContext === undefined + ? { requestId, result } + : { requestId, result, decisionContext } + ); } catch (error) { if (this.disconnected) { return; @@ -1379,7 +1430,7 @@ export class CopilotSession { try { const context: FactoryContext = { runId: params.runId, - args: params.args as JsonValue, + args: params.args, session: self, signal: controller.signal, phase: (title: string) => { @@ -1392,17 +1443,17 @@ export class CopilotSession { }, 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: { - label: options.label, - schema: options.schema, - model: options.model, - }, + opts, }), controller.signal ); @@ -1452,8 +1503,7 @@ export class CopilotSession { runId: params.runId, executionToken: params.executionToken, key, - resultJson: - result as FactoryJournalPutRequest["resultJson"], + resultJson: result, }), controller.signal ); @@ -1465,12 +1515,19 @@ export class CopilotSession { throw new Error("nested factories are not supported"); }, }; - const result = await definition.run(context); + 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 } as FactoryExecuteResult; + return { result }; } finally { try { await progress.close(); @@ -1595,7 +1652,13 @@ export class CopilotSession { } try { const result = await this.elicitationHandler(context); - await this.rpc.ui.handlePendingElicitation({ requestId, result }); + await this.rpc.ui.handlePendingElicitation({ + requestId, + result: { + action: result.action, + ...(result.content ? { content: result.content } : {}), + }, + }); } catch { // Handler failed — attempt to cancel so the request doesn't hang try { diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index b3d572496b..128ced3d68 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -11,6 +11,7 @@ import type { Canvas } from "./canvas.js"; import type { SessionFsProvider } from "./sessionFsProvider.js"; import type { CopilotRequestHandler } from "./copilotRequestHandler.js"; import type { + AutoTier, PermissionRequest as GeneratedPermissionRequest, PermissionRequestedData as GeneratedPermissionRequestedData, PermissionRequestedEvent as GeneratedPermissionRequestedEvent, @@ -19,7 +20,10 @@ import type { SessionEvent as GeneratedSessionEvent, } from "./generated/session-events.js"; import type { CopilotSession } from "./session.js"; +import type { FactoryJsonSchema, JsonValue } from "./factory.js"; import type { + GitHubTokenAcquireRequest, + GitHubTokenAcquireResult, GitHubTelemetryNotification, ModelBillingTokenPrices, OpenCanvasInstance, @@ -30,10 +34,38 @@ import type { ToolSet } from "./toolSet.js"; export type { RemoteSessionMode } from "./generated/rpc.js"; export type { CurrentToolMetadata } from "./generated/rpc.js"; export type { + GitHubTokenAcquireReason, + GitHubTokenAcquireResult, GitHubTelemetryNotification, GitHubTelemetryEvent, GitHubTelemetryClientInfo, } from "./generated/rpc.js"; + +/** + * Arguments passed to a session's {@link GitHubTokenProvider}. + * + * The callback registration identifier is intentionally kept inside the SDK. + */ +export type GitHubTokenProviderArgs = Pick< + GitHubTokenAcquireRequest, + "host" | "sessionId" | "reason" +>; + +/** Tagged token or cancellation returned by a {@link GitHubTokenProvider}. */ +export type GitHubTokenProviderResult = GitHubTokenAcquireResult; + +/** + * Acquires a GitHub token for one session. + * + * A token result must include `expiresIn`: the positive number of seconds of + * remaining lifetime when the callback completes. Production GitHub tokens + * typically last eight hours. Initial cancellation, callback errors, and + * invalid token responses reject session creation or resume instead of falling + * back to ambient authentication. + */ +export type GitHubTokenProvider = ( + args: GitHubTokenProviderArgs +) => GitHubTokenProviderResult | Promise; export type { ModelBillingTokenPrices, ModelBillingTokenPricesLongContext, @@ -41,7 +73,7 @@ export type { export type SessionEvent = | Exclude | PermissionRequestedEvent; -export type { ReasoningSummary } from "./generated/session-events.js"; +export type { AutoTier, ReasoningSummary } from "./generated/session-events.js"; export type { SessionFsProvider } from "./sessionFsProvider.js"; export { createSessionFsAdapter } from "./sessionFsProvider.js"; export type { SessionFsFileInfo } from "./sessionFsProvider.js"; @@ -52,6 +84,13 @@ 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, + PermissionResponseCapability, +} from "./generated/rpc.js"; export type { CopilotRequestContext } from "./copilotRequestHandler.js"; export { CopilotRequestHandler, @@ -268,6 +307,37 @@ export type InternalRuntimeConnection = RuntimeConnection | ParentProcessRuntime */ export type CopilotClientMode = "empty" | "copilot-cli"; +/** + * Identity of the integrating application, declared once on the `server.connect` + * handshake so the telemetry the runtime emits on this connection is attributed + * to a single, consistent surface rather than to the runtime's own build. + * + * All fields are optional; omit any of them (or the whole object) to keep the + * runtime's default attribution. Version fields are ignored by the runtime + * unless they look like a version string. + */ +export interface CopilotClientInfo { + /** + * Name of the application using the SDK, e.g. `"acme-developer-portal"`. + */ + applicationName?: string; + + /** + * Version of the application using the SDK, e.g. `"2.4.0"`. + */ + applicationVersion?: string; + + /** + * Optional name of a specific integration within the application, such as an extension or plugin. + */ + integrationName?: string; + + /** + * Optional version of the integration identified by `integrationName`. + */ + integrationVersion?: string; +} + export interface CopilotClientOptions { /** * How to connect to the Copilot runtime. When omitted, defaults to @@ -303,6 +373,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"`). @@ -432,6 +509,16 @@ export interface CopilotClientOptions { */ enableRemoteSessions?: boolean; + /** + * Identity of the integrating application, forwarded to the runtime on the + * `server.connect` handshake. Declaring it lets the telemetry the runtime + * emits on this connection be attributed to a single, consistent surface + * (e.g. the application and its Copilot integration) instead of the + * runtime's own build. All fields are optional; omit it to keep the default + * attribution. + */ + clientInfo?: CopilotClientInfo; + /** * @internal Hook used by `joinSession()` to construct a client that talks * to its parent process over stdio. Not part of the public API. @@ -451,7 +538,7 @@ export type ToolBinaryResult = { description?: string; }; -export type ToolTelemetry = Record | undefined>; +export type ToolTelemetry = Record | undefined>; export type ToolResultObject = { textResultForLlm: string; @@ -656,6 +743,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; } /** @@ -672,6 +770,7 @@ export function defineTool( skipPermission?: boolean; defer?: "auto" | "never"; metadata?: Record; + isTerminal?: boolean; } ): Tool { return { name, ...config }; @@ -1100,7 +1199,7 @@ export type SystemMessageConfig = | SystemMessageReplaceConfig | SystemMessageCustomizeConfig; -import type { PermissionDecisionRequest } from "./generated/rpc.js"; +import type { PermissionDecisionRequest, PermissionDecisionContext } from "./generated/rpc.js"; /** * Permission request types from the server. This is the generated @@ -1136,10 +1235,51 @@ export type PermissionRequestedEvent = Omit Promise | PermissionRequestResult; +) => + | Promise + | PermissionRequestResult + | AttributedPermissionResult; /** * Approves permission requests when managed settings are disabled. @@ -1167,7 +1307,7 @@ export const defaultJoinSessionPermissionHandler: PermissionHandler = // ============================================================================ /** - * Request for user input from the agent (enables ask_user tool) + * Legacy question-and-answer request from the `ask_user` tool. */ export interface UserInputRequest { /** @@ -1994,6 +2134,30 @@ export interface FactoryMeta { 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; } @@ -2007,6 +2171,17 @@ export interface FactoryMeta { * provider-level choices are conceptually per-provider rather than global. */ export interface CapiSessionOptions { + /** + * Routing preference used when the session model is `auto`. + * Requires a runtime with Auto tier support and V2 Auto routing. + * + * When omitted on create, the runtime uses its default routing behavior. + * The runtime persists this preference across cold resume; an explicit tier + * on cold resume overrides the persisted value. For an already-resident + * session, omission preserves the current tier and a different tier is rejected. + */ + autoTier?: AutoTier; + /** * Whether to use the WebSocket transport for the CAPI Responses API. * @@ -2076,6 +2251,56 @@ export interface GitHubMcpToolConfig { disableFormDeferral?: boolean; } +/** Well-known managed bypass-permissions policies. */ +export const DisableBypassPermissionsModes = { + /** Turn off bypass-permissions mode entirely. */ + Disable: "disable", + /** Permit automatic bypass but block full allow-all. */ + AllowAutoOnly: "allow-auto-only", +} as const; + +/** + * 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 { + /** + * Restricts bypass-permissions mode for the session. See + * {@link DisableBypassPermissionsModes} for well-known values. Unknown + * values are forwarded so newer runtime policies fail closed. + */ + disableBypassPermissionsMode?: string; + /** 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; +} + +/** Selects the model-facing shape of the built-in `ask_user` tool. */ +export type AskUserVariant = "legacy" | "elicitation"; + /** * Shared configuration fields used by both {@link SessionConfig} (for * creating a new session) and {@link ResumeSessionConfig} (for resuming @@ -2256,6 +2481,13 @@ export interface SessionConfigBase { */ excludedBuiltinAgents?: string[]; + /** + * Built-in skill names to include in the session. In `mode: "empty"`, + * omitting this option excludes all runtime-bundled skills; specifying names + * opts those built-ins back in. Skills from other sources remain eligible. + */ + includedBuiltinSkills?: string[]; + /** * Custom provider configuration (BYOK - Bring Your Own Key). * When specified, uses the provided API endpoint instead of the Copilot API. @@ -2312,6 +2544,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. * @@ -2372,10 +2612,20 @@ export interface SessionConfigBase { /** * Handler for user input requests from the agent. - * When provided, enables the ask_user tool allowing the agent to ask questions. + * When provided with the default `legacy` {@link AskUserVariant}, enables the + * question-and-answer form of the `ask_user` tool. */ onUserInputRequest?: UserInputHandler; + /** + * Selects the model-facing shape of the built-in `ask_user` tool. + * + * The default is `"legacy"`. To use `"elicitation"`, also provide + * {@link onElicitationRequest} so the host can answer structured forms. + * The runtime resolves this option when it creates or cold-resumes the session. + */ + askUserVariant?: AskUserVariant; + /** * Handler for elicitation requests from the agent. * When provided, the server calls back to this client for form-based UI dialogs. @@ -2535,6 +2785,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. @@ -2559,6 +2816,16 @@ export interface SessionConfigBase { */ gitHubToken?: string; + /** + * Acquires short-lived GitHub credentials for this session on demand. + * + * Mutually exclusive with {@link SessionConfigBase.gitHubToken}. The + * callback receives the effective GitHub host, the session ID when known, + * and whether this is the initial acquisition or a refresh. Its opaque + * registration ID remains internal to the SDK. + */ + gitHubTokenProvider?: GitHubTokenProvider; + /** * Opt-in: when true, the runtime self-fetches enterprise managed settings * (bypass-permissions policy) at session bootstrap using the session's @@ -2567,6 +2834,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 bypass-mode + * restrictions are composed fail-closed. + * + * 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 @@ -2647,6 +2939,12 @@ export interface SessionConfigBase { */ createSessionFsProvider?: (session: CopilotSession) => SessionFsProvider; + /** + * Feature-flag values resolved by the host for this session. + * Re-supply them when resuming after a runtime restart. + */ + featureFlags?: Record; + /** * ExP assignment ("flight") data injected by a trusted integrator, in the * same JSON shape the Copilot CLI fetches from the experimentation service @@ -2709,6 +3007,20 @@ export interface ResumeSessionConfig extends SessionConfigBase { openCanvases?: OpenCanvasInstance[]; } +/** + * Options that only an extension join may supply, kept off {@link ResumeSessionConfig} + * because the runtime ignores them for every other kind of connection. + * + * @internal + */ +export interface ExtensionJoinOptions { + /** + * Names of sensitive environment variables the extension asks the host to grant. + * Sent on the `session.resume` wire payload as `requestedEnvironmentVariables`. + */ + requestedEnvironmentVariables?: string[]; +} + /** * Arguments passed to a {@link BearerTokenProvider} callback when the runtime needs a * fresh bearer token for a BYOK provider. diff --git a/nodejs/test/client-api-codegen.test.ts b/nodejs/test/client-api-codegen.test.ts new file mode 100644 index 0000000000..9331ad7689 --- /dev/null +++ b/nodejs/test/client-api-codegen.test.ts @@ -0,0 +1,115 @@ +import { describe, expect, it } from "vitest"; + +import { emitClientSessionApiRegistration as emitGoClientSessionApiRegistration } from "../../scripts/codegen/go.ts"; +import { emitClientSessionApiRegistration as emitPythonClientSessionApiRegistration } from "../../scripts/codegen/python.ts"; +import { emitClientSessionApiRegistration as emitTypeScriptClientSessionApiRegistration } from "../../scripts/codegen/typescript.ts"; + +const clientSessionSchema: Record = { + mixed: { + visible: { + rpcMethod: "mixed.visible", + params: { + type: "object", + title: "VisibleRequest", + properties: { + sessionId: { type: "string" }, + }, + required: ["sessionId"], + }, + result: { + type: "object", + title: "VisibleResult", + properties: {}, + }, + }, + secret: { + rpcMethod: "mixed.secret", + visibility: "internal", + params: { + $ref: "#/definitions/InternalRequest", + }, + result: { + $ref: "#/definitions/InternalResult", + }, + }, + }, + internalOnly: { + hidden: { + rpcMethod: "internalOnly.hidden", + visibility: "internal", + params: { + $ref: "#/definitions/InternalRequest", + }, + result: { + $ref: "#/definitions/InternalResult", + }, + }, + }, +}; + +const allInternalClientSessionSchema: Record = { + internalOnly: clientSessionSchema.internalOnly, +}; + +function expectOnlyPublicClientSessionHandlers(code: string): void { + expect(code).toContain("mixed.visible"); + expect(code).not.toContain("mixed.secret"); + expect(code).not.toContain("internalOnly.hidden"); + expect(code).not.toContain("InternalRequest"); + expect(code).not.toContain("InternalResult"); +} + +describe("client-session API codegen", () => { + it("excludes internal methods from TypeScript handlers", () => { + const code = emitTypeScriptClientSessionApiRegistration(clientSessionSchema).join("\n"); + const allInternalCode = emitTypeScriptClientSessionApiRegistration( + allInternalClientSessionSchema + ).join("\n"); + + expectOnlyPublicClientSessionHandlers(code); + expect(code).not.toContain("InternalOnlyHandler"); + expect(allInternalCode).toContain("export interface ClientSessionApiHandlers {"); + expect(allInternalCode).toContain("export function registerClientSessionApiHandlers("); + expect(allInternalCode).not.toContain("InternalOnlyHandler"); + }); + + it("excludes internal methods from Go handlers", () => { + const lines: string[] = []; + emitGoClientSessionApiRegistration(lines, clientSessionSchema, (name) => name, new Map()); + const code = lines.join("\n"); + const allInternalLines: string[] = []; + emitGoClientSessionApiRegistration( + allInternalLines, + allInternalClientSessionSchema, + (name) => name, + new Map() + ); + const allInternalCode = allInternalLines.join("\n"); + + expectOnlyPublicClientSessionHandlers(code); + expect(code).not.toContain("InternalOnlyHandler"); + expect(allInternalCode).toContain("type ClientSessionAPIHandlers struct {"); + expect(allInternalCode).toContain("func RegisterClientSessionAPIHandlers("); + expect(allInternalCode).not.toContain("InternalOnlyHandler"); + expect(allInternalCode).not.toContain("clientSessionHandlerError"); + }); + + it("excludes internal methods from Python handlers", () => { + const lines: string[] = []; + emitPythonClientSessionApiRegistration(lines, clientSessionSchema, (name) => name); + const code = lines.join("\n"); + const allInternalLines: string[] = []; + emitPythonClientSessionApiRegistration( + allInternalLines, + allInternalClientSessionSchema, + (name) => name + ); + const allInternalCode = allInternalLines.join("\n"); + + expectOnlyPublicClientSessionHandlers(code); + expect(code).not.toContain("InternalOnlyHandler"); + expect(allInternalCode).toContain("class ClientSessionApiHandlers:"); + expect(allInternalCode).toContain("def register_client_session_api_handlers("); + expect(allInternalCode).not.toContain("InternalOnlyHandler"); + }); +}); diff --git a/nodejs/test/client.test.ts b/nodejs/test/client.test.ts index bbe6fbe666..2abba2fcb6 100644 --- a/nodejs/test/client.test.ts +++ b/nodejs/test/client.test.ts @@ -3,14 +3,19 @@ import { EventEmitter } from "node:events"; import { PassThrough } from "stream"; import { mkdtempSync } from "node:fs"; import { tmpdir } from "node:os"; -import { join } from "node:path"; +import { join, resolve } from "node:path"; import { describe, expect, it, onTestFinished, vi } from "vitest"; import { approveAll, + createAttributedPermissionResult, CopilotClient, createCanvas, + DisableBypassPermissionsModes, RuntimeConnection, + type CapiSessionOptions, + type CopilotClientOptions, type GitHubTelemetryNotification, + type ManagedSettings, type ModelInfo, } from "../src/index.js"; import { CopilotSession } from "../src/session.js"; @@ -57,6 +62,73 @@ describe("approveAll", () => { }); describe("CopilotClient", () => { + it.each([ + { + source: "connection path", + connection: RuntimeConnection.forStdio({ path: "/explicit/copilot" }), + env: {}, + expected: "/explicit/copilot", + }, + { + source: "COPILOT_CLI_PATH", + connection: RuntimeConnection.forStdio(), + env: { COPILOT_CLI_PATH: "/environment/copilot" }, + expected: "/environment/copilot", + }, + ])( + "preserves explicit child-process override from $source", + ({ connection, env, expected }) => { + const client = new CopilotClient({ connection, env }); + + expect((client as any).resolvedCliPath).toBe(expected); + } + ); + + 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()); @@ -83,6 +155,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; @@ -170,6 +327,39 @@ describe("CopilotClient", () => { }); }); + it("forwards the ask-user variant on create and cold 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 onElicitationRequest = async () => ({ action: "decline" as const }); + + const session = await client.createSession({ + askUserVariant: "elicitation", + onElicitationRequest, + }); + await client.resumeSession(session.sessionId, { + askUserVariant: "elicitation", + onElicitationRequest, + }); + + expect(spy.mock.calls.find(([method]) => method === "session.create")![1]).toMatchObject({ + askUserVariant: "elicitation", + requestElicitation: true, + }); + expect(spy.mock.calls.find(([method]) => method === "session.resume")![1]).toMatchObject({ + askUserVariant: "elicitation", + requestElicitation: true, + }); + }); + it("omits GitHub MCP tool config when unset", async () => { const client = new CopilotClient(); await client.start(); @@ -491,6 +681,99 @@ describe("CopilotClient", () => { expect(payload.openCanvasInstances).toBeUndefined(); }); + it("forwards an extension environment request and applies the grant to process.env", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => stopClient(client)); + onTestFinished(() => { + delete process.env.SDK_TEST_GRANTED_TOKEN; + }); + + const spy = vi + .spyOn((client as any).connection!, "sendRequest") + .mockImplementation(async (method: string, params: any) => { + if (method === "session.resume") { + return { + sessionId: params.sessionId, + grantedEnvironmentVariables: { SDK_TEST_GRANTED_TOKEN: "granted-value" }, + }; + } + throw new Error(`Unexpected method: ${method}`); + }); + + await client.resumeSessionForExtension( + "session-env", + { onPermissionRequest: defaultJoinSessionPermissionHandler }, + undefined, + { requestedEnvironmentVariables: ["SDK_TEST_GRANTED_TOKEN"] } + ); + + const payload = spy.mock.calls.find(([method]) => method === "session.resume")![1] as any; + expect(payload.requestedEnvironmentVariables).toEqual(["SDK_TEST_GRANTED_TOKEN"]); + expect(process.env.SDK_TEST_GRANTED_TOKEN).toBe("granted-value"); + }); + + it("ignores granted variables the extension never requested", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => stopClient(client)); + onTestFinished(() => { + delete process.env.SDK_TEST_GRANTED_TOKEN; + }); + + vi.spyOn((client as any).connection!, "sendRequest").mockImplementation( + async (method: string, params: any) => { + if (method === "session.resume") { + return { + sessionId: params.sessionId, + grantedEnvironmentVariables: { + SDK_TEST_GRANTED_TOKEN: "granted-value", + SDK_TEST_SMUGGLED: "not-approved", + }, + }; + } + throw new Error(`Unexpected method: ${method}`); + } + ); + + await client.resumeSessionForExtension( + "session-env-extra", + { onPermissionRequest: defaultJoinSessionPermissionHandler }, + undefined, + { requestedEnvironmentVariables: ["SDK_TEST_GRANTED_TOKEN"] } + ); + + expect(process.env.SDK_TEST_GRANTED_TOKEN).toBe("granted-value"); + // The user approved one name, so a host answering with a second one + // cannot widen the grant. + expect(process.env.SDK_TEST_SMUGGLED).toBeUndefined(); + }); + + it("omits the environment request when a resume does not ask for one", 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, + grantedEnvironmentVariables: { SDK_TEST_UNREQUESTED: "leaked" }, + }; + } + throw new Error(`Unexpected method: ${method}`); + }); + + await client.resumeSession("session-no-env", { onPermissionRequest: approveAll }); + + const payload = spy.mock.calls.find(([method]) => method === "session.resume")![1] as any; + expect(payload).not.toHaveProperty("requestedEnvironmentVariables"); + // A grant is only honored for a request this client actually made. + expect(process.env.SDK_TEST_UNREQUESTED).toBeUndefined(); + }); + it("forwards reasoningSummary in session.create and session.resume", async () => { const client = new CopilotClient(); await client.start(); @@ -714,6 +997,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(); @@ -730,12 +1075,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 }, }); @@ -747,9 +1094,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 }); }); @@ -984,7 +1333,7 @@ describe("CopilotClient", () => { expect(resumePayload.expAssignments).toBeUndefined(); }); - it("forwards capi options in session.create and session.resume", async () => { + it("forwards featureFlags in session.create and session.resume", async () => { const client = new CopilotClient(); await client.start(); onTestFinished(() => stopClient(client)); @@ -996,14 +1345,15 @@ describe("CopilotClient", () => { if (method === "session.resume") return { sessionId: params.sessionId }; throw new Error(`Unexpected method: ${method}`); }); + const featureFlags = { ENABLED_TEST_FLAG: true, DISABLED_TEST_FLAG: false }; const session = await client.createSession({ onPermissionRequest: approveAll, - capi: { enableWebSocketResponses: false }, + featureFlags, }); await client.resumeSession(session.sessionId, { onPermissionRequest: approveAll, - capi: { enableWebSocketResponses: false }, + featureFlags, }); const createPayload = spy.mock.calls.find( @@ -1012,10 +1362,55 @@ describe("CopilotClient", () => { const resumePayload = spy.mock.calls.find( ([method]) => method === "session.resume" )![1] as any; - expect(createPayload.capi).toEqual({ enableWebSocketResponses: false }); - expect(resumePayload.capi).toEqual({ enableWebSocketResponses: false }); + expect(createPayload.featureFlags).toEqual(featureFlags); + expect(resumePayload.featureFlags).toEqual(featureFlags); }); + it.each([ + undefined, + {}, + { enableWebSocketResponses: false }, + { enableWebSocketResponses: true }, + { autoTier: "efficiency" }, + { autoTier: "balance" }, + { autoTier: "intelligence" }, + { autoTier: "balance", enableWebSocketResponses: false }, + ] satisfies (CapiSessionOptions | undefined)[])( + "forwards capi options %j in session.create and session.resume", + async (capi) => { + 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 session = await client.createSession({ + onPermissionRequest: approveAll, + model: "auto", + capi, + }); + await client.resumeSession(session.sessionId, { + onPermissionRequest: approveAll, + capi, + }); + + 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(JSON.parse(JSON.stringify(createPayload)).capi).toEqual(capi); + expect(JSON.parse(JSON.stringify(resumePayload)).capi).toEqual(capi); + } + ); + it("forwards pluginDirectories and largeOutput in session.create and session.resume", async () => { const client = new CopilotClient(); await client.start(); @@ -1030,6 +1425,7 @@ describe("CopilotClient", () => { }); const pluginDirs = ["/tmp/plugins/a", "/tmp/plugins/b"]; + const disabledMcpServers = ["local-files", "remote-github"]; const largeOutput = { enabled: true, maxSizeBytes: 1024, @@ -1044,11 +1440,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, }); @@ -1059,8 +1457,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); }); @@ -1661,6 +2061,134 @@ describe("CopilotClient", () => { spy.mockRestore(); }); + it("sends includedBuiltinSkills=[] in the empty-mode post-create options patch", async () => { + const client = new CopilotClient({ mode: "empty", baseDirectory: "/tmp/copilot-test" }); + 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 ?? "s1" }; + if (method === "session.options.update") return {}; + throw new Error(`Unexpected method: ${method}`); + }); + await client.createSession({ onPermissionRequest: approveAll, availableTools: [] }); + + const patch = spy.mock.calls.find((c) => c[0] === "session.options.update")![1] as any; + expect(patch.includedBuiltinSkills).toEqual([]); + expect(patch.installedPlugins).toEqual([]); + }); + + it("sends includedBuiltinSkills=[] in the empty-mode post-resume options patch", async () => { + const client = new CopilotClient({ mode: "empty", baseDirectory: "/tmp/copilot-test" }); + 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 ?? "s1" }; + if (method === "session.resume") return { sessionId: params.sessionId }; + if (method === "session.options.update") return {}; + throw new Error(`Unexpected method: ${method}`); + }); + await client.createSession({ onPermissionRequest: approveAll, availableTools: [] }); + spy.mockClear(); + await client.resumeSession("s1", { onPermissionRequest: approveAll, availableTools: [] }); + + const patch = spy.mock.calls.find((c) => c[0] === "session.options.update")![1] as any; + expect(patch.includedBuiltinSkills).toEqual([]); + }); + + it("preserves an explicit built-in skill allowlist after empty-mode resume", async () => { + const client = new CopilotClient({ mode: "empty", baseDirectory: "/tmp/copilot-test" }); + await client.start(); + onTestFinished(() => stopClient(client)); + + const spy = vi + .spyOn((client as any).connection!, "sendRequest") + .mockImplementation(async (method: string) => { + if (method === "session.resume") return { sessionId: "s1" }; + if (method === "session.options.update") return { success: true }; + throw new Error(`Unexpected method: ${method}`); + }); + await client.resumeSession("s1", { + onPermissionRequest: approveAll, + availableTools: [], + includedBuiltinSkills: ["code-review"], + }); + + const patch = spy.mock.calls.find((c) => c[0] === "session.options.update")![1] as any; + expect(patch.includedBuiltinSkills).toEqual(["code-review"]); + }); + + it("keeps includedBuiltinSkills=[] even when the caller opts into custom skills in empty mode", async () => { + const client = new CopilotClient({ mode: "empty", baseDirectory: "/tmp/copilot-test" }); + 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 ?? "s1" }; + if (method === "session.options.update") return {}; + throw new Error(`Unexpected method: ${method}`); + }); + await client.createSession({ + onPermissionRequest: approveAll, + availableTools: [], + enableSkills: true, + skillDirectories: ["/tmp/custom-skills"], + }); + + const createPayload = spy.mock.calls.find((c) => c[0] === "session.create")![1] as any; + expect(createPayload.enableSkills).toBe(true); + expect(createPayload.skillDirectories).toEqual(["/tmp/custom-skills"]); + const patch = spy.mock.calls.find((c) => c[0] === "session.options.update")![1] as any; + expect(patch.includedBuiltinSkills).toEqual([]); + }); + + it("preserves an explicit built-in skill allowlist in empty mode", async () => { + const client = new CopilotClient({ mode: "empty", baseDirectory: "/tmp/copilot-test" }); + 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.options.update") return { success: true }; + throw new Error(`Unexpected method: ${method}`); + }); + await client.createSession({ + onPermissionRequest: approveAll, + availableTools: [], + includedBuiltinSkills: ["code-review"], + }); + + const patch = spy.mock.calls.find((c) => c[0] === "session.options.update")![1] as any; + expect(patch.includedBuiltinSkills).toEqual(["code-review"]); + }); + + it("does not send includedBuiltinSkills in copilot-cli mode", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => stopClient(client)); + + const spy = vi.spyOn((client as any).connection!, "sendRequest"); + await client.createSession({ onPermissionRequest: approveAll }); + + const patch = spy.mock.calls.find((c) => c[0] === "session.options.update"); + // copilot-cli mode sends no post-create options patch at all here. + if (patch) { + expect((patch[1] as any).includedBuiltinSkills).toBeUndefined(); + } + const createPayload = spy.mock.calls.find((c) => c[0] === "session.create")![1] as any; + expect(createPayload.includedBuiltinSkills).toBeUndefined(); + spy.mockRestore(); + }); + it("forwards continuePendingWork in session.resume request", async () => { const client = new CopilotClient(); await client.start(); @@ -2099,6 +2627,17 @@ describe("CopilotClient", () => { expect((client as any).isExternalServer).toBe(true); }); + it("should parse bracketed IPv6 host:port URL format", () => { + const client = new CopilotClient({ + connection: RuntimeConnection.forUri("[::1]:9000"), + logLevel: "error", + }); + + expect((client as any).runtimePort).toBe(9000); + expect((client as any).actualHost).toBe("::1"); + expect((client as any).isExternalServer).toBe(true); + }); + it("should parse http://host:port URL format", () => { const client = new CopilotClient({ connection: RuntimeConnection.forUri("http://localhost:7000"), @@ -2110,6 +2649,26 @@ describe("CopilotClient", () => { expect((client as any).isExternalServer).toBe(true); }); + it("should parse http://[ipv6]:port URL format", () => { + const client = new CopilotClient({ + connection: RuntimeConnection.forUri("http://[::1]:7000"), + logLevel: "error", + }); + + expect((client as any).runtimePort).toBe(7000); + expect((client as any).actualHost).toBe("::1"); + expect((client as any).isExternalServer).toBe(true); + }); + + it("should reject a bracketed non-IPv6 host", () => { + expect(() => { + new CopilotClient({ + connection: RuntimeConnection.forUri("[not-ipv6]:1234"), + logLevel: "error", + }); + }).toThrow(/Invalid cliUrl format/); + }); + it("should parse https://host:port URL format", () => { const client = new CopilotClient({ connection: RuntimeConnection.forUri("https://example.com:443"), @@ -2824,7 +3383,7 @@ describe("CopilotClient", () => { const payload = spy.mock.calls.find((c) => c[0] === "session.create")![1] as any; expect(payload.commands).toEqual([ { name: "deploy", description: "Deploy the app" }, - { name: "rollback", description: undefined }, + { name: "rollback", description: "" }, ]); }); @@ -3592,3 +4151,206 @@ 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 managedSettings = { + permissions: { + disableBypassPermissionsMode: DisableBypassPermissionsModes.AllowAutoOnly, + deny: ["Shell(git push)"], + ask: ["Domain(publish.example)"], + allow: ["Read(**)"], + }, + } satisfies ManagedSettings; + const params = await captureCreateParams({ + managedSettings, + }); + expect(params.managedSettings).toEqual({ + permissions: { + disableBypassPermissionsMode: "allow-auto-only", + deny: ["Shell(git push)"], + ask: ["Domain(publish.example)"], + allow: ["Read(**)"], + }, + }); + }); + + it("forwards the disable bypass-permissions mode", async () => { + const managedSettings = { + permissions: { + disableBypassPermissionsMode: DisableBypassPermissionsModes.Disable, + }, + } satisfies ManagedSettings; + const params = await captureCreateParams({ managedSettings }); + + expect(params.managedSettings).toEqual({ + permissions: { + disableBypassPermissionsMode: "disable", + }, + }); + }); + + it("forwards unknown bypass-permissions modes", async () => { + const managedSettings = { + permissions: { + disableBypassPermissionsMode: "future-fail-closed-mode", + }, + } satisfies ManagedSettings; + const params = await captureCreateParams({ managedSettings }); + + expect(params.managedSettings).toEqual(managedSettings); + }); + + 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)"] }, + }); + }); +}); + +describe("connect handshake clientInfo", () => { + // Drives verifyProtocolVersion() against a stubbed connection so we can + // observe the `connect` params without spawning a runtime. `connect` maps to + // connection.sendRequest("connect", params) in the generated internal RPC. + async function captureConnectParams( + options: Partial> = {} + ): Promise> { + const client = new CopilotClient({ + connection: RuntimeConnection.forUri("localhost:1234"), + ...options, + }); + const sendRequest = vi.fn(async (method: string, _params?: unknown) => { + if (method === "connect") return { protocolVersion: 3 }; + throw new Error(`Unexpected method: ${method}`); + }); + (client as any).connection = { sendRequest }; + + await (client as any).verifyProtocolVersion(); + + const connectCall = sendRequest.mock.calls.find(([method]) => method === "connect"); + expect(connectCall, "connect was not called").toBeTruthy(); + return connectCall![1] as Record; + } + + it("forwards a declared client identity on the connect handshake", async () => { + const clientInfo = { + applicationName: "acme-developer-portal", + applicationVersion: "2.4.0", + integrationName: "copilot-assistant", + integrationVersion: "1.5.0", + }; + + const params = await captureConnectParams({ clientInfo }); + + expect(params.clientInfo).toEqual({ + editorName: "acme-developer-portal", + editorVersion: "2.4.0", + extensionName: "copilot-assistant", + extensionVersion: "1.5.0", + }); + }); + + it("omits clientInfo from the handshake when the host declares none", async () => { + const params = await captureConnectParams(); + + expect(params).not.toHaveProperty("clientInfo"); + }); + + it("drops empty fields and omits an all-empty identity", async () => { + const allEmpty = await captureConnectParams({ + clientInfo: { + applicationName: "", + applicationVersion: "", + integrationName: "", + integrationVersion: "", + }, + }); + expect(allEmpty).not.toHaveProperty("clientInfo"); + + const partial = await captureConnectParams({ + clientInfo: { applicationName: "example-app", applicationVersion: "" }, + }); + expect(partial.clientInfo).toEqual({ editorName: "example-app" }); + }); + + it("keeps telemetry forwarding alongside a declared identity", async () => { + const params = await captureConnectParams({ + clientInfo: { applicationName: "example-app" }, + onGitHubTelemetry: () => {}, + }); + + expect(params).toMatchObject({ + clientInfo: { editorName: "example-app" }, + enableGitHubTelemetryForwarding: true, + }); + }); +}); diff --git a/nodejs/test/e2e/builtin_tools.e2e.test.ts b/nodejs/test/e2e/builtin_tools.e2e.test.ts index 36b70ea195..39900bc7d6 100644 --- a/nodejs/test/e2e/builtin_tools.e2e.test.ts +++ b/nodejs/test/e2e/builtin_tools.e2e.test.ts @@ -130,6 +130,19 @@ describe("Built-in Tools", async () => { async () => { await writeFile(join(workDir, "data.txt"), "apple\nbanana\napricot\ncherry\n"); const session = await client.createSession({ onPermissionRequest: approveAll }); + let grepToolCallId: string | undefined; + let grepCompletedSuccessfully = false; + session.on((event) => { + if (event.type === "tool.execution_start" && event.data.toolName === "grep") { + grepToolCallId = event.data.toolCallId; + } else if ( + event.type === "tool.execution_complete" && + event.data.toolCallId === grepToolCallId && + event.data.success + ) { + grepCompletedSuccessfully = true; + } + }); const msg = await session.sendAndWait( { prompt: "Search for lines starting with 'ap' in the file 'data.txt'. Tell me which lines matched.", @@ -138,6 +151,7 @@ describe("Built-in Tools", async () => { ); expect(msg?.data.content).toContain("apple"); expect(msg?.data.content).toContain("apricot"); + expect(grepCompletedSuccessfully).toBe(true); }, TEST_TIMEOUT_MS ); diff --git a/nodejs/test/e2e/client.e2e.test.ts b/nodejs/test/e2e/client.e2e.test.ts index 89489f78e6..bc3421bfa1 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) { @@ -179,21 +184,15 @@ describe("Client", () => { await client.stop(); }); - it("should report error with stderr when CLI fails to start", async () => { + it.skipIf(isInProcessTransport)("should report error when CLI fails to start", async () => { const client = new CopilotClient({ - connection: RuntimeConnection.forStdio({ args: ["--nonexistent-flag-for-testing"] }), + connection: RuntimeConnection.forStdio({ + args: ["--nonexistent-flag-for-testing"], + }), }); onTestFinishedStop(client); - let initialError: Error | undefined; - try { - await client.start(); - expect.fail("Expected start() to throw an error"); - } catch (error) { - initialError = error as Error; - expect(initialError.message).toContain("stderr"); - expect(initialError.message).toContain("nonexistent"); - } + await expect(client.start()).rejects.toBeInstanceOf(Error); // Verify subsequent calls also fail (don't hang) try { @@ -201,7 +200,7 @@ describe("Client", () => { await session.send("test"); expect.fail("Expected send() to throw an error after CLI exit"); } catch (error) { - expect((error as Error).message).toContain("Connection is closed"); + expect(error).toBeInstanceOf(Error); } }); }); diff --git a/nodejs/test/e2e/client_options.e2e.test.ts b/nodejs/test/e2e/client_options.e2e.test.ts index e3dc41343b..2f1dee6cb5 100644 --- a/nodejs/test/e2e/client_options.e2e.test.ts +++ b/nodejs/test/e2e/client_options.e2e.test.ts @@ -467,7 +467,7 @@ describe("Client options", async () => { }); const session = await client.createSession({ clientName: "advanced-create-client", - model: "claude-sonnet-4.5", + model: "claude-sonnet-5", reasoningEffort: "medium", reasoningSummary: "detailed", contextTier: "long_context", @@ -532,7 +532,7 @@ describe("Client options", async () => { provider: "create-provider", id: "create-model", name: "Create Model", - modelId: "claude-sonnet-4.5", + modelId: "claude-sonnet-5", wireModel: "create-wire-model", maxContextWindowTokens: 12_000, maxPromptTokens: 10_000, @@ -544,7 +544,7 @@ describe("Client options", async () => { const createRequest = getCapturedRequest(capturePath, "session.create"); expect(createRequest.clientName).toBe("advanced-create-client"); - expect(createRequest.model).toBe("claude-sonnet-4.5"); + expect(createRequest.model).toBe("claude-sonnet-5"); expect(createRequest.reasoningEffort).toBe("medium"); expect(createRequest.reasoningSummary).toBe("detailed"); expect(createRequest.contextTier).toBe("long_context"); @@ -609,7 +609,7 @@ describe("Client options", async () => { await client.start(); const session = await client.createSession({ - model: "claude-sonnet-4.5", + model: "claude-sonnet-5", provider: { type: "azure", wireApi: "responses", @@ -619,7 +619,7 @@ describe("Client options", async () => { bearerToken: "provider-bearer-token", azure: { apiVersion: "2024-02-15-preview" }, headers: { "X-Provider-Wire": "yes" }, - modelId: "claude-sonnet-4.5", + modelId: "claude-sonnet-5", wireModel: "azure-deployment", maxPromptTokens: 8192, maxOutputTokens: 1024, @@ -636,7 +636,7 @@ describe("Client options", async () => { expect(provider.bearerToken).toBe("provider-bearer-token"); expect(getObject(provider.azure).apiVersion).toBe("2024-02-15-preview"); expect(getObject(provider.headers)["X-Provider-Wire"]).toBe("yes"); - expect(provider.modelId).toBe("claude-sonnet-4.5"); + expect(provider.modelId).toBe("claude-sonnet-5"); expect(provider.wireModel).toBe("azure-deployment"); expect(provider.maxPromptTokens).toBe(8192); expect(provider.maxOutputTokens).toBe(1024); diff --git a/nodejs/test/e2e/copilot_request_cancel_error.e2e.test.ts b/nodejs/test/e2e/copilot_request_cancel_error.e2e.test.ts index 69bacd4f6e..b0af6524a1 100644 --- a/nodejs/test/e2e/copilot_request_cancel_error.e2e.test.ts +++ b/nodejs/test/e2e/copilot_request_cancel_error.e2e.test.ts @@ -56,8 +56,8 @@ function serveNonInference(url: string): Response { const MODEL_CATALOG_JSON = JSON.stringify({ data: [ { - id: "claude-sonnet-4.5", - name: "Claude Sonnet 4.5", + id: "claude-sonnet-5", + name: "Claude Sonnet 5", object: "model", vendor: "Anthropic", version: "1", @@ -65,7 +65,7 @@ const MODEL_CATALOG_JSON = JSON.stringify({ model_picker_enabled: true, capabilities: { type: "chat", - family: "claude-sonnet-4.5", + family: "claude-sonnet-5", tokenizer: "o200k_base", limits: { max_context_window_tokens: 200000, max_output_tokens: 8192 }, supports: { diff --git a/nodejs/test/e2e/copilot_request_handler.e2e.test.ts b/nodejs/test/e2e/copilot_request_handler.e2e.test.ts index 309250d852..04bccb7e08 100644 --- a/nodejs/test/e2e/copilot_request_handler.e2e.test.ts +++ b/nodejs/test/e2e/copilot_request_handler.e2e.test.ts @@ -42,8 +42,8 @@ async function startFakeUpstream(): Promise<{ sendJson(res, 200, { data: [ { - id: "claude-sonnet-4.5", - name: "Claude Sonnet 4.5", + id: "claude-sonnet-5", + name: "Claude Sonnet 5", object: "model", vendor: "Anthropic", version: "1", @@ -52,7 +52,7 @@ async function startFakeUpstream(): Promise<{ supported_endpoints: ["/responses", "ws:/responses"], capabilities: { type: "chat", - family: "claude-sonnet-4.5", + family: "claude-sonnet-5", tokenizer: "o200k_base", limits: { max_context_window_tokens: 200000, diff --git a/nodejs/test/e2e/copilot_request_session_id.e2e.test.ts b/nodejs/test/e2e/copilot_request_session_id.e2e.test.ts index bd070c20ca..9b0dbbb3bc 100644 --- a/nodejs/test/e2e/copilot_request_session_id.e2e.test.ts +++ b/nodejs/test/e2e/copilot_request_session_id.e2e.test.ts @@ -171,7 +171,7 @@ const CHAT_COMPLETION_STREAM_EVENTS: string[] = (() => { id: "chatcmpl-stub-1", object: "chat.completion.chunk", created: 1, - model: "claude-sonnet-4.5", + model: "claude-sonnet-5", }; return [ `data: ${JSON.stringify({ @@ -210,7 +210,7 @@ const BUFFERED_CHAT_COMPLETION_JSON = JSON.stringify({ id: "chatcmpl-stub-1", object: "chat.completion", created: 1, - model: "claude-sonnet-4.5", + model: "claude-sonnet-5", choices: [ { index: 0, @@ -224,8 +224,8 @@ const BUFFERED_CHAT_COMPLETION_JSON = JSON.stringify({ const MODEL_CATALOG_JSON = JSON.stringify({ data: [ { - id: "claude-sonnet-4.5", - name: "Claude Sonnet 4.5", + id: "claude-sonnet-5", + name: "Claude Sonnet 5", object: "model", vendor: "Anthropic", version: "1", @@ -233,7 +233,7 @@ const MODEL_CATALOG_JSON = JSON.stringify({ model_picker_enabled: true, capabilities: { type: "chat", - family: "claude-sonnet-4.5", + family: "claude-sonnet-5", tokenizer: "o200k_base", limits: { max_context_window_tokens: 200000, max_output_tokens: 8192 }, supports: { @@ -300,14 +300,14 @@ describe("CopilotRequestHandler threads the runtime session id (CAPI + BYOK)", a const session = await client.createSession({ onPermissionRequest: approveAll, // BYOK providers require an explicit model id. - model: "claude-sonnet-4.5", + model: "claude-sonnet-5", provider: { type: "openai", wireApi: "responses", baseUrl: "https://byok.invalid/v1", apiKey: "byok-secret", - modelId: "claude-sonnet-4.5", - wireModel: "claude-sonnet-4.5", + modelId: "claude-sonnet-5", + wireModel: "claude-sonnet-5", }, }); const byokSessionId = session.sessionId; 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 0000000000..8478f93288 --- /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-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-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-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-5", + name: "Claude Sonnet 5", + object: "model", + vendor: "Anthropic", + version: "1", + preview: false, + model_picker_enabled: true, + capabilities: { + type: "chat", + family: "claude-sonnet-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 so session initialization settles before assertions. + 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); + await waitForMcpStatus(enabledSession, "github-mcp-server", "connected"); + const requestsBeforeFirstMessage = await mcpRequestCount(); + expect(requestsBeforeFirstMessage).toBeGreaterThan(0); + expectSyntheticResponse( + await enabledSession.sendAndWait({ prompt: MCP_TRIGGER_PROMPT }) + ); + await waitForMcpRequestCount(requestsBeforeFirstMessage + 1); + } + ); + + 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/event_fidelity.e2e.test.ts b/nodejs/test/e2e/event_fidelity.e2e.test.ts index da4b8105ad..8ae2091b33 100644 --- a/nodejs/test/e2e/event_fidelity.e2e.test.ts +++ b/nodejs/test/e2e/event_fidelity.e2e.test.ts @@ -35,9 +35,9 @@ describe("Event Fidelity", async () => { const assistantIdx = types.lastIndexOf("assistant.message"); expect(userIdx).toBeLessThan(assistantIdx); - // session.idle should be last + // session.idle completes the conversational turn; metadata may follow it. const idleIdx = types.lastIndexOf("session.idle"); - expect(idleIdx).toBe(types.length - 1); + expect(assistantIdx).toBeLessThan(idleIdx); await session.disconnect(); }); diff --git a/nodejs/test/e2e/extension_env_access.e2e.test.ts b/nodejs/test/e2e/extension_env_access.e2e.test.ts new file mode 100644 index 0000000000..1aa4875dd4 --- /dev/null +++ b/nodejs/test/e2e/extension_env_access.e2e.test.ts @@ -0,0 +1,230 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { execFileSync, spawn } from "node:child_process"; +import { existsSync, mkdtempSync, readFileSync } from "node:fs"; +import { copyFile, mkdir, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; +import { expect, it } from "vitest"; +import { + createMessageConnection, + StreamMessageReader, + StreamMessageWriter, +} from "vscode-jsonrpc/node.js"; +import { approveAll, RuntimeConnection } from "../../src/index.js"; +import { getSdkProtocolVersion } from "../../src/sdkProtocolVersion.js"; +import { createSdkTestContext, getLegacyCliPathForTests } from "./harness/sdkTestContext.js"; +import { retry } from "./harness/sdkTestHelper.js"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const FIXTURE = join(__dirname, "fixtures", "env-access-extension.mjs"); +const DIST_DIR = resolve(__dirname, "..", "..", "dist"); + +interface ExtensionRun { + resumeParams: Record; + /** Sample taken before the join, so an inherited value is distinguishable from a granted one. */ + prejoin: string; + postjoin: string; + result: string; +} + +/** + * Run the fixture extension as a real child process against a stub host that + * speaks the extension side of the wire. + * + * The extension connection is plain JSON-RPC over the extension process's own + * stdio, so a stub host observes exactly what the CLI observes. That is the only + * way to cover this feature end to end today: the released CLI predates the host + * half (github/copilot-agent-runtime#15144), so it ignores the request and grants + * nothing. Once the pinned CLI release carries the host half, the + * real-CLI case below can assert the grant instead. + */ +async function runExtensionAgainstStubHost(options: { + requested: string[]; + /** Pass an explicit empty list rather than omitting the option. */ + requestEmptyList?: boolean; + /** Names the extension samples but never asks for. */ + observe?: string[]; + /** Values the host resolves for an approved request, or undefined to deny it. */ + granted?: Record; +}): Promise { + if (!existsSync(join(DIST_DIR, "extension.js"))) { + throw new Error(`Built SDK not found at ${DIST_DIR}. Run \`npm run build\` first.`); + } + + const dir = mkdtempSync(join(tmpdir(), "copilot-env-access-")); + const prejoinFile = join(dir, "prejoin"); + const postjoinFile = join(dir, "postjoin"); + const resultFile = join(dir, "result"); + + const child = spawn(process.execPath, [FIXTURE], { + stdio: ["pipe", "pipe", "pipe"], + env: { + ...process.env, + SESSION_ID: "stub-host-session", + EXTENSION_SDK_MODULE: pathToFileURL(join(DIST_DIR, "extension.js")).href, + EXTENSION_ENV_REQUEST: options.requested.join(","), + EXTENSION_ENV_REQUEST_EMPTY: options.requestEmptyList ? "1" : "", + EXTENSION_OBSERVE_ENV_NAMES: (options.observe ?? []).join(","), + EXTENSION_PREJOIN_FILE: prejoinFile, + EXTENSION_POSTJOIN_FILE: postjoinFile, + EXTENSION_RESULT_FILE: resultFile, + }, + }); + + const stderr: string[] = []; + child.stderr!.on("data", (chunk) => stderr.push(String(chunk))); + + let capturedResumeParams: Record = {}; + const connection = createMessageConnection( + new StreamMessageReader(child.stdout!), + new StreamMessageWriter(child.stdin!) + ); + connection.onRequest("connect", () => ({ protocolVersion: getSdkProtocolVersion() })); + connection.onRequest("session.resume", (params: Record) => { + capturedResumeParams = params; + if (!options.granted) { + throw new Error( + 'Extension "env-access" was denied access to sensitive environment variables' + ); + } + return { sessionId: params.sessionId, grantedEnvironmentVariables: options.granted }; + }); + // Everything else the SDK issues while joining is irrelevant here, and an + // unanswered request would hang the join. + connection.onRequest(() => ({})); + connection.onNotification(() => {}); + connection.listen(); + + try { + await retry( + "wait for the fixture extension to report its join result", + async () => { + expect( + existsSync(resultFile), + `extension never reported; stderr: ${stderr.join("")}` + ).toBe(true); + }, + 300, + 100 + ); + + return { + resumeParams: capturedResumeParams, + prejoin: readFileSync(prejoinFile, "utf-8"), + postjoin: readFileSync(postjoinFile, "utf-8"), + result: readFileSync(resultFile, "utf-8"), + }; + } finally { + connection.dispose(); + child.kill(); + // Windows keeps the directory locked until the child is gone. + await new Promise((resolveExit) => { + if (child.exitCode !== null || child.signalCode !== null) { + resolveExit(); + return; + } + child.once("exit", () => resolveExit()); + }); + await rm(dir, { recursive: true, force: true, maxRetries: 20, retryDelay: 100 }); + } +} + +it("puts an extension's environment request on the wire and applies the grant", async () => { + const run = await runExtensionAgainstStubHost({ + requested: ["E2E_SDK_TOKEN", "E2E_SDK_OTHER"], + granted: { E2E_SDK_TOKEN: "granted-token", E2E_SDK_OTHER: "granted-other" }, + }); + + expect(run.resumeParams.requestedEnvironmentVariables).toEqual([ + "E2E_SDK_TOKEN", + "E2E_SDK_OTHER", + ]); + // The values crossed the process boundary rather than being inherited. + expect(run.prejoin).toBe("E2E_SDK_TOKEN=\nE2E_SDK_OTHER="); + expect(run.postjoin).toBe("E2E_SDK_TOKEN=granted-token\nE2E_SDK_OTHER=granted-other"); + expect(run.result).toBe("joined"); +}); + +it("grants nothing to an extension whose request the host denies", async () => { + const run = await runExtensionAgainstStubHost({ requested: ["E2E_SDK_TOKEN"] }); + + expect(run.resumeParams.requestedEnvironmentVariables).toEqual(["E2E_SDK_TOKEN"]); + expect(run.result).toContain("denied access to sensitive environment variables"); + expect(run.postjoin).toBe("E2E_SDK_TOKEN="); +}); + +it("leaves the wire payload alone when an extension asks for nothing", async () => { + const omitted = await runExtensionAgainstStubHost({ requested: [], granted: {} }); + const emptyList = await runExtensionAgainstStubHost({ + requested: [], + requestEmptyList: true, + granted: {}, + }); + + expect(omitted.resumeParams).not.toHaveProperty("requestedEnvironmentVariables"); + // An empty list is the other public way to ask for nothing. + expect(emptyList.resumeParams).not.toHaveProperty("requestedEnvironmentVariables"); + expect(omitted.result).toBe("joined"); + expect(emptyList.result).toBe("joined"); +}); + +it("ignores a granted variable the extension never requested", async () => { + const run = await runExtensionAgainstStubHost({ + requested: ["E2E_SDK_TOKEN"], + observe: ["E2E_SDK_SMUGGLED"], + granted: { E2E_SDK_TOKEN: "granted-token", E2E_SDK_SMUGGLED: "not-approved" }, + }); + + // The user approved one name, so a host answering with a second one cannot + // widen the grant. + expect(run.postjoin).toBe("E2E_SDK_TOKEN=granted-token\nE2E_SDK_SMUGGLED="); +}); + +const cliObservations = mkdtempSync(join(tmpdir(), "copilot-env-access-cli-")); +const cliResultFile = join(cliObservations, "result"); +const cliContext = await createSdkTestContext({ + copilotClientOptions: { + connection: RuntimeConnection.forStdio({ path: await getLegacyCliPathForTests() }), + env: { + COPILOT_CLI_ENABLED_FEATURE_FLAGS: "EXTENSIONS", + EXTENSION_ENV_REQUEST: "E2E_SDK_TOKEN", + EXTENSION_RESULT_FILE: cliResultFile, + EXTENSION_PREJOIN_FILE: join(cliObservations, "prejoin"), + EXTENSION_POSTJOIN_FILE: join(cliObservations, "postjoin"), + }, + }, +}); + +// The released CLI ignores `requestedEnvironmentVariables`, so this covers the +// half a real CLI can prove today: asking for variables does not break the join. +// It becomes the grant test once the pinned CLI release carries the host half. +it("joins a real CLI that does not support environment requests", async () => { + const { workDir, copilotClient } = cliContext; + const extensionDir = join(workDir, ".github", "extensions", "env-access"); + await rm(join(workDir, ".github"), { recursive: true, force: true }); + await rm(cliResultFile, { force: true }); + await mkdir(extensionDir, { recursive: true }); + await copyFile(FIXTURE, join(extensionDir, "extension.mjs")); + execFileSync("git", ["init", "--quiet"], { cwd: workDir }); + + await using _session = await copilotClient.createSession({ + requestExtensions: true, + extensionSdkPath: DIST_DIR, + onPermissionRequest: approveAll, + }); + + await retry( + "wait for the env-access extension to join the session", + async () => { + expect(existsSync(cliResultFile)).toBe(true); + }, + 300, + 100 + ); + + expect(readFileSync(cliResultFile, "utf-8")).toBe("joined"); +}); diff --git a/nodejs/test/e2e/factory.e2e.test.ts b/nodejs/test/e2e/factory.e2e.test.ts index 547ecbbd5d..98004406f9 100644 --- a/nodejs/test/e2e/factory.e2e.test.ts +++ b/nodejs/test/e2e/factory.e2e.test.ts @@ -1,77 +1,373 @@ import { execFileSync } from "node:child_process"; -import { existsSync } from "node:fs"; -import { copyFile, mkdir } from "node:fs/promises"; +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 } from "vitest"; -import { approveAll } from "../../src/index.js"; +import { expect, it, vi } from "vitest"; +import { approveAll, FactoryResumeError, RuntimeConnection } from "../../src/index.js"; import { createSdkTestContext, DEFAULT_GITHUB_TOKEN, - isInProcessTransport, + getLegacyCliPathForTests, } 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", - }, - }, - }); - -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 { copilotClient, openAiEndpoint, workDir } = factoryTestContext; - - await openAiEndpoint.setCopilotUserByToken(DEFAULT_GITHUB_TOKEN, { - login: "factory-e2e-user", - copilot_plan: "individual_pro", - token_based_billing: true, - }); - - const extensionDir = join(workDir, ".github", "extensions", "factory-smoke"); - const readyFile = join(extensionDir, "ready"); - await mkdir(extensionDir, { recursive: true }); - await copyFile( - join(__dirname, "fixtures", "factory-extension.mjs"), - join(extensionDir, "extension.mjs") - ); - execFileSync("git", ["init", "--quiet"], { cwd: workDir }); - - await using session = await copilotClient.createSession({ - requestExtensions: true, - extensionSdkPath: resolve(__dirname, "..", "..", "dist"), - onPermissionRequest: approveAll, - 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 - ); - - 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 }, - }); +const factoryTestContext = await createSdkTestContext({ + copilotClientOptions: { + connection: RuntimeConnection.forStdio({ path: await getLegacyCliPathForTests() }), + env: { + COPILOT_CLI_ENABLED_FEATURE_FLAGS: "EXTENSIONS,AGENT_FACTORIES", + }, + }, +}); + +async function setupFactoryExtension(workDir: string, onPermissionRequest = approveAll) { + 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("runs an extension-authored factory across the SDK process boundary", async () => { + const { workDir } = factoryTestContext; + await using session = await setupFactoryExtension(workDir); + + const result = await session.factory.run("argument-echo", { + args: { source: "sdk-e2e", count: 11 }, + notifyOnComplete: false, + }); + + expect(result).toMatchObject({ + status: "completed", + result: { source: "sdk-e2e", count: 11 }, + }); +}); + +// TODO(cli-1.0.81-2): the subagent request is rejected downstream under CLI 1.0.81-2, so the +// fixture reports didThrow: true. Re-enable once the runtime fix ships. +// +// The timeout is generous because the factory abandons its subagent once the runtime has +// accepted the request, so the run settles only after the runtime drains that work. +it.skip("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 }, + }); +}, 60_000); + +it("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("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", { notifyOnComplete: false }); + 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("forwards factory runtime controls across the SDK process boundary", async () => { + const { workDir } = factoryTestContext; + await using session = await setupFactoryExtension(workDir); + + const suppressed = await session.factory.run("phased", { + notifyOnComplete: false, + logPhaseNames: false, + }); + + expect(suppressed).toMatchObject({ + status: "completed", + result: "finished", + }); + const progress = await session.factory.getRunProgress(suppressed.runId); + expect(progress.records).toEqual( + expect.arrayContaining([ + expect.objectContaining({ kind: "phase", text: "Collect" }), + expect.objectContaining({ kind: "log", text: "Collected" }), + expect.objectContaining({ kind: "phase", text: "Summarize" }), + expect.objectContaining({ kind: "log", text: "Summarized" }), + ]) + ); + + const events = await session.getEvents(); + expect( + events.some( + (event) => + event.type === "system.notification" && + event.data.kind.type === "factory_completed" && + event.data.kind.runId === suppressed.runId + ) + ).toBe(false); + expect( + events.filter( + (event) => event.type === "session.info" && event.data.infoType === "factory_phase" + ) + ).toEqual([]); +}); + +it("pages factory runs and returns cursor metadata", async () => { + const { workDir } = factoryTestContext; + await using session = await setupFactoryExtension(workDir); + + const first = await session.factory.run("argument-echo", { + args: { ordinal: 1 }, + notifyOnComplete: false, + }); + const second = await session.factory.run("argument-echo", { + args: { ordinal: 2 }, + notifyOnComplete: false, + }); + const third = await session.factory.run("argument-echo", { + args: { ordinal: 3 }, + notifyOnComplete: false, + }); + + const newest = await session.factory.listRuns({ limit: 1 }); + expect(newest).toMatchObject({ + runs: [expect.objectContaining({ runId: third.runId })], + hasMoreNewer: false, + omittedOlder: 2, + }); + expect(newest.oldestSeq).toBe(newest.newestSeq); + expect(newest.oldestSeq).not.toBeNull(); + + const older = await session.factory.listRuns({ + beforeSeq: newest.oldestSeq!, + limit: 1, + }); + expect(older).toMatchObject({ + runs: [expect.objectContaining({ runId: second.runId })], + hasMoreNewer: true, + omittedOlder: 1, + }); + + const oldest = await session.factory.listRuns({ + beforeSeq: older.oldestSeq!, + limit: 1, + }); + expect(oldest).toMatchObject({ + runs: [expect.objectContaining({ runId: first.runId })], + hasMoreNewer: true, + omittedOlder: 0, + }); +}); + +it("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", { notifyOnComplete: false }) + ).resolves.toMatchObject({ + status: "completed", + }); + expect(denyPermissions).not.toHaveBeenCalled(); +}); + +it("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", { notifyOnComplete: false }); + expect(failedRun).toMatchObject({ + status: "error", + }); + + await expect( + session.factory.resume(failedRun.runId, { + notifyOnComplete: false, + logPhaseNames: false, + }) + ).resolves.toMatchObject({ + status: "completed", + result: "resumed", + }); + expect(denyPermissions).not.toHaveBeenCalled(); +}); + +it("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", { + notifyOnComplete: false, + }); + + expect(result).toMatchObject({ + status: "completed", + result: expect.stringContaining("factory.run and factory.resume"), + }); + expect((result as { result: string }).result).toContain("factory body"); +}); + +it("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", { + notifyOnComplete: false, + }); + + expect(result).toMatchObject({ + status: "completed", + result: expect.stringContaining("factory.run and factory.resume"), + }); + expect((result as { result: string }).result).toContain("factory body"); +}); + +it("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", { notifyOnComplete: false }); + 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("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", { notifyOnComplete: false }); + + expect(result).toMatchObject({ + status: "completed", + result: [1, "two", false], + }); +}); + +it("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, + notifyOnComplete: false, + }); + + expect(result).toMatchObject({ + status: "completed", + result: args, + }); +}); diff --git a/nodejs/test/e2e/fixtures/env-access-extension.mjs b/nodejs/test/e2e/fixtures/env-access-extension.mjs new file mode 100644 index 0000000000..39cea3f1ba --- /dev/null +++ b/nodejs/test/e2e/fixtures/env-access-extension.mjs @@ -0,0 +1,82 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// Extension that asks for named sensitive environment variables at join time. +// +// Every input is an environment variable so one fixture serves both the stub-host +// case (spawned directly, importing the built SDK through EXTENSION_SDK_MODULE) +// and the real-CLI case (forked by the CLI, which injects the SDK module). +// +// - EXTENSION_SDK_MODULE: import specifier for the SDK. Defaults to the module +// name the CLI resolves for a forked extension. +// - EXTENSION_ENV_REQUEST: comma-separated names to pass to joinSession as +// requestedEnvironmentVariables. +// - EXTENSION_PREJOIN_FILE: `NAME=` per requested name, sampled BEFORE the +// join. A test compares it with the post-join sample to tell a value the +// process already inherited from one the host granted. +// - EXTENSION_POSTJOIN_FILE: the same sample, written once the join settles. +// - EXTENSION_RESULT_FILE: `joined` or `rejected:`. A denied extension +// has no session to report through, so it reports here. +// - EXTENSION_ENV_REQUEST_EMPTY: set to `1` to pass an explicit empty list when +// EXTENSION_ENV_REQUEST names nothing, instead of omitting the option. +// - EXTENSION_OBSERVE_ENV_NAMES: comma-separated names that are sampled but +// deliberately NOT requested, so a test can prove a name outside the approved +// set never reached this process. + +import { writeFileSync } from "node:fs"; + +const sdkModule = process.env.EXTENSION_SDK_MODULE ?? "@github/copilot-sdk/extension"; +const { joinSession } = await import(sdkModule); + +const requested = (process.env.EXTENSION_ENV_REQUEST ?? "") + .split(",") + .map((name) => name.trim()) + .filter((name) => name.length > 0); +const observed = (process.env.EXTENSION_OBSERVE_ENV_NAMES ?? "") + .split(",") + .map((name) => name.trim()) + .filter((name) => name.length > 0); +const sampledNames = [...requested, ...observed]; +const sample = () => sampledNames.map((name) => `${name}=${process.env[name] ?? ""}`).join("\n"); + +const record = (file, contents) => { + if (file) { + writeFileSync(file, contents); + } +}; + +record(process.env.EXTENSION_PREJOIN_FILE, sample()); + +const config = { + tools: [ + { + name: "env_access_greeter", + description: "Greets someone. Always call this tool when asked to greet.", + parameters: { type: "object", properties: { name: { type: "string" } } }, + handler: async (args) => `Hello from env-access, ${args.name || "World"}!`, + }, + ], +}; +// An extension that wants nothing normally omits the option entirely. +// EXTENSION_ENV_REQUEST_EMPTY covers the other public way to ask for nothing: +// passing an empty list, which must reach the wire the same way. +if (requested.length > 0) { + config.requestedEnvironmentVariables = requested; +} else if (process.env.EXTENSION_ENV_REQUEST_EMPTY === "1") { + config.requestedEnvironmentVariables = []; +} + +try { + await joinSession(config); + record(process.env.EXTENSION_POSTJOIN_FILE, sample()); + record(process.env.EXTENSION_RESULT_FILE, "joined"); +} catch (error) { + // Sampled after the rejection too, so a test can prove a denied extension + // never saw the value rather than only that the join failed. + record(process.env.EXTENSION_POSTJOIN_FILE, sample()); + record( + process.env.EXTENSION_RESULT_FILE, + `rejected:${error instanceof Error ? error.message : String(error)}` + ); +} diff --git a/nodejs/test/e2e/fixtures/factory-extension.mjs b/nodejs/test/e2e/fixtures/factory-extension.mjs index fab95a90f1..fb344b4863 100644 --- a/nodejs/test/e2e/fixtures/factory-extension.mjs +++ b/nodejs/test/e2e/fixtures/factory-extension.mjs @@ -1,14 +1,188 @@ -import { writeFileSync } from "node:fs"; +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, }); -await joinSession({ factories: [argumentEcho] }); -writeFileSync(new URL("./ready", import.meta.url), "ready"); +const arrayResult = defineFactory({ + meta: { + name: "array-result", + description: "Return an array result.", + phases: [], + }, + run: async () => [1, "two", false], +}); + +const phased = defineFactory({ + meta: { + name: "phased", + description: "Record named phases and ordinary progress.", + phases: [{ title: "Collect" }, { title: "Summarize" }], + }, + run: async ({ phase, log }) => { + phase("Collect"); + log("Collected"); + phase("Summarize"); + log("Summarized"); + return "finished"; + }, +}); + +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, + phased, + 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" }, + notifyOnComplete: false, + }); + 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/sdkTestContext.ts b/nodejs/test/e2e/harness/sdkTestContext.ts index bf62db4826..c4befb148e 100644 --- a/nodejs/test/e2e/harness/sdkTestContext.ts +++ b/nodejs/test/e2e/harness/sdkTestContext.ts @@ -12,6 +12,8 @@ import { afterAll, afterEach, beforeEach, onTestFailed, TestContext } from "vite import { CopilotClient, CopilotClientOptions, RuntimeConnection } from "../../../src"; import { CapiProxy } from "./CapiProxy"; import { formatError, retry } from "./sdkTestHelper"; +import { ensureCopilotPackage } from "../../../scripts/releaseArtifacts"; +import { COPILOT_CLI_VERSION } from "../../../src/cliVersion"; export const isCI = process.env.GITHUB_ACTIONS === "true"; export const DEFAULT_GITHUB_TOKEN = "fake-token-for-e2e-tests"; @@ -50,6 +52,12 @@ function getCliPathForTests(): string | undefined { return undefined; } +/** Resolves the legacy SEA only for tests that explicitly exercise Node-hosted features. */ +export async function getLegacyCliPathForTests(): Promise { + const packageRoot = await ensureCopilotPackage(COPILOT_CLI_VERSION); + return join(packageRoot, "app.js"); +} + export async function createSdkTestContext({ logLevel, useStdio, @@ -286,8 +294,13 @@ export async function createSdkTestContext({ process.chdir(restoreCwd); restoreCwd = undefined; } - // Empty directories but leave them in place for next test - await rimraf([join(homeDir, "*"), join(workDir, "*")], { glob: true }); + // The in-process runtime retains open state files until afterAll shuts it down. + // Keep its isolated home intact while it is alive; removing open files on POSIX + // can leave later tests using unlinked database state. + const cleanupPaths = isInProcess + ? [join(workDir, "*")] + : [join(homeDir, "*"), join(workDir, "*")]; + await rimraf(cleanupPaths, { glob: true }); }); afterAll(async () => { diff --git a/nodejs/test/e2e/inprocess_ffi.e2e.test.ts b/nodejs/test/e2e/inprocess_ffi.e2e.test.ts index af879ea77b..e3b5f75ee4 100644 --- a/nodejs/test/e2e/inprocess_ffi.e2e.test.ts +++ b/nodejs/test/e2e/inprocess_ffi.e2e.test.ts @@ -11,9 +11,8 @@ describe("In-process FFI transport", () => { // exercised by the full E2E suite running under the `inprocess` CI matrix cell, // not a dedicated test. it("should start and connect over in-process FFI", async () => { - // In-process FFI hosting resolves the CLI entrypoint (COPILOT_CLI_PATH or the - // bundled platform package) and its sibling native runtime library itself. If - // neither is available, start() throws and the test fails hard. + // In-process FFI hosting loads runtime.node directly from the bundled runtime. + // If it is unavailable, start() throws and the test fails hard. const client = new CopilotClient({ connection: RuntimeConnection.forInProcess() }); await client.start(); diff --git a/nodejs/test/e2e/mcp_and_agents.e2e.test.ts b/nodejs/test/e2e/mcp_and_agents.e2e.test.ts index a593ff9882..414e760ea2 100644 --- a/nodejs/test/e2e/mcp_and_agents.e2e.test.ts +++ b/nodejs/test/e2e/mcp_and_agents.e2e.test.ts @@ -103,7 +103,6 @@ describe("MCP Servers and Custom Agents", async () => { // Create a session first const session1 = await client.createSession({ onPermissionRequest: approveAll }); const sessionId = session1.sessionId; - await session1.sendAndWait({ prompt: "What is 1+1?" }); // Resume with MCP servers const mcpServers = createTestMcpServers("test-server"); diff --git a/nodejs/test/e2e/pending_work_resume.e2e.test.ts b/nodejs/test/e2e/pending_work_resume.e2e.test.ts index 85abc3a900..7c2906c7b9 100644 --- a/nodejs/test/e2e/pending_work_resume.e2e.test.ts +++ b/nodejs/test/e2e/pending_work_resume.e2e.test.ts @@ -13,6 +13,7 @@ import type { PermissionRequestResult, } from "../../src/index.js"; import { createSdkTestContext, DEFAULT_GITHUB_TOKEN } from "./harness/sdkTestContext.js"; +import { waitForCondition } from "./harness/sdkTestHelper.js"; const PENDING_WORK_TIMEOUT_MS = 60_000; const TEST_TIMEOUT_MS = 180_000; @@ -516,7 +517,46 @@ describe("Pending work resume", async () => { ).toBe("beta"); if (scenario.disconnectOriginalClient) { - await suspendedClient.forceStop(); + const lockObserver = new CopilotClient({ + workingDirectory: workDir, + env, + gitHubToken: DEFAULT_GITHUB_TOKEN, + connection: RuntimeConnection.forStdio({ + path: process.env.COPILOT_CLI_PATH, + }), + }); + try { + await lockObserver.start(); + await waitForCondition( + async () => { + const result = await lockObserver.rpc.sessions.checkInUse({ + sessionIds: [sessionId], + }); + return result.inUse.includes(sessionId); + }, + { + timeoutMs: PENDING_WORK_TIMEOUT_MS, + timeoutMessage: `Timed out waiting for session '${sessionId}' to acquire its lock.`, + } + ); + + await suspendedClient.forceStop(); + + await waitForCondition( + async () => { + const result = await lockObserver.rpc.sessions.checkInUse({ + sessionIds: [sessionId], + }); + return !result.inUse.includes(sessionId); + }, + { + timeoutMs: PENDING_WORK_TIMEOUT_MS, + timeoutMessage: `Timed out waiting for session '${sessionId}' to release its lock.`, + } + ); + } finally { + await lockObserver.forceStop(); + } } const resumedClient = createConnectingClient(cliUrl); diff --git a/nodejs/test/e2e/permissions.e2e.test.ts b/nodejs/test/e2e/permissions.e2e.test.ts index e7c26a2930..d7600ed165 100644 --- a/nodejs/test/e2e/permissions.e2e.test.ts +++ b/nodejs/test/e2e/permissions.e2e.test.ts @@ -5,17 +5,20 @@ 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"; +const isWindows = process.platform === "win32"; + describe("Permission callbacks", async () => { const { copilotClient: client, workDir } = await createSdkTestContext(); @@ -90,6 +93,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; @@ -574,67 +632,76 @@ describe("Permission callbacks", async () => { } }); - it("should invoke permission location and folder trust rpc apis", async () => { - const session = await client.createSession({ onPermissionRequest: approveAll }); - const locationDirectory = await createUniqueWorkDirectory(workDir, "permission-location"); - const trustedDirectory = await createUniqueWorkDirectory(workDir, "folder-trust"); - const commandIdentifier = `node-permission-location-${Date.now()}`; - try { - const resolved = await session.rpc.permissions.locations.resolve({ - workingDirectory: locationDirectory, - }); - expect(resolved.locationType).toBe("dir"); - expectPathEqual(resolved.locationKey, locationDirectory); - - expect( - ( - await session.rpc.permissions.locations.addToolApproval({ - locationKey: resolved.locationKey, - approval: { - kind: "commands", - commandIdentifiers: [commandIdentifier], - }, - }) - ).success - ).toBe(true); - - const applied = await session.rpc.permissions.locations.apply({ - workingDirectory: locationDirectory, - }); - expect(applied.locationType).toBe(resolved.locationType); - expectPathEqual(applied.locationKey, resolved.locationKey); - expect(applied.appliedRuleCount).toBeGreaterThanOrEqual(1); - expect( - applied.appliedRules.some( - (rule) => rule.kind === "shell" && rule.argument === commandIdentifier - ) - ).toBe(true); - - expect( - ( - await session.rpc.permissions.folderTrust.isTrusted({ - path: trustedDirectory, - }) - ).trusted - ).toBe(false); - expect( - ( - await session.rpc.permissions.folderTrust.addTrusted({ - path: trustedDirectory, - }) - ).success - ).toBe(true); - expect( - ( - await session.rpc.permissions.folderTrust.isTrusted({ - path: trustedDirectory, - }) - ).trusted - ).toBe(true); - } finally { - await session.disconnect(); + // TODO(cli-1.0.81-2): CLI 1.0.81-2 no longer matches the location key returned by + // permissions.locations.resolve when permissions.locations.apply re-resolves it on Windows, + // so appliedRuleCount comes back as 0. Still covered on Linux and macOS. + it.skipIf(isWindows)( + "should invoke permission location and folder trust rpc apis", + async () => { + const session = await client.createSession({ onPermissionRequest: approveAll }); + const locationDirectory = await createUniqueWorkDirectory( + workDir, + "permission-location" + ); + const trustedDirectory = await createUniqueWorkDirectory(workDir, "folder-trust"); + const commandIdentifier = `node-permission-location-${Date.now()}`; + try { + const resolved = await session.rpc.permissions.locations.resolve({ + workingDirectory: locationDirectory, + }); + expect(resolved.locationType).toBe("dir"); + expectPathEqual(resolved.locationKey, locationDirectory); + + expect( + ( + await session.rpc.permissions.locations.addToolApproval({ + locationKey: resolved.locationKey, + approval: { + kind: "commands", + commandIdentifiers: [commandIdentifier], + }, + }) + ).success + ).toBe(true); + + const applied = await session.rpc.permissions.locations.apply({ + workingDirectory: locationDirectory, + }); + expect(applied.locationType).toBe(resolved.locationType); + expectPathEqual(applied.locationKey, resolved.locationKey); + expect(applied.appliedRuleCount).toBeGreaterThanOrEqual(1); + expect( + applied.appliedRules.some( + (rule) => rule.kind === "shell" && rule.argument === commandIdentifier + ) + ).toBe(true); + + expect( + ( + await session.rpc.permissions.folderTrust.isTrusted({ + path: trustedDirectory, + }) + ).trusted + ).toBe(false); + expect( + ( + await session.rpc.permissions.folderTrust.addTrusted({ + path: trustedDirectory, + }) + ).success + ).toBe(true); + expect( + ( + await session.rpc.permissions.folderTrust.isTrusted({ + path: trustedDirectory, + }) + ).trusted + ).toBe(true); + } finally { + await session.disconnect(); + } } - }); + ); }); async function createUniqueWorkDirectory(baseDir: string, prefix: string): Promise { diff --git a/nodejs/test/e2e/rewind.e2e.test.ts b/nodejs/test/e2e/rewind.e2e.test.ts new file mode 100644 index 0000000000..7fdfee94c8 --- /dev/null +++ b/nodejs/test/e2e/rewind.e2e.test.ts @@ -0,0 +1,98 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { existsSync, readFileSync, writeFileSync } 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 ORIGINAL_FILE_CONTENT = "Original rewind content"; +const PREPARED_FILE_CONTENT = "Prepared rewind content"; +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); + writeFileSync(filePath, ORIGINAL_FILE_CONTENT); + const session = await client.createSession({ + model: "claude-sonnet-5", + enableFileChangeTracking: true, + onPermissionRequest: approveAll, + }); + + try { + const ready = await session.sendAndWait({ + prompt: `Use the edit tool to replace the exact contents of ${FILE_NAME} from ${ORIGINAL_FILE_CONTENT} to ${PREPARED_FILE_CONTENT}. After the tool succeeds, reply with exactly SDK_REWIND_READY.`, + }); + expect(ready?.data.content).toBe("SDK_REWIND_READY"); + expect(readFileSync(filePath, "utf8")).toBe(PREPARED_FILE_CONTENT); + + const response = await session.sendAndWait({ + prompt: `Use the edit tool to replace the exact contents of ${FILE_NAME} from ${PREPARED_FILE_CONTENT} to ${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() + 30_000; + while ( + Date.now() < deadline && + (rewindPoints.unavailableReason !== undefined || + rewindPoints.points.length !== 2 || + !rewindPoints.points[1]?.turnChangedFiles || + !rewindPoints.points[1]?.canRestoreFiles) + ) { + 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(2); + const rewindPoint = rewindPoints.points[1]; + expect(rewindPoint.turnChangedFiles).toBe(true); + 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(true); + expect(readFileSync(filePath, "utf8")).toBe(PREPARED_FILE_CONTENT); + + 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/rpc.e2e.test.ts b/nodejs/test/e2e/rpc.e2e.test.ts index f90547da9b..4e93fdbe44 100644 --- a/nodejs/test/e2e/rpc.e2e.test.ts +++ b/nodejs/test/e2e/rpc.e2e.test.ts @@ -73,7 +73,7 @@ describe("Session RPC", async () => { it.skip("should call session.rpc.model.getCurrent", async () => { const session = await client.createSession({ onPermissionRequest: approveAll, - model: "claude-sonnet-4.5", + model: "claude-sonnet-5", }); const result = await session.rpc.model.getCurrent(); @@ -85,7 +85,7 @@ describe("Session RPC", async () => { it.skip("should call session.rpc.model.switchTo", async () => { const session = await client.createSession({ onPermissionRequest: approveAll, - model: "claude-sonnet-4.5", + model: "claude-sonnet-5", }); // Get initial model diff --git a/nodejs/test/e2e/rpc_server.e2e.test.ts b/nodejs/test/e2e/rpc_server.e2e.test.ts index 5075ae68d9..13a63875e9 100644 --- a/nodejs/test/e2e/rpc_server.e2e.test.ts +++ b/nodejs/test/e2e/rpc_server.e2e.test.ts @@ -144,7 +144,7 @@ describe("Server-scoped RPC", async () => { const result = await authClient.listModels(); expect(Array.isArray(result)).toBe(true); - expect(result.some((m) => m.id === "claude-sonnet-4.5")).toBe(true); + expect(result.some((m) => m.id === "claude-sonnet-5")).toBe(true); for (const model of result) { expect(model.name).toBeTruthy(); } diff --git a/nodejs/test/e2e/rpc_session_state.e2e.test.ts b/nodejs/test/e2e/rpc_session_state.e2e.test.ts index 5164f99232..aab08b3bc5 100644 --- a/nodejs/test/e2e/rpc_session_state.e2e.test.ts +++ b/nodejs/test/e2e/rpc_session_state.e2e.test.ts @@ -40,7 +40,7 @@ describe("Session-scoped RPC", async () => { it("should call session rpc model getcurrent", async () => { const session = await client.createSession({ onPermissionRequest: approveAll, - model: "claude-sonnet-4.5", + model: "claude-sonnet-5", }); const result = await session.rpc.model.getCurrent(); @@ -65,7 +65,7 @@ describe("Session-scoped RPC", async () => { it("should call session rpc model switchto", async () => { const session = await switchClient.createSession({ onPermissionRequest: approveAll, - model: "claude-sonnet-4.5", + model: "claude-sonnet-5", }); const before = await session.rpc.model.getCurrent(); @@ -315,14 +315,14 @@ describe("Session-scoped RPC", async () => { const branch = `rpc-context-${randomUUID()}`; const session = await client.createSession({ onPermissionRequest: approveAll, - model: "claude-sonnet-4.5", + model: "claude-sonnet-5", workingDirectory: firstDirectory, }); try { const initialSnapshot = await session.rpc.metadata.snapshot(); expect(initialSnapshot.sessionId).toBe(session.sessionId); expect(initialSnapshot.currentMode).toBe("interactive"); - expect(initialSnapshot.selectedModel).toBe("claude-sonnet-4.5"); + expect(initialSnapshot.selectedModel).toBe("claude-sonnet-5"); expect(initialSnapshot.isRemote).toBe(false); expect(initialSnapshot.alreadyInUse).toBe(false); expect(Date.parse(initialSnapshot.startTime)).not.toBeNaN(); @@ -446,7 +446,7 @@ describe("Session-scoped RPC", async () => { it("should set reasoning effort and auto name", async () => { const session = await client.createSession({ onPermissionRequest: approveAll, - model: "claude-sonnet-4.5", + model: "claude-sonnet-5", }); try { const reasoning = await session.rpc.model.setReasoningEffort({ @@ -455,7 +455,7 @@ describe("Session-scoped RPC", async () => { expect(reasoning.reasoningEffort).toBe("high"); const currentModel = await session.rpc.model.getCurrent(); - expect(currentModel.modelId).toBe("claude-sonnet-4.5"); + expect(currentModel.modelId).toBe("claude-sonnet-5"); expect(currentModel.reasoningEffort).toBe("high"); const autoName = `Auto Session ${randomUUID()}`; @@ -734,11 +734,11 @@ describe("Session-scoped RPC", async () => { const contextInfo = await session.rpc.metadata.contextInfo({ promptTokenLimit: 128_000, outputTokenLimit: 4_096, - selectedModel: "claude-sonnet-4.5", + selectedModel: "claude-sonnet-5", }); expect(contextInfo.contextInfo).not.toBeNull(); if (contextInfo.contextInfo) { - expect(contextInfo.contextInfo.modelName).toBe("claude-sonnet-4.5"); + expect(contextInfo.contextInfo.modelName).toBe("claude-sonnet-5"); expect(contextInfo.contextInfo.promptTokenLimit).toBe(128_000); expect(contextInfo.contextInfo.limit).toBeGreaterThanOrEqual( contextInfo.contextInfo.promptTokenLimit @@ -755,7 +755,7 @@ describe("Session-scoped RPC", async () => { } const recomputed = await session.rpc.metadata.recomputeContextTokens({ - modelId: "claude-sonnet-4.5", + modelId: "claude-sonnet-5", }); expect(recomputed.systemTokenCount).toBeGreaterThan(0); expect(recomputed.messagesTokenCount).toBeGreaterThan(0); diff --git a/nodejs/test/e2e/rpc_session_state_extras.e2e.test.ts b/nodejs/test/e2e/rpc_session_state_extras.e2e.test.ts index 54f40fe12c..7b88af7e2d 100644 --- a/nodejs/test/e2e/rpc_session_state_extras.e2e.test.ts +++ b/nodejs/test/e2e/rpc_session_state_extras.e2e.test.ts @@ -70,7 +70,7 @@ describe("Session-scoped state extras RPC", async () => { try { await authClient.start(); session = await authClient.createSession({ - model: "claude-sonnet-4.5", + model: "claude-sonnet-5", onPermissionRequest: approveAll, }); @@ -79,7 +79,7 @@ describe("Session-scoped state extras RPC", async () => { expect(Array.isArray(result.list)).toBe(true); expect(result.list.length).toBeGreaterThan(0); expect( - result.list.some((model) => JSON.stringify(model).includes("claude-sonnet-4.5")) + result.list.some((model) => JSON.stringify(model).includes("claude-sonnet-5")) ).toBe(true); } finally { await disconnect(session); @@ -126,7 +126,7 @@ describe("Session-scoped state extras RPC", async () => { provider: providerName, id: modelId, name: "SDK Runtime Model", - modelId: "claude-sonnet-4.5", + modelId: "claude-sonnet-5", wireModel: "wire-sdk-runtime-model", maxContextWindowTokens: 4096, maxPromptTokens: 3072, @@ -203,21 +203,21 @@ describe("Session-scoped state extras RPC", async () => { it("should get and set allowall permissions", { timeout: 120_000 }, async () => { const session = await createSession(); try { - const initial = await session.rpc.permissions.getAllowAll(); - expect(initial.enabled).toBe(false); + const initial = await session.rpc.permissions.getMode(); + expect(initial.mode).toBe("manual"); - const enable = await session.rpc.permissions.setAllowAll({ enabled: true }); + const enable = await session.rpc.permissions.setMode({ mode: "allow-all" }); expect(enable.success).toBe(true); - expect(enable.enabled).toBe(true); - expect((await session.rpc.permissions.getAllowAll()).enabled).toBe(true); + expect(enable.mode).toBe("allow-all"); + expect((await session.rpc.permissions.getMode()).mode).toBe("allow-all"); - const disable = await session.rpc.permissions.setAllowAll({ enabled: false }); + const disable = await session.rpc.permissions.setMode({ mode: "manual" }); expect(disable.success).toBe(true); - expect(disable.enabled).toBe(false); - expect((await session.rpc.permissions.getAllowAll()).enabled).toBe(false); + expect(disable.mode).toBe("manual"); + expect((await session.rpc.permissions.getMode()).mode).toBe("manual"); } finally { try { - await session.rpc.permissions.setAllowAll({ enabled: false }); + await session.rpc.permissions.setMode({ mode: "manual" }); } catch { // Best-effort reset. } diff --git a/nodejs/test/e2e/rpc_ui_ephemeral_query.e2e.test.ts b/nodejs/test/e2e/rpc_ui_ephemeral_query.e2e.test.ts index 662294d70a..7fa1e90f5d 100644 --- a/nodejs/test/e2e/rpc_ui_ephemeral_query.e2e.test.ts +++ b/nodejs/test/e2e/rpc_ui_ephemeral_query.e2e.test.ts @@ -4,23 +4,29 @@ import { describe, expect, it } from "vitest"; import { approveAll } from "../../src/index.js"; -import { createSdkTestContext } from "./harness/sdkTestContext.js"; +import { createSdkTestContext, isInProcessTransport } from "./harness/sdkTestContext.js"; describe("UI ephemeral query RPC", async () => { const { copilotClient: client } = await createSdkTestContext(); - it("should answer ephemeral query", { timeout: 120_000 }, async () => { - const session = await client.createSession({ onPermissionRequest: approveAll }); - try { - const result = await session.rpc.ui.ephemeralQuery({ - question: "In one word, what is the primary color of a clear daytime sky?", - }); + // TODO(cli-1.0.81-2): CLI 1.0.81-6 still fails session.ui.ephemeralQuery against the + // recorded snapshot on macOS ("Failed to get response from the AI model"). + it.skipIf(isInProcessTransport || process.platform === "darwin")( + "should answer ephemeral query", + { timeout: 120_000 }, + async () => { + const session = await client.createSession({ onPermissionRequest: approveAll }); + try { + const result = await session.rpc.ui.ephemeralQuery({ + question: "In one word, what is the primary color of a clear daytime sky?", + }); - expect(result).toBeDefined(); - expect(result.answer.trim()).toBeTruthy(); - expect(result.answer.toLowerCase()).toContain("blue"); - } finally { - await session.disconnect(); + expect(result).toBeDefined(); + expect(result.answer.trim()).toBeTruthy(); + expect(result.answer.toLowerCase()).toContain("blue"); + } finally { + await session.disconnect(); + } } - }); + ); }); diff --git a/nodejs/test/e2e/session.e2e.test.ts b/nodejs/test/e2e/session.e2e.test.ts index d99a3e392d..bab0efc687 100644 --- a/nodejs/test/e2e/session.e2e.test.ts +++ b/nodejs/test/e2e/session.e2e.test.ts @@ -98,7 +98,7 @@ describe("Sessions", () => { it("should create and disconnect sessions", async () => { await using session = await client.createSession({ onPermissionRequest: approveAll, - model: "claude-sonnet-4.5", + model: "claude-sonnet-5", }); expect(session.sessionId).toMatch(/^[a-f0-9-]+$/); @@ -107,7 +107,7 @@ describe("Sessions", () => { expect(sessionStartEvents).toMatchObject([ { type: "session.start", - data: { sessionId: session.sessionId, selectedModel: "claude-sonnet-4.5" }, + data: { sessionId: session.sessionId, selectedModel: "claude-sonnet-5" }, }, ]); @@ -252,10 +252,13 @@ describe("Sessions", () => { // It only tells the model about the specified tools and no others const traffic = await waitForExchanges(); - expect(traffic[0].request.tools).toMatchObject([ - { function: { name: "view" } }, - { function: { name: "edit" } }, - ]); + expect(traffic[0].request.tools).toHaveLength(2); + expect(traffic[0].request.tools).toEqual( + expect.arrayContaining([ + expect.objectContaining({ function: expect.objectContaining({ name: "view" }) }), + expect.objectContaining({ function: expect.objectContaining({ name: "edit" }) }), + ]) + ); }); it("should create a session with excludedTools", async () => { diff --git a/nodejs/test/e2e/session_config.e2e.test.ts b/nodejs/test/e2e/session_config.e2e.test.ts index 85137e0ff9..8d041f1ec8 100644 --- a/nodejs/test/e2e/session_config.e2e.test.ts +++ b/nodejs/test/e2e/session_config.e2e.test.ts @@ -119,6 +119,7 @@ describe("Session Configuration", async () => { const session = await client.createSession({ onPermissionRequest: approveAll, + model: "claude-sonnet-5", modelCapabilities: { supports: { vision: false } }, }); @@ -129,7 +130,7 @@ describe("Session Configuration", async () => { expect(hasImageUrlContent(t1Messages)).toBe(false); // Switch vision on (re-specify same model with updated capabilities) - await session.setModel("claude-sonnet-4.5", { + await session.setModel("claude-sonnet-5", { modelCapabilities: { supports: { vision: true } }, }); @@ -149,6 +150,7 @@ describe("Session Configuration", async () => { const session = await client.createSession({ onPermissionRequest: approveAll, + model: "claude-sonnet-5", modelCapabilities: { supports: { vision: true } }, }); @@ -159,7 +161,7 @@ describe("Session Configuration", async () => { expect(hasImageUrlContent(t1Messages)).toBe(true); // Switch vision off - await session.setModel("claude-sonnet-4.5", { + await session.setModel("claude-sonnet-5", { modelCapabilities: { supports: { vision: false } }, }); @@ -342,7 +344,7 @@ describe("Session Configuration", async () => { id: "msg_stub_1", type: "message", role: "assistant", - model: "claude-sonnet-4.5", + model: "claude-sonnet-5", content: [], stop_reason: null, stop_sequence: null, @@ -384,8 +386,8 @@ describe("Session Configuration", async () => { return json({ data: [ { - id: "claude-sonnet-4.5", - name: "Claude Sonnet 4.5", + id: "claude-sonnet-5", + name: "Claude Sonnet 5", object: "model", vendor: "Anthropic", version: "1", @@ -393,7 +395,7 @@ describe("Session Configuration", async () => { model_picker_enabled: true, capabilities: { type: "chat", - family: "claude-sonnet-4.5", + family: "claude-sonnet-5", tokenizer: "o200k_base", limits: { max_context_window_tokens: 200000, max_output_tokens: 8192 }, supports: { @@ -423,7 +425,7 @@ describe("Session Configuration", async () => { id: "msg_stub_1", type: "message", role: "assistant", - model: "claude-sonnet-4.5", + model: "claude-sonnet-5", content: [{ type: "text", text: "OK from the synthetic stream." }], stop_reason: "end_turn", stop_sequence: null, @@ -434,7 +436,7 @@ describe("Session Configuration", async () => { id: "chatcmpl-stub-1", object: "chat.completion", created: 1, - model: "claude-sonnet-4.5", + model: "claude-sonnet-5", choices: [ { index: 0, @@ -462,8 +464,8 @@ describe("Session Configuration", async () => { type: "anthropic" as const, baseUrl: "https://anthropic-citations.invalid/v1", apiKey: "test-provider-key", - modelId: "claude-sonnet-4.5", - wireModel: "claude-sonnet-4.5", + modelId: "claude-sonnet-5", + wireModel: "claude-sonnet-5", }; } @@ -563,7 +565,7 @@ describe("Session Configuration", async () => { try { const session = await citationClient.createSession({ onPermissionRequest: approveAll, - model: "claude-sonnet-4.5", + model: "claude-sonnet-5", enableCitations: true, provider: createAnthropicProvider(), }); @@ -607,7 +609,7 @@ describe("Session Configuration", async () => { try { const session2 = await resumeClient.resumeSession(session1.sessionId, { onPermissionRequest: approveAll, - model: "claude-sonnet-4.5", + model: "claude-sonnet-5", enableCitations: true, provider: createAnthropicProvider(), }); @@ -711,7 +713,7 @@ describe("Session Configuration", async () => { it("should forward custom provider headers on create", async () => { const session = await client.createSession({ onPermissionRequest: approveAll, - model: "claude-sonnet-4.5", + model: "claude-sonnet-5", provider: createProxyProvider("create-provider-header"), }); @@ -734,7 +736,7 @@ describe("Session Configuration", async () => { const session2 = await client.resumeSession(sessionId, { onPermissionRequest: approveAll, - model: "claude-sonnet-4.5", + model: "claude-sonnet-5", provider: createProxyProvider("resume-provider-header"), }); @@ -762,7 +764,7 @@ describe("Session Configuration", async () => { // tests for serialization coverage). const session = await client.createSession({ onPermissionRequest: approveAll, - model: "claude-sonnet-4.5", + model: "claude-sonnet-5", provider: { type: "openai", baseUrl: openAiEndpoint.url, @@ -791,7 +793,7 @@ describe("Session Configuration", async () => { type: "openai", baseUrl: openAiEndpoint.url, apiKey: "test-provider-key", - modelId: "claude-sonnet-4.5", + modelId: "claude-sonnet-5", }, }); @@ -799,7 +801,7 @@ describe("Session Configuration", async () => { const exchanges = await openAiEndpoint.getExchanges(); expect(exchanges.length).toBe(1); - expect(exchanges[0].request.model).toBe("claude-sonnet-4.5"); + expect(exchanges[0].request.model).toBe("claude-sonnet-5"); await session.disconnect(); }); diff --git a/nodejs/test/e2e/session_fs.e2e.test.ts b/nodejs/test/e2e/session_fs.e2e.test.ts index 5726d39b5f..9727de06e1 100644 --- a/nodejs/test/e2e/session_fs.e2e.test.ts +++ b/nodejs/test/e2e/session_fs.e2e.test.ts @@ -145,9 +145,11 @@ describe("Session Fs", async () => { prompt: "Call the get_big_string tool and reply with the word DONE only.", }); - // The tool result should reference a temp file under the session state path + // The tool result should reference a temp file under the session state path. + // The CLI joins the temp path using the host separator, so normalize before + // matching to keep the assertion valid on Windows. const messages = await session.getEvents(); - const toolResult = findToolCallResult(messages, "get_big_string"); + const toolResult = findToolCallResult(messages, "get_big_string")?.replaceAll("\\", "/"); expect(toolResult).toContain(`${sessionStatePath}/temp/`); const filename = toolResult?.match( new RegExp(`(${escapeRegExp(sessionStatePath)}/temp/[^\\s]+)`) diff --git a/nodejs/test/e2e/telemetry.e2e.test.ts b/nodejs/test/e2e/telemetry.e2e.test.ts index c0f71ebfc6..66a0bb8cef 100644 --- a/nodejs/test/e2e/telemetry.e2e.test.ts +++ b/nodejs/test/e2e/telemetry.e2e.test.ts @@ -103,11 +103,6 @@ describe("Telemetry export", async () => { expect(span.instrumentationScope?.name).toBe(sourceName); } - // All spans for one SDK turn must share the same trace id and must not be in error state. - const traceIds = Array.from( - new Set(spans.map((span) => span.traceId).filter((id): id is string => Boolean(id))) - ); - expect(traceIds).toHaveLength(1); for (const span of spans) { expect(span.status?.code).not.toBe(2); } @@ -122,6 +117,8 @@ describe("Telemetry export", async () => { expect(isRootSpan(invokeAgentSpan!)).toBe(true); const invokeAgentSpanId = invokeAgentSpan!.spanId; expect(invokeAgentSpanId).toBeTruthy(); + const invokeAgentTraceId = invokeAgentSpan!.traceId; + expect(invokeAgentTraceId).toBeTruthy(); const chatSpans = spans.filter( (span) => getStringAttribute(span, "gen_ai.operation.name") === "chat" @@ -129,6 +126,7 @@ describe("Telemetry export", async () => { expect(chatSpans.length).toBeGreaterThan(0); for (const chat of chatSpans) { expect(chat.parentSpanId).toBe(invokeAgentSpanId); + expect(chat.traceId).toBe(invokeAgentTraceId); } expect( chatSpans.some((span) => @@ -148,6 +146,7 @@ describe("Telemetry export", async () => { ); expect(toolSpan).toBeDefined(); expect(toolSpan!.parentSpanId).toBe(invokeAgentSpanId); + expect(toolSpan!.traceId).toBe(invokeAgentTraceId); expect(getStringAttribute(toolSpan!, "gen_ai.tool.name")).toBe(toolName); expect(getStringAttribute(toolSpan!, "gen_ai.tool.call.id")).toBeTruthy(); expect(getStringAttribute(toolSpan!, "gen_ai.tool.call.arguments")).toBe( diff --git a/nodejs/test/e2e/tool_results.e2e.test.ts b/nodejs/test/e2e/tool_results.e2e.test.ts index 6e8729c429..eb6ecf6f79 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 c505f8aa86..16db1aca87 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,50 @@ describe("Custom tools", async () => { expect(assistantMessage?.data.content).toContain("HELLO"); }); + // TODO(cli-1.0.81-2): CLI 1.0.81-2 stops servicing nested requests on the same stdio + // connection while awaiting a tool handler, so calling session.history.clearContext + // from inside a terminal tool deadlocks. Tracked as a runtime reentrancy regression; + // re-enable once the fixed CLI ships. + it.skip("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/e2e/ui_elicitation.e2e.test.ts b/nodejs/test/e2e/ui_elicitation.e2e.test.ts index 2e85dd5af2..51195206aa 100644 --- a/nodejs/test/e2e/ui_elicitation.e2e.test.ts +++ b/nodejs/test/e2e/ui_elicitation.e2e.test.ts @@ -5,7 +5,11 @@ import { afterAll, describe, expect, it } from "vitest"; import { CopilotClient, approveAll, RuntimeConnection } from "../../src/index.js"; import type { SessionEvent } from "../../src/index.js"; -import { createSdkTestContext, isInProcessTransport } from "./harness/sdkTestContext.js"; +import { + createSdkTestContext, + getLegacyCliPathForTests, + isInProcessTransport, +} from "./harness/sdkTestContext.js"; describe("UI Elicitation", async () => { const { copilotClient: client } = await createSdkTestContext(); @@ -38,6 +42,38 @@ describe("UI Elicitation Callback", async () => { } ); + // In-process sessions do not expose current tool metadata for introspection. + it.skipIf(isInProcessTransport)( + "session created with the elicitation ask-user variant exposes the structured tool", + { timeout: 60_000 }, + async () => { + const legacyClient = ctx.createClient({ + connection: RuntimeConnection.forStdio({ + path: await getLegacyCliPathForTests(), + }), + }); + try { + const session = await legacyClient.createSession({ + onPermissionRequest: approveAll, + askUserVariant: "elicitation", + onElicitationRequest: async () => ({ action: "accept", content: {} }), + }); + + await session.rpc.tools.initializeAndValidate(); + const { tools } = await session.rpc.tools.getCurrentMetadata(); + const askUserSchema = tools?.find((tool) => tool.name === "ask_user") + ?.input_schema as { properties?: Record } | undefined; + + expect(askUserSchema?.properties).toHaveProperty("message"); + expect(askUserSchema?.properties).toHaveProperty("requestedSchema"); + expect(askUserSchema?.properties).not.toHaveProperty("question"); + await session.disconnect(); + } finally { + await legacyClient.stop(); + } + } + ); + it( "session created without onElicitationRequest reports no elicitation capability", { timeout: 60_000 }, diff --git a/nodejs/test/extension.test.ts b/nodejs/test/extension.test.ts index e94ad2204f..bebe8db7f0 100644 --- a/nodejs/test/extension.test.ts +++ b/nodejs/test/extension.test.ts @@ -47,6 +47,39 @@ describe("joinSession", () => { expect(config.suppressResumeEvent).toBe(false); }); + it("forwards the requested environment variables and keeps them off the resume config", async () => { + process.env.SESSION_ID = "session-123"; + const resumeForExtension = vi + .spyOn(CopilotClient.prototype, "resumeSessionForExtension") + .mockResolvedValue({} as any); + + await joinSession({ + requestedEnvironmentVariables: ["GITHUB_TOKEN", "MY_SECRET"], + tools: [], + }); + + const [, config, , extensionOptions] = resumeForExtension.mock.calls[0]!; + expect(extensionOptions).toEqual({ + requestedEnvironmentVariables: ["GITHUB_TOKEN", "MY_SECRET"], + }); + expect(config).not.toHaveProperty("requestedEnvironmentVariables"); + }); + + it("requests no environment variables when the option is omitted or empty", async () => { + process.env.SESSION_ID = "session-123"; + const resumeForExtension = vi + .spyOn(CopilotClient.prototype, "resumeSessionForExtension") + .mockResolvedValue({} as any); + + await joinSession({ tools: [] }); + await joinSession({ requestedEnvironmentVariables: [], tools: [] }); + + expect(resumeForExtension.mock.calls[0]![3]).toBeUndefined(); + // An empty list means the same as omitting the option, so it must not put + // an environment request on the wire either. + expect(resumeForExtension.mock.calls[1]![3]).toBeUndefined(); + }); + it("exports the canvas helper from the extension surface", () => { const canvas = createCanvas({ id: "counter", diff --git a/nodejs/test/factory.test.ts b/nodejs/test/factory.test.ts index 0282cc4a58..c9b8f65074 100644 --- a/nodejs/test/factory.test.ts +++ b/nodejs/test/factory.test.ts @@ -15,6 +15,7 @@ import { type FactoryAgentOptions, type FactoryContext, type FactoryDefinition, + type FactoryJsonSchema, type JsonValue, } from "../src/factory.js"; @@ -402,6 +403,195 @@ describe("factories", () => { expect(generatedRpc).toContain("timeoutSeconds?: number;"); }); + // A guessed ceiling does not make a run safer: it stops a healthy run partway + // with `factory_limit_reached`, after that run has already taken the user's + // approval and spent credits. Both documents are handed to the model verbatim + // by the `factories_manage` guide, so neither may read as an invitation to + // invent one. + it("documents limits as opt-in rather than inviting an invented ceiling", () => { + const guide = readFileSync(new URL("../docs/factories.md", import.meta.url), "utf8"); + const patterns = readFileSync( + new URL("../docs/factory-patterns.md", import.meta.url), + "utf8" + ); + + expect(guide).toContain( + "Set a ceiling only from real knowledge of what the factory costs, or because the user named one" + ); + expect(guide).toContain("no basis for estimating a number"); + + // The opening `defineFactory` sample is the shape an author copies. Filling + // all four ceilings in there taught the numbers as much as the syntax. + const openingSample = guide.slice(0, guide.indexOf("## Declaring an argument shape")); + expect(openingSample).not.toContain("limits: {"); + + // The Scaling section used to answer "there is no built-in concurrency cap" + // with "so declare one before fanning out widely". + expect(patterns).not.toContain("declare one before fanning out widely"); + expect(patterns).toContain("bound a wide fan-out with the factory's own counters"); + }); + + 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 listRunsMetadata = ["oldestSeq", "newestSeq", "hasMoreNewer", "omittedOlder"]; + 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); + for (const field of listRunsMetadata) { + expect(document).toContain(field); + } + } + + 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(); @@ -469,7 +659,8 @@ describe("factories", () => { expect(resumeSessionForExtension).toHaveBeenCalledWith( "session-extension", expect.objectContaining({ suppressResumeEvent: true }), - [factory] + [factory], + undefined ); }); @@ -569,6 +760,133 @@ describe("factories", () => { 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); @@ -653,6 +971,95 @@ describe("factories", () => { }); }); + 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") { @@ -1097,14 +1504,31 @@ describe("factories", () => { revision: 4, }; const detail = { ...summary, phases: [], agents: [], progress }; + const runsPage = { + runs: [summary], + oldestSeq: 11, + newestSeq: 12, + hasMoreNewer: true, + omittedOlder: 10, + }; const sendRequest = vi.fn(async (method: string) => { - if (method === "session.factory.listRuns") return { runs: [summary] }; + if (method === "session.factory.listRuns") return runsPage; 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]); + const listedPage = await session.factory.listRuns({ + afterSeq: 10, + beforeSeq: 20, + limit: 50, + }); + expect(listedPage).toEqual(runsPage); + expect(listedPage.oldestSeq).toBe(11); + expect(listedPage.newestSeq).toBe(12); + expect(listedPage.hasMoreNewer).toBe(true); + expect(listedPage.omittedOlder).toBe(10); await expect(session.factory.getRunDetail("run-observe")).resolves.toEqual(detail); await expect( session.factory.getRunProgress("run-observe", { @@ -1116,11 +1540,17 @@ describe("factories", () => { expect(sendRequest).toHaveBeenNthCalledWith(1, "session.factory.listRuns", { sessionId: session.sessionId, }); - expect(sendRequest).toHaveBeenNthCalledWith(2, "session.factory.getRunDetail", { + expect(sendRequest).toHaveBeenNthCalledWith(2, "session.factory.listRuns", { + sessionId: session.sessionId, + afterSeq: 10, + beforeSeq: 20, + limit: 50, + }); + expect(sendRequest).toHaveBeenNthCalledWith(3, "session.factory.getRunDetail", { sessionId: session.sessionId, runId: "run-observe", }); - expect(sendRequest).toHaveBeenNthCalledWith(3, "session.factory.getRunProgress", { + expect(sendRequest).toHaveBeenNthCalledWith(4, "session.factory.getRunProgress", { sessionId: session.sessionId, runId: "run-observe", phaseId: "p0", @@ -1431,6 +1861,56 @@ describe("factories", () => { ); }); + 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") { @@ -1679,6 +2159,8 @@ describe("factories", () => { await expect( session.factory.resume("run-prior", { limits: { maxTotalSubagents: 7 }, + notifyOnComplete: true, + logPhaseNames: true, }) ).resolves.toMatchObject({ status: "completed", @@ -1688,13 +2170,20 @@ describe("factories", () => { session.factory.run("by-name", { args: { value: 1 }, limits: { maxTotalSubagents: 7 }, + notifyOnComplete: false, + logPhaseNames: true, resumeFromRunId: "run-prior", }) ).resolves.toMatchObject({ status: "completed", result: { name: "stored-name", persistedArgs: true }, }); - await expect(session.factory.run(factory)).resolves.toMatchObject({ + await expect( + session.factory.run(factory, { + notifyOnComplete: true, + logPhaseNames: false, + }) + ).resolves.toMatchObject({ status: "completed", result: { name: "friendly-run" }, }); @@ -1702,17 +2191,25 @@ describe("factories", () => { sessionId: session.sessionId, runId: "run-prior", limits: { maxTotalSubagents: 7 }, + notifyOnComplete: true, + logPhaseNames: true, }); expect(sendRequest).toHaveBeenNthCalledWith(2, "session.factory.resume", { sessionId: session.sessionId, runId: "run-prior", limits: { maxTotalSubagents: 7 }, + notifyOnComplete: false, + logPhaseNames: true, }); expect(sendRequest).toHaveBeenNthCalledWith(3, "session.factory.run", { sessionId: session.sessionId, name: "friendly-run", args: {}, - options: { limits: undefined }, + options: { + limits: undefined, + notifyOnComplete: true, + logPhaseNames: false, + }, }); }); @@ -1736,8 +2233,11 @@ describe("factories", () => { "not_found", "non_resumable", "already_active", - "reapproval_declined", - "no_approval_provider", + "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) => { @@ -1755,6 +2255,21 @@ describe("factories", () => { } ); + 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", diff --git a/nodejs/test/github-token-provider.test.ts b/nodejs/test/github-token-provider.test.ts new file mode 100644 index 0000000000..2202f466a1 --- /dev/null +++ b/nodejs/test/github-token-provider.test.ts @@ -0,0 +1,292 @@ +import { describe, expect, it, vi } from "vitest"; +import { CopilotClient, RuntimeConnection, type GitHubTokenProvider } from "../src/index.js"; + +function createMockClient( + request: (method: string, params: Record) => Promise +): CopilotClient { + const client = new CopilotClient({ + connection: RuntimeConnection.forUri("localhost:1234"), + }); + (client as unknown as { connection: unknown }).connection = { + sendRequest: request, + dispose: vi.fn(), + }; + return client; +} + +function getTokenHandler(client: CopilotClient) { + return ( + client as unknown as { + clientGlobalHandlers: { + gitHubToken: { + getToken(params: { + registrationId: string; + host: string; + sessionId?: string; + reason: "initial" | "refresh"; + }): Promise; + }; + }; + } + ).clientGlobalHandlers.gitHubToken.getToken; +} + +describe("session GitHub token providers", () => { + it("rejects a static token and provider together", async () => { + const client = new CopilotClient({ + connection: RuntimeConnection.forUri("localhost:1234"), + }); + + await expect( + client.createSession({ + gitHubToken: "static", + gitHubTokenProvider: async () => ({ + kind: "token", + accessToken: "dynamic", + expiresIn: 28_800, + }), + }) + ).rejects.toThrow("gitHubToken and gitHubTokenProvider are mutually exclusive"); + }); + + it("serializes only the opaque registration and maps token and cancellation results", async () => { + let createPayload: Record | undefined; + const request = vi.fn(async (method: string, params: Record) => { + if (method === "session.create") { + createPayload = params; + return { sessionId: params.sessionId }; + } + throw new Error(`Unexpected method: ${method}`); + }); + const observed: unknown[] = []; + const provider: GitHubTokenProvider = vi + .fn() + .mockImplementationOnce(async (args) => { + observed.push(args); + return { + kind: "token", + accessToken: "secret-token", + tokenType: "Bearer", + expiresIn: 28_800, + }; + }) + .mockImplementationOnce(async (args) => { + observed.push(args); + return { kind: "cancelled" }; + }); + const client = createMockClient(request); + const session = await client.createSession({ + sessionId: "session-one", + gitHubTokenProvider: provider, + }); + + const registrationId = createPayload?.gitHubTokenProviderRegistrationId; + expect(registrationId).toMatch( + /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/ + ); + expect(createPayload).not.toHaveProperty("gitHubTokenProvider"); + expect(createPayload?.gitHubToken).toBeUndefined(); + + const handler = getTokenHandler(client); + await expect( + handler({ + registrationId: registrationId as string, + host: "github.example.com", + reason: "initial", + }) + ).resolves.toEqual({ + kind: "token", + accessToken: "secret-token", + tokenType: "Bearer", + expiresIn: 28_800, + }); + await expect( + handler({ + registrationId: registrationId as string, + host: "github.example.com", + sessionId: session.sessionId, + reason: "refresh", + }) + ).resolves.toEqual({ kind: "cancelled" }); + expect(observed).toEqual([ + { + host: "github.example.com", + sessionId: "session-one", + reason: "initial", + }, + { + host: "github.example.com", + sessionId: "session-one", + reason: "refresh", + }, + ]); + }); + + it("preserves callback errors and rejects unknown registrations", async () => { + const client = createMockClient(async (_method, params) => ({ + sessionId: params.sessionId, + })); + const failure = new Error("credential broker failed"); + await client.createSession({ + sessionId: "error-session", + gitHubTokenProvider: () => { + throw failure; + }, + }); + const registrationId = [ + ...( + client as unknown as { + githubTokenProviders: Map; + } + ).githubTokenProviders.keys(), + ][0]; + const handler = getTokenHandler(client); + + await expect( + handler({ + registrationId, + host: "github.com", + reason: "initial", + }) + ).rejects.toBe(failure); + await expect( + handler({ + registrationId: "unknown", + host: "github.com", + reason: "refresh", + }) + ).rejects.toThrow("No GitHub token provider registered"); + }); + + it("rolls back failed creation and cleans up on session and client close", async () => { + const failingClient = createMockClient(async () => { + throw new Error("create failed"); + }); + await expect( + failingClient.createSession({ + gitHubTokenProvider: async () => ({ kind: "cancelled" }), + }) + ).rejects.toThrow("create failed"); + expect( + ( + failingClient as unknown as { + githubTokenProviders: Map; + } + ).githubTokenProviders + ).toHaveLength(0); + + const client = createMockClient(async (method, params) => { + if (method === "session.create") return { sessionId: params.sessionId }; + if (method === "session.destroy") return {}; + if (method === "session.delete") return { success: true }; + throw new Error(`Unexpected method: ${method}`); + }); + const first = await client.createSession({ + sessionId: "first", + gitHubTokenProvider: async () => ({ kind: "cancelled" }), + }); + await client.createSession({ + sessionId: "second", + gitHubTokenProvider: async () => ({ kind: "cancelled" }), + }); + const registrations = ( + client as unknown as { + githubTokenProviders: Map; + } + ).githubTokenProviders; + expect(registrations).toHaveLength(2); + + await first.disconnect(); + expect(registrations).toHaveLength(1); + await client.deleteSession("second"); + expect(registrations).toHaveLength(0); + await client.forceStop(); + expect(registrations).toHaveLength(0); + }); + + it("rotates a resumed session only after resume succeeds", async () => { + const payloads: Record[] = []; + const client = createMockClient(async (method, params) => { + payloads.push(params); + if (method === "session.create" || method === "session.resume") { + return { sessionId: params.sessionId }; + } + throw new Error(`Unexpected method: ${method}`); + }); + const firstProvider = vi.fn(async () => ({ kind: "cancelled" as const })); + const secondProvider = vi.fn(async () => ({ kind: "cancelled" as const })); + await client.createSession({ + sessionId: "resumed", + gitHubTokenProvider: firstProvider, + }); + const firstRegistration = payloads[0].gitHubTokenProviderRegistrationId as string; + + await client.resumeSession("resumed", { + gitHubTokenProvider: secondProvider, + }); + const secondRegistration = payloads[1].gitHubTokenProviderRegistrationId as string; + const handler = getTokenHandler(client); + + await expect( + handler({ + registrationId: firstRegistration, + host: "github.com", + reason: "refresh", + }) + ).rejects.toThrow("No GitHub token provider registered"); + await expect( + handler({ + registrationId: secondRegistration, + host: "github.com", + reason: "refresh", + }) + ).resolves.toEqual({ kind: "cancelled" }); + expect(secondProvider).toHaveBeenCalledOnce(); + expect(firstProvider).not.toHaveBeenCalled(); + }); + + it("does not retire a concurrent pending registration", async () => { + const resumeResolvers: Array<(value: { sessionId: string }) => void> = []; + const payloads: Record[] = []; + const client = createMockClient(async (method, params) => { + payloads.push(params); + if (method === "session.create") { + return { sessionId: params.sessionId }; + } + if (method === "session.resume") { + return await new Promise<{ sessionId: string }>((resolve) => { + resumeResolvers.push(resolve); + }); + } + throw new Error(`Unexpected method: ${method}`); + }); + await client.createSession({ + sessionId: "concurrent", + gitHubTokenProvider: async () => ({ kind: "cancelled" }), + }); + + const firstResume = client.resumeSession("concurrent", { + gitHubTokenProvider: async () => ({ kind: "cancelled" }), + }); + const secondResume = client.resumeSession("concurrent", { + gitHubTokenProvider: async () => ({ kind: "cancelled" }), + }); + await vi.waitFor(() => expect(resumeResolvers).toHaveLength(2)); + + resumeResolvers[0]({ sessionId: "concurrent" }); + await firstResume; + const registrations = ( + client as unknown as { + githubTokenProviders: Map; + } + ).githubTokenProviders; + expect(registrations).toHaveLength(2); + + resumeResolvers[1]({ sessionId: "concurrent" }); + await secondResume; + expect(registrations).toHaveLength(1); + expect(registrations.has(payloads[2].gitHubTokenProviderRegistrationId as string)).toBe( + true + ); + }); +}); diff --git a/nodejs/test/runtimeArtifacts.test.ts b/nodejs/test/runtimeArtifacts.test.ts new file mode 100644 index 0000000000..ffd22f21ef --- /dev/null +++ b/nodejs/test/runtimeArtifacts.test.ts @@ -0,0 +1,330 @@ +import { existsSync, mkdtempSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join, resolve } from "node:path"; +import { createHash } from "node:crypto"; +import { spawnSync } from "node:child_process"; +import { c as createTar } from "tar"; +import { afterEach, describe, expect, it, vi } from "vitest"; + +import { + defaultRuntimeCacheRoot, + ensureRuntimeBundle, + getRuntimePackageName, + getRuntimePlatform, + getRuntimeReleaseAssetName, + materializeRuntimeBundle, +} from "../src/runtimeArtifacts.js"; +import { COPILOT_CLI_USE_NPM_PACKAGE, COPILOT_CLI_VERSION } from "../src/cliVersion.js"; +import { ensureCopilotPackage } from "../scripts/releaseArtifacts.js"; + +describe("defaultRuntimeCacheRoot", () => { + it.each([ + [ + "darwin", + "/home/test", + {}, + join("/home/test", "Library", "Caches", "github-copilot-sdk", "runtime"), + ], + ["linux", "/home/test", {}, join("/home/test", ".cache", "github-copilot-sdk", "runtime")], + [ + "linux", + "/home/test", + { XDG_CACHE_HOME: "/cache" }, + join("/cache", "github-copilot-sdk", "runtime"), + ], + [ + "win32", + "C:\\Users\\test", + { LOCALAPPDATA: "C:\\Users\\test\\AppData\\Local" }, + join("C:\\Users\\test\\AppData\\Local", "github-copilot-sdk", "runtime"), + ], + ])("uses the %s user cache directory", (platform, home, environment, expected) => { + expect(defaultRuntimeCacheRoot(platform, home, environment)).toBe(expected); + }); +}); + +describe("release runtime selection", () => { + it("keeps the compiled CLI version aligned with package metadata", () => { + const packageJson = JSON.parse( + readFileSync(join(import.meta.dirname, "../package.json"), "utf8") + ); + expect(COPILOT_CLI_VERSION).toBe(packageJson.copilotCliVersion); + // lgtm[js/trivial-conditional] This generated constant is true for internal canary builds. + if (COPILOT_CLI_USE_NPM_PACKAGE) { + expect(packageJson.dependencies["@github/copilot"]).toBe(COPILOT_CLI_VERSION); + } else { + expect(packageJson.dependencies).not.toHaveProperty("@github/copilot"); + } + }); + + it("can pin an internal npm package without contacting GitHub Releases", () => { + const root = mkdtempSync(join(tmpdir(), "copilot-cli-version-")); + mkdirSync(join(root, "scripts"), { recursive: true }); + mkdirSync(join(root, "src"), { recursive: true }); + writeFileSync(join(root, "package.json"), "{}\n"); + writeFileSync( + join(root, "scripts", "set-cli-version.js"), + readFileSync(join(import.meta.dirname, "../scripts/set-cli-version.js")) + ); + + const result = spawnSync( + process.execPath, + [join(root, "scripts", "set-cli-version.js"), "9.9.9-canary.test", "--npm-package"], + { encoding: "utf8" } + ); + expect(result.status, result.stderr).toBe(0); + expect(JSON.parse(readFileSync(join(root, "package.json"), "utf8"))).toMatchObject({ + copilotCliVersion: "9.9.9-canary.test", + }); + expect(existsSync(join(root, "copilot-cli.json"))).toBe(false); + expect(readFileSync(join(root, "src", "cliVersion.ts"), "utf8")).toContain( + "COPILOT_CLI_USE_NPM_PACKAGE = true" + ); + }); + + it.each([ + ["darwin", "arm64", false, "darwin-arm64"], + ["darwin", "x64", false, "darwin-x64"], + ["linux", "arm64", false, "linux-arm64"], + ["linux", "x64", true, "linuxmusl-x64"], + ["win32", "arm64", false, "win32-arm64"], + ])("maps %s/%s to %s", (platform, arch, musl, expected) => { + expect(getRuntimePlatform(platform, arch, musl)).toBe(expected); + }); + + it("uses the platform npm tarball published in the CLI release", () => { + expect(getRuntimeReleaseAssetName("1.2.3-4", "linux-x64")).toBe( + "github-copilot-1.2.3-4-linux-x64.tgz" + ); + }); + + it("uses the SDK platform package namespace", () => { + expect(getRuntimePackageName("linux-x64")).toBe("@github/copilot-sdk-linux-x64"); + }); +}); + +describe("materializeRuntimeBundle", () => { + afterEach(() => vi.unstubAllEnvs()); + + it("materializes an adjacent pair from an absent cache with a stripped environment", () => { + const sourceDir = mkdtempSync(join(tmpdir(), "copilot-runtime-source-")); + const cacheRoot = join(sourceDir, "absent-cache"); + const emptyPath = join(sourceDir, "empty-path"); + mkdirSync(emptyPath); + const platform = process.platform === "win32" ? "win32-x64" : "test-platform"; + const wrapperName = platform.startsWith("win32") + ? "copilot-runtime.exe" + : "copilot-runtime"; + const prebuilds = join(sourceDir, "prebuilds", platform); + const wrapper = join(prebuilds, wrapperName); + const runtimeNode = join(prebuilds, "runtime.node"); + mkdirSync(prebuilds, { recursive: true }); + writeFileSync(wrapper, "wrapper"); + writeFileSync(runtimeNode, "runtime"); + mkdirSync(join(sourceDir, "ripgrep", "bin", platform), { recursive: true }); + writeFileSync(join(sourceDir, "ripgrep", "bin", platform, "rg"), "ripgrep"); + mkdirSync(join(sourceDir, "definitions"), { recursive: true }); + writeFileSync(join(sourceDir, "definitions", "future.json"), "{}"); + mkdirSync(join(sourceDir, "copilot-sdk"), { recursive: true }); + writeFileSync(join(sourceDir, "copilot-sdk", "extension.js"), "extension SDK"); + mkdirSync(join(sourceDir, "preloads"), { recursive: true }); + writeFileSync(join(sourceDir, "preloads", "extension_bootstrap.mjs"), "bootstrap"); + mkdirSync(join(sourceDir, "sdk"), { recursive: true }); + writeFileSync(join(sourceDir, "sdk", "index.js"), "legacy SDK"); + writeFileSync(join(sourceDir, "app.js"), "excluded"); + writeFileSync(join(sourceDir, "copilot"), "excluded"); + writeFileSync(join(sourceDir, "copilot.exe"), "excluded"); + writeFileSync(join(sourceDir, "LICENSE.md"), "excluded"); + writeFileSync(join(sourceDir, "README.md"), "excluded"); + + vi.stubEnv("PATH", emptyPath); + vi.stubEnv("COPILOT_CLI_PATH", undefined); + vi.stubEnv("COPILOT_RUNTIME_HOST_COMMAND", undefined); + vi.stubEnv("COPILOT_RUNTIME_PROVIDER_LIB", undefined); + + expect(process.env.COPILOT_CLI_PATH).toBeUndefined(); + expect(process.env.COPILOT_RUNTIME_HOST_COMMAND).toBeUndefined(); + expect(process.env.COPILOT_RUNTIME_PROVIDER_LIB).toBeUndefined(); + + const installedWrapper = materializeRuntimeBundle( + { packageRoot: sourceDir, platform }, + cacheRoot + ); + const installDir = resolve(dirname(installedWrapper), "..", ".."); + + expect(readFileSync(installedWrapper, "utf8")).toBe("wrapper"); + expect(readFileSync(join(installDir, "prebuilds", platform, "runtime.node"), "utf8")).toBe( + "runtime" + ); + expect(readFileSync(join(installDir, "ripgrep", "bin", platform, "rg"), "utf8")).toBe( + "ripgrep" + ); + expect(readFileSync(join(installDir, "copilot-sdk", "extension.js"), "utf8")).toBe( + "extension SDK" + ); + expect(readFileSync(join(installDir, "preloads", "extension_bootstrap.mjs"), "utf8")).toBe( + "bootstrap" + ); + expect(readFileSync(join(installDir, "sdk", "index.js"), "utf8")).toBe("legacy SDK"); + expect(existsSync(join(installDir, "app.js"))).toBe(false); + expect(existsSync(join(installDir, "copilot"))).toBe(false); + expect(existsSync(join(installDir, "copilot.exe"))).toBe(false); + expect(existsSync(join(installDir, "LICENSE.md"))).toBe(false); + expect(existsSync(join(installDir, "README.md"))).toBe(false); + if (process.platform !== "win32") { + expect(statSync(installedWrapper).mode & 0o111).not.toBe(0); + } + }); + + it("fails clearly when the package has no runtime.node", () => { + const sourceDir = mkdtempSync(join(tmpdir(), "copilot-runtime-missing-node-")); + const platform = process.platform === "win32" ? "win32-x64" : "test-platform"; + const wrapperName = platform.startsWith("win32") + ? "copilot-runtime.exe" + : "copilot-runtime"; + const prebuilds = join(sourceDir, "prebuilds", platform); + const wrapper = join(prebuilds, wrapperName); + mkdirSync(prebuilds, { recursive: true }); + writeFileSync(wrapper, "wrapper"); + + expect(() => + materializeRuntimeBundle( + { + packageRoot: sourceDir, + platform, + }, + join(sourceDir, "cache") + ) + ).toThrow(/Copilot runtime\.node not found/); + }); +}); + +describe("ensureRuntimeBundle", () => { + it("resolves the installed platform runtime without network access", async () => { + const root = mkdtempSync(join(tmpdir(), "copilot-packaged-runtime-")); + const nodeModules = join(root, "node_modules"); + const platform = "linux-x64"; + const packageRoot = join(nodeModules, ...getRuntimePackageName(platform).split("/")); + const prebuilds = join(packageRoot, "prebuilds", platform); + mkdirSync(prebuilds, { recursive: true }); + writeFileSync(join(packageRoot, "package.json"), "{}"); + writeFileSync(join(prebuilds, "copilot-runtime"), "wrapper"); + writeFileSync(join(prebuilds, "runtime.node"), "runtime"); + mkdirSync(join(packageRoot, "schemas")); + writeFileSync(join(packageRoot, "schemas", "api.schema.json"), "{}"); + const fetcher = vi.fn(() => { + throw new Error("runtime resolution must not fetch"); + }); + vi.stubGlobal("fetch", fetcher); + + const runtimePath = await ensureRuntimeBundle(COPILOT_CLI_VERSION, { + packageSearchPaths: [nodeModules], + platform, + }); + + expect(runtimePath).toBe(join(prebuilds, "copilot-runtime")); + expect(readFileSync(join(dirname(runtimePath), "runtime.node"), "utf8")).toBe("runtime"); + expect(fetcher).not.toHaveBeenCalled(); + vi.unstubAllGlobals(); + }); + + it("fails clearly when the platform package is not installed", async () => { + await expect( + ensureRuntimeBundle(COPILOT_CLI_VERSION, { + packageSearchPaths: [], + platform: "linux-x64", + }) + ).rejects.toThrow( + "Could not resolve @github/copilot-sdk-linux-x64. Reinstall @github/copilot-sdk" + ); + }); +}); + +describe("release package acquisition", () => { + it("downloads, verifies, and caches a release package for packaging", async () => { + const sourceRoot = mkdtempSync(join(tmpdir(), "copilot-release-source-")); + const packageRoot = join(sourceRoot, "package"); + const platform = "linux-x64"; + const prebuilds = join(packageRoot, "prebuilds", platform); + mkdirSync(prebuilds, { recursive: true }); + writeFileSync(join(prebuilds, "copilot-runtime"), "wrapper"); + writeFileSync(join(prebuilds, "runtime.node"), "runtime"); + mkdirSync(join(packageRoot, "schemas"), { recursive: true }); + writeFileSync(join(packageRoot, "schemas", "api.schema.json"), "{}"); + + const archivePath = join(sourceRoot, "runtime.tgz"); + await createTar({ cwd: sourceRoot, file: archivePath, gzip: true }, ["package"]); + const archive = readFileSync(archivePath); + const version = "1.2.3-4"; + const assetName = getRuntimeReleaseAssetName(version, platform); + const checksum = createHash("sha256").update(archive).digest("hex"); + const fetcher = vi.fn(async (input: string | URL | Request) => + String(input).endsWith("/SHA256SUMS.txt") + ? new Response(`${checksum} ${assetName}\n`) + : new Response(archive) + ); + const cacheRoot = join(sourceRoot, "cache"); + + const downloadedPackage = await ensureCopilotPackage(version, { + cacheRoot, + fetch: fetcher, + platform, + }); + expect(readFileSync(join(downloadedPackage, "schemas", "api.schema.json"), "utf8")).toBe( + "{}" + ); + + await expect( + ensureCopilotPackage(version, { + cacheRoot, + fetch: fetcher, + platform, + }) + ).resolves.toBe(downloadedPackage); + expect(fetcher).toHaveBeenCalledTimes(2); + }); + + it("rejects a release package that does not match SHA256SUMS.txt", async () => { + const cacheRoot = mkdtempSync(join(tmpdir(), "copilot-release-mismatch-")); + const assetName = getRuntimeReleaseAssetName("1.2.3", "linux-x64"); + const fetcher = vi.fn(async (input: string | URL | Request) => + String(input).endsWith("/SHA256SUMS.txt") + ? new Response(`${"0".repeat(64)} ${assetName}\n`) + : new Response("corrupt archive") + ); + + await expect( + ensureCopilotPackage("1.2.3", { + cacheRoot, + fetch: fetcher, + platform: "linux-x64", + }) + ).rejects.toThrow("Checksum mismatch"); + expect(existsSync(join(cacheRoot, "1.2.3", "packages", "linux-x64"))).toBe(false); + }); + + it("times out and retries a stalled release download", async () => { + const cacheRoot = mkdtempSync(join(tmpdir(), "copilot-release-timeout-")); + const fetcher = vi.fn( + (_input: string | URL | Request, init?: RequestInit) => + new Promise((_resolve, reject) => { + const signal = init?.signal; + if (!signal) { + reject(new Error("Expected a request timeout signal.")); + return; + } + signal.addEventListener("abort", () => reject(signal.reason), { once: true }); + }) + ); + + await expect( + ensureCopilotPackage("1.2.3-timeout", { + cacheRoot, + fetch: fetcher, + fetchTimeoutMs: 10, + platform: "linux-x64", + }) + ).rejects.toThrow("Failed to download"); + expect(fetcher).toHaveBeenCalledTimes(3); + }); +}); diff --git a/nodejs/test/session-event-codegen.test.ts b/nodejs/test/session-event-codegen.test.ts index 14340292be..2dad8f4f18 100644 --- a/nodejs/test/session-event-codegen.test.ts +++ b/nodejs/test/session-event-codegen.test.ts @@ -3,10 +3,37 @@ import { describe, expect, it } from "vitest"; import { generateSessionEventsCode as generateCSharpSessionEventsCode } from "../../scripts/codegen/csharp.ts"; import { generateGoSessionEventsCode } from "../../scripts/codegen/go.ts"; -import { generatePythonSessionEventsCode } from "../../scripts/codegen/python.ts"; +import { + generatePythonSessionEventsCode, + postProcessExternalRefsForPython, +} from "../../scripts/codegen/python.ts"; import { generateSessionEventsCode as generateRustSessionEventsCode } from "../../scripts/codegen/rust.ts"; describe("session event codegen", () => { + it("replaces external reference placeholders regardless of acronym casing", () => { + const code = `@dataclass +class ExternalRefMCPOauthHTTPResponse: + external_ref_marker_external_ref_mcp_oauth_http_response: str + + @staticmethod + def from_dict(obj: Any) -> 'ExternalRefMCPOauthHTTPResponse': + value = obj.get("__externalRefMarker___ExternalRef_McpOauthHttpResponse") + return ExternalRefMCPOauthHTTPResponse(value) + +@dataclass +class ProbeResult: + response: ExternalRefMCPOauthHTTPResponse +`; + + const processed = postProcessExternalRefsForPython( + code, + new Map([["__ExternalRef_McpOauthHttpResponse", "McpOauthHttpResponse"]]) + ); + + expect(processed).toContain("response: McpOauthHttpResponse"); + expect(processed).not.toContain("class ExternalRefMCPOauthHTTPResponse"); + }); + it("maps special schema formats to the expected Python types", () => { const schema: JSONSchema7 = { definitions: { @@ -84,6 +111,7 @@ describe("session event codegen", () => { expect(code).toContain("payload: str"); expect(code).toContain("encoded: str"); expect(code).toContain("count: int"); + expect(code).toContain("assert not isinstance(x, float) or x.is_integer()"); }); it("strips Ms suffixes from duration member names while preserving JSON names", () => { diff --git a/nodejs/test/session-event-types.test.ts b/nodejs/test/session-event-types.test.ts index fef7acdb2b..93edebfc80 100644 --- a/nodejs/test/session-event-types.test.ts +++ b/nodejs/test/session-event-types.test.ts @@ -16,12 +16,20 @@ 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, + AutoTier, + CapiSessionOptions, PermissionRequest, PermissionRequestedData, PermissionRequestedEvent, + PermissionResponseCapability, + ManagedSettingsResolvedData, + ManagedSettingsResolvedEvent, + ManagedSettingsResolvedSource, // *Data payload types from the v0.3.0 generated session-event schema. AssistantMessageData, @@ -56,6 +64,8 @@ import type { WorkingDirectoryContextHostType, FactoryContext, FactoryDefinition, + FactoryAgentOptions, + FactoryRunResult, JsonValue, } from "../src/index.js"; @@ -94,6 +104,21 @@ type _DefaultFactoryResultIsJsonValueOrVoid = _AssertEqual< 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. @@ -105,6 +130,32 @@ type _PermissionRequestedEventStaysAlignedWithSessionEventUnion = _AssertEqual< const _permissionRequestedEventAlignmentCheck: _PermissionRequestedEventStaysAlignedWithSessionEventUnion = true; describe("Session event type exports (#1156)", () => { + it.each(["efficiency", "balance", "intelligence", undefined] satisfies ( + | AutoTier + | undefined + )[])("exposes Auto tier %s on start and resume data", (autoTier) => { + const start: StartData = { + copilotVersion: "1.0.82-1", + producer: "copilot-agent", + sessionId: "session-1", + startTime: "2026-08-28T00:00:00Z", + version: 1, + autoTier, + }; + const resume: ResumeData = { + eventCount: 1, + resumeTime: "2026-08-28T00:01:00Z", + autoTier, + }; + const capi: CapiSessionOptions = { autoTier: start.autoTier }; + expect(capi.autoTier).toBe(autoTier); + expect(resume.autoTier).toBe(autoTier); + if (autoTier === undefined) { + expect(JSON.parse(JSON.stringify(start))).not.toHaveProperty("autoTier"); + expect(JSON.parse(JSON.stringify(resume))).not.toHaveProperty("autoTier"); + } + }); + it("exposes the headline ToolExecutionStartData type with a usable shape", () => { // This is the specific type called out in issue #1156. The annotation // is the compile-time API-surface check; these assertions only validate @@ -163,6 +214,45 @@ describe("Session event type exports (#1156)", () => { 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( @@ -260,6 +350,8 @@ describe("Session event type exports (#1156)", () => { assertImportable(); assertImportable(); assertImportable(); + assertImportable(); + assertImportable(); assertImportable(); assertImportable(); @@ -270,6 +362,8 @@ describe("Session event type exports (#1156)", () => { 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 index 8b6e390c4a..4ee2e8eebc 100644 --- a/nodejs/test/session-send-and-wait.test.ts +++ b/nodejs/test/session-send-and-wait.test.ts @@ -7,7 +7,10 @@ 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 { +function sessionEvent( + type: "session.idle", + data: { mode?: "interactive" | "plan" | "autopilot" } = {} +): SessionEvent { return { type, id: "00000000-0000-4000-8000-000000000001", @@ -106,6 +109,24 @@ describe("sendAndWait", () => { await expect(pending).resolves.toBeUndefined(); }); + it("ignores autopilot continuation idle events", async () => { + const { session, sendStarted, resolveSend } = controlledSession(); + const pending = session.sendAndWait({ prompt: "hi" }); + await sendStarted; + + session._dispatchEvent(sessionEvent("session.idle", { mode: "autopilot" })); + resolveSend(); + + const stateAfterContinuation = await Promise.race([ + pending.then(() => "settled"), + new Promise<"pending">((resolve) => setTimeout(() => resolve("pending"), 0)), + ]); + expect(stateAfterContinuation).toBe("pending"); + + session._dispatchEvent(sessionEvent("session.idle", { mode: "interactive" })); + 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" }); diff --git a/nodejs/test/typescript-codegen.test.ts b/nodejs/test/typescript-codegen.test.ts index e3f3d28571..0a63a5293a 100644 --- a/nodejs/test/typescript-codegen.test.ts +++ b/nodejs/test/typescript-codegen.test.ts @@ -48,6 +48,128 @@ 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", () => { diff --git a/nodejs/vitest.config.ts b/nodejs/vitest.config.ts index bb07cb0174..4ac56bb45e 100644 --- a/nodejs/vitest.config.ts +++ b/nodejs/vitest.config.ts @@ -2,6 +2,55 @@ import { defineConfig } from "vitest/config"; const integrationTestTimeout = process.platform === "win32" ? 60000 : 30000; +const isInProcessTransport = + (process.env.COPILOT_SDK_DEFAULT_CONNECTION ?? "").toLowerCase() === "inprocess"; + +// TODO(cli-1.0.81-2): CLI 1.0.81-5 still stops completing model-driven turns when +// hosted in-process against CAPI. The shared runtime then poisons every later +// model-driven test. These suites still run over stdio on all three OSes, while +// pure-RPC in-process coverage remains enabled. +const inProcessBlockedE2E = [ + "**/test/e2e/abort.e2e.test.ts", + "**/test/e2e/agent_and_compact_rpc.e2e.test.ts", + "**/test/e2e/ask_user.e2e.test.ts", + "**/test/e2e/builtin_tools.e2e.test.ts", + "**/test/e2e/client_api.e2e.test.ts", + "**/test/e2e/client_lifecycle.e2e.test.ts", + "**/test/e2e/compaction.e2e.test.ts", + "**/test/e2e/copilot_request_cancel_error.e2e.test.ts", + "**/test/e2e/copilot_request_handler.e2e.test.ts", + "**/test/e2e/copilot_request_session_id.e2e.test.ts", + "**/test/e2e/disabled_mcp_servers.e2e.test.ts", + "**/test/e2e/event_fidelity.e2e.test.ts", + "**/test/e2e/hooks.e2e.test.ts", + "**/test/e2e/hooks_extended.e2e.test.ts", + "**/test/e2e/mcp_and_agents.e2e.test.ts", + "**/test/e2e/mode_empty.e2e.test.ts", + "**/test/e2e/multi_turn.e2e.test.ts", + "**/test/e2e/permissions.e2e.test.ts", + "**/test/e2e/pre_mcp_tool_call_hook.e2e.test.ts", + "**/test/e2e/provider_endpoint.e2e.test.ts", + "**/test/e2e/rewind.e2e.test.ts", + "**/test/e2e/rpc_event_side_effects.e2e.test.ts", + "**/test/e2e/rpc_session_state.e2e.test.ts", + "**/test/e2e/rpc_session_state_extras.e2e.test.ts", + "**/test/e2e/rpc_shell_and_fleet.e2e.test.ts", + "**/test/e2e/session.e2e.test.ts", + "**/test/e2e/session_config.e2e.test.ts", + "**/test/e2e/session_fs.e2e.test.ts", + "**/test/e2e/session_fs_sqlite.e2e.test.ts", + "**/test/e2e/session_lifecycle.e2e.test.ts", + "**/test/e2e/session_todos_changed.e2e.test.ts", + "**/test/e2e/skills.e2e.test.ts", + "**/test/e2e/streaming_fidelity.e2e.test.ts", + "**/test/e2e/subagent_hooks.e2e.test.ts", + "**/test/e2e/suspend.e2e.test.ts", + "**/test/e2e/system_message_sections.e2e.test.ts", + "**/test/e2e/system_message_transform.e2e.test.ts", + "**/test/e2e/tool_results.e2e.test.ts", + "**/test/e2e/tools.e2e.test.ts", +]; + export default defineConfig({ test: { globals: true, @@ -17,6 +66,7 @@ export default defineConfig({ "**/dist/**", "**/*.d.ts", "**/basic-test.ts", // Old manual test + ...(isInProcessTransport ? inProcessBlockedE2E : []), ], }, }); diff --git a/python/README.md b/python/README.md index 10630fb841..359026df41 100644 --- a/python/README.md +++ b/python/README.md @@ -29,8 +29,9 @@ runtime: python -m copilot download-runtime ``` -This caches the runtime binary locally. If you skip this step, the SDK will -attempt to download it automatically on first use as a fallback. +This caches `copilot-runtime`, its adjacent `runtime.node`, and the compatible +`copilot` host locally. If you skip this step, the SDK downloads the bundle +automatically on first managed stdio/TCP use. To pre-provision the native library required by the in-process (FFI) transport (see [In-process (FFI) transport](#in-process-ffi-transport)), pass `--in-process`: @@ -39,15 +40,15 @@ To pre-provision the native library required by the in-process (FFI) transport python -m copilot download-runtime --in-process ``` -This additionally fetches the native runtime library into the versioned runtime -cache. Stdio/TCP users never download it. When omitted, it is downloaded -lazily on first use of the in-process transport. +This instead provisions the compatible CLI artifact and native runtime library +used by in-process hosting. When omitted, they are downloaded lazily on first +use of the in-process transport. | Platform | Cache path | |----------|-----------| -| Linux | `~/.cache/github-copilot-sdk/cli//copilot` | -| macOS | `~/Library/Caches/github-copilot-sdk/cli//copilot` | -| Windows | `%LOCALAPPDATA%\github-copilot-sdk\cli\\copilot.exe` | +| Linux | `~/.cache/github-copilot-sdk/cli//prebuilds//` | +| macOS | `~/Library/Caches/github-copilot-sdk/cli//prebuilds//` | +| Windows | `%LOCALAPPDATA%\github-copilot-sdk\cli\\prebuilds\\` | ### Environment variables @@ -56,7 +57,8 @@ lazily on first use of the in-process transport. | `COPILOT_CLI_PATH` | Use this specific binary instead of downloading | | `COPILOT_CLI_EXTRACT_DIR` | Override the cache directory (binary placed directly here) | | `COPILOT_SKIP_CLI_DOWNLOAD` | Set to `1` to disable auto-download | -| `COPILOT_CLI_DOWNLOAD_BASE_URL` | Override the GitHub Releases download URL | +| `COPILOT_NPM_REGISTRY_URL` | Override the npm registry used for managed out-of-process and in-process runtime downloads | +| `COPILOT_CLI_DOWNLOAD_BASE_URL` | Override the GitHub Releases download URL used for the root CLI | ## Run the Sample @@ -223,6 +225,10 @@ All options are kw-only parameters: - `RuntimeConnection.for_uri(url, connection_token=None)` — connect to an existing CLI server (e.g. `"localhost:8080"`). - `RuntimeConnection.for_inprocess()` — host the runtime in-process via its native C ABI (FFI). See [In-process (FFI) transport](#in-process-ffi-transport). +Managed stdio and TCP connections use the downloaded `copilot-runtime` +executable with adjacent `runtime.node` by default. An explicit connection +path or `COPILOT_CLI_PATH` overrides the downloaded runtime. + Child-process connections (`for_stdio`/`for_tcp`) also expose a per-connection `env` field for the spawned process. Set it on the returned connection instead of the client-level `env` — setting both raises: @@ -272,6 +278,7 @@ 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.** +- `capi` (CapiSessionOptions): Copilot API options. With `model="auto"`, set `auto_tier` to `"efficiency"`, `"balance"`, or `"intelligence"` to choose a routing preference. Requires a runtime with Auto tier support and V2 Auto routing. Omission preserves default behavior. See [Auto tier persistence](../docs/features/session-persistence.md#auto-tier-persistence) for resume semantics. - `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. @@ -281,9 +288,26 @@ These are passed as keyword arguments to `create_session()`: - `infinite_sessions` (InfiniteSessionConfig): Automatic context compaction configuration - `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. +- `github_token_provider` (callable): Acquires rotating, session-scoped GitHub tokens. Token results require a positive `expiresIn` value in seconds remaining when the callback completes; production tokens typically last eight hours. Cannot be combined with `github_token`. - `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. +- `on_user_input_request` (callable): Handler for legacy question-and-answer requests from the agent. Enables the legacy `ask_user` tool. See [User Input Requests](#user-input-requests) section. +- `ask_user_variant` (`"legacy"` | `"elicitation"`): Selects the model-facing shape of the `ask_user` tool. Defaults to `"legacy"`; use `"elicitation"` with `on_elicitation_request`. Re-supply this option when cold-resuming a session. - `hooks` (SessionHooks): Hook handlers for session lifecycle events. See [Session Hooks](#session-hooks) section. + +```python +async def provide_github_token(args): + return { + "kind": "token", + "accessToken": await acquire_token_for_host(args["host"]), + "expiresIn": 8 * 60 * 60, + } + + +session = await client.create_session(github_token_provider=provide_github_token) +``` + +Initial acquisition runs during session creation or resume. Cancellation, provider errors, and invalid token responses reject that operation instead of falling back to ambient authentication. Idle sessions refresh only before their next credential-consuming operation; there is no background refresh timer. + - `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:** @@ -349,7 +373,7 @@ async with await client.create_session( > **Note:** When using `from __future__ import annotations`, define Pydantic models at module level (not inside functions). -**Low-level API (without Pydantic):** +#### Low-level API (without Pydantic) For users who prefer manual schema definition: @@ -710,9 +734,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): @@ -883,7 +907,7 @@ To let a specific custom tool bypass the permission prompt entirely, set `skip_p ## User Input Requests -Enable the agent to ask questions to the user using the `ask_user` tool by providing an `on_user_input_request` handler: +Enable the legacy question-and-answer `ask_user` tool by providing an `on_user_input_request` handler: ```python async def handle_user_input(request, invocation): diff --git a/python/copilot/__init__.py b/python/copilot/__init__.py index 678fffbf14..5d9e3f9500 100644 --- a/python/copilot/__init__.py +++ b/python/copilot/__init__.py @@ -29,18 +29,29 @@ OpenCanvasInstance, ) from .client import ( + AskUserVariant, + AutoTier, CapiSessionOptions, ChildProcessRuntimeConnection, + ClientInfo, CloudSessionOptions, CloudSessionRepository, CopilotClient, CopilotExpAssignmentResponse, + DisableBypassPermissionsModes, ExpConfigEntry, ExpFlagValue, GetAuthStatusResponse, GetStatusResponse, + GitHubTokenCancelledResult, + GitHubTokenProvider, + GitHubTokenProviderArgs, + GitHubTokenProviderResult, + GitHubTokenResult, InProcessRuntimeConnection, LogLevel, + ManagedSettings, + ManagedSettingsPermissions, ModelBilling, ModelCapabilities, ModelInfo, @@ -83,8 +94,16 @@ GitHubTelemetryClientInfo, GitHubTelemetryEvent, GitHubTelemetryNotification, + GitHubTokenAcquireReason, + GitHubTokenAcquireResult, + GitHubTokenAcquireResultKind, ModelBillingTokenPrices, ModelBillingTokenPricesLongContext, + PermissionDecisionContext, + PermissionDecisionOutcome, + PermissionDecisionSource, + PermissionDecisionSurface, + PermissionResponseCapability, ) from .generated.session_events import ( PermissionRequest, @@ -95,6 +114,7 @@ AgentStopHandler, AgentStopHookInput, AgentStopHookOutput, + AttributedPermissionResult, AutoModeSwitchHandler, AutoModeSwitchRequest, AutoModeSwitchResponse, @@ -176,6 +196,7 @@ UserPromptTransformedHandler, UserPromptTransformedHookInput, UserPromptTransformedHookOutput, + create_attributed_permission_result, ) from .session_fs_provider import ( SessionFsFileInfo, @@ -207,9 +228,12 @@ "AgentStopHandler", "AgentStopHookInput", "AgentStopHookOutput", + "AttributedPermissionResult", "AutoModeSwitchHandler", "AutoModeSwitchRequest", "AutoModeSwitchResponse", + "AskUserVariant", + "AutoTier", "BUILTIN_TOOLS_ISOLATED", "CanvasAction", "CanvasDeclaration", @@ -221,6 +245,7 @@ "CanvasProviderIdentity", "CapiSessionOptions", "ChildProcessRuntimeConnection", + "ClientInfo", "CloudSessionOptions", "CloudSessionRepository", "CommandContext", @@ -249,6 +274,7 @@ "ExitPlanModeResult", "ExtensionInfo", "CopilotWebSocketForwarder", + "DisableBypassPermissionsModes", "GetAuthStatusResponse", "BearerTokenProvider", "GetStatusResponse", @@ -256,6 +282,14 @@ "GitHubTelemetryClientInfo", "GitHubTelemetryEvent", "GitHubTelemetryNotification", + "GitHubTokenAcquireReason", + "GitHubTokenAcquireResult", + "GitHubTokenAcquireResultKind", + "GitHubTokenProvider", + "GitHubTokenProviderArgs", + "GitHubTokenProviderResult", + "GitHubTokenResult", + "GitHubTokenCancelledResult", "InfiniteSessionConfig", "InProcessRuntimeConnection", "InputOptions", @@ -272,6 +306,8 @@ "McpAuthStaticClientConfig", "McpAuthToken", "McpAuthWwwAuthenticateParams", + "ManagedSettings", + "ManagedSettingsPermissions", "ModelBilling", "ModelBillingTokenPrices", "ModelBillingTokenPricesLongContext", @@ -291,6 +327,11 @@ "PermissionNoResult", "PermissionRequest", "PermissionRequestResult", + "PermissionDecisionContext", + "PermissionDecisionOutcome", + "PermissionDecisionSource", + "PermissionDecisionSurface", + "PermissionResponseCapability", "PingResponse", "PostToolUseHandler", "PostToolUseFailureHandler", @@ -369,6 +410,7 @@ "UserPromptTransformedHookInput", "UserPromptTransformedHookOutput", "convert_mcp_call_tool_result", + "create_attributed_permission_result", "create_session_fs_adapter", "define_tool", ] diff --git a/python/copilot/_cli_download.py b/python/copilot/_cli_download.py index b831e072ad..4477fcfff3 100644 --- a/python/copilot/_cli_download.py +++ b/python/copilot/_cli_download.py @@ -27,7 +27,7 @@ import tempfile import time import zipfile -from pathlib import Path +from pathlib import Path, PurePosixPath from urllib.error import HTTPError, URLError from urllib.request import urlopen @@ -373,6 +373,164 @@ def _extract_runtime_node(data: bytes, npm_platform: str) -> bytes: raise RuntimeError(f"'{target}' not found in runtime package for {npm_platform}.") +def _extract_runtime_wrapper(data: bytes, npm_platform: str) -> bytes: + """Extract the SDK out-of-process wrapper from an npm platform tarball.""" + wrapper_name = "copilot-runtime.exe" if sys.platform == "win32" else "copilot-runtime" + target = f"package/prebuilds/{npm_platform}/{wrapper_name}" + with tarfile.open(fileobj=io.BytesIO(data), mode="r:gz") as tf: + for name in tf.getnames(): + if name == target or name.endswith(f"/prebuilds/{npm_platform}/{wrapper_name}"): + member = tf.getmember(name) + extracted = tf.extractfile(member) + if extracted is not None: + return extracted.read() + raise RuntimeError(f"'{target}' not found in runtime package for {npm_platform}.") + + +_HOSTLESS_EXCLUDED_TOP_LEVEL = { + "app.js", + "assets", + "changelog.json", + "copilot", + "copilot.exe", + "copilot-sdk", + "foundry-local-sdk", + "index.js", + "LICENSE.md", + "napi-oop-runtime", + "npm-loader.js", + "package.json", + "preloads", + "pvrecorder", + "queries", + "README.md", + "sdk", + "sea-loader.js", + "webview", +} + + +def _hostless_runtime_path(member_name: str, npm_platform: str) -> Path | None: + parts = PurePosixPath(member_name).parts + if not parts or parts[0] != "package" or len(parts) < 2: + return None + relative = parts[1:] + top_level = relative[0] + file_name = relative[-1] + if ( + top_level in _HOSTLESS_EXCLUDED_TOP_LEVEL + or (top_level.startswith("tree-sitter") and top_level.endswith(".wasm")) + or (top_level.startswith("voice-") and top_level.endswith(".js")) + or file_name == "cli-native.node" + or "mediaremote-adapter" in relative + or file_name.startswith("copilot-runtime-bin") + ): + return None + if top_level == "prebuilds": + if len(relative) < 3 or relative[1] != npm_platform: + return None + relative = relative[2:] + destination = Path(*relative) + if destination.is_absolute() or ".." in destination.parts: + raise RuntimeError(f"Unsafe runtime package path: {member_name}") + return destination + + +def _extract_runtime_bundle(data: bytes, npm_platform: str, destination: Path) -> None: + """Extract the hostless runtime tree, retaining unknown package assets by default.""" + with tarfile.open(fileobj=io.BytesIO(data), mode="r:gz") as archive: + for member in archive: + relative = _hostless_runtime_path(member.name, npm_platform) + if relative is None or member.isdir(): + continue + if not member.isfile(): + raise RuntimeError(f"Unsupported runtime package entry: {member.name}") + extracted = archive.extractfile(member) + if extracted is None: + raise RuntimeError(f"Failed to read runtime package entry: {member.name}") + target = destination / relative + target.parent.mkdir(parents=True, exist_ok=True) + target.write_bytes(extracted.read()) + if sys.platform != "win32": + target.chmod(member.mode & 0o777) + + +def ensure_runtime_wrapper(version: str | None = None, force: bool = False) -> str: + """Provision the runtime pair and its retained npm package assets.""" + ver = version or CLI_VERSION + if not ver: + raise RuntimeError("No runtime version is pinned.") + npm_platform = get_npm_platform() + wrapper_name = "copilot-runtime.exe" if sys.platform == "win32" else "copilot-runtime" + pair_dir = get_cache_dir(ver) / "prebuilds" / npm_platform + wrapper_path = pair_dir / wrapper_name + runtime_path = pair_dir / "runtime.node" + assets_marker = pair_dir / ".hostless-runtime-assets-v2" + + wrapper_exists = wrapper_path.is_file() and wrapper_path.stat().st_size > 0 + runtime_exists = runtime_path.is_file() and runtime_path.stat().st_size > 0 + if wrapper_exists and runtime_exists and assets_marker.is_file() and not force: + return str(wrapper_path) + if not force and wrapper_exists != runtime_exists: + raise RuntimeError( + f"Incomplete Copilot runtime bundle in {pair_dir}: " + f"{wrapper_name} and runtime.node are required." + ) + if _should_skip_download(): + raise RuntimeError( + f"Copilot runtime bundle is not cached in {pair_dir} " + "and automatic downloads are disabled." + ) + + data = _fetch_url_bytes(get_runtime_lib_url(ver, npm_platform), timeout=600) + integrity = _fetch_runtime_integrity(npm_platform, ver) + if not integrity: + raise RuntimeError( + "No Subresource Integrity value available for the Copilot runtime " + f"package ({npm_platform}@{ver}); refusing to stage unverified native code." + ) + _verify_integrity(data, integrity) + import shutil + + pair_dir.parent.mkdir(parents=True, exist_ok=True) + staging_dir = Path(tempfile.mkdtemp(dir=pair_dir.parent, prefix=".runtime-bundle-")) + try: + _extract_runtime_bundle(data, npm_platform, staging_dir) + staged_wrapper = staging_dir / wrapper_name + staged_runtime = staging_dir / "runtime.node" + if ( + not staged_wrapper.is_file() + or staged_wrapper.stat().st_size == 0 + or not staged_runtime.is_file() + or staged_runtime.stat().st_size == 0 + ): + raise RuntimeError("Copilot runtime wrapper and runtime.node must both be non-empty.") + if sys.platform != "win32": + staged_wrapper.chmod( + staged_wrapper.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH + ) + (staging_dir / assets_marker.name).write_text("1\n", encoding="ascii") + try: + if pair_dir.exists() and (force or not assets_marker.is_file()): + shutil.rmtree(pair_dir, ignore_errors=True) + staging_dir.replace(pair_dir) + except OSError: + if ( + wrapper_path.is_file() + and wrapper_path.stat().st_size > 0 + and runtime_path.is_file() + and runtime_path.stat().st_size > 0 + and assets_marker.is_file() + ): + return str(wrapper_path) + raise + finally: + if staging_dir.exists(): + shutil.rmtree(staging_dir, ignore_errors=True) + + return str(wrapper_path) + + def ensure_runtime_library(cli_path: str, version: str | None = None) -> str | None: """Ensure the native in-process (FFI) runtime library sits next to ``cli_path``. @@ -536,7 +694,10 @@ def main() -> None: print(f"Downloading Copilot runtime v{ver}...") try: - path = download_cli(ver, force=args.force) + if args.in_process: + path = download_cli(ver, force=args.force) + else: + path = ensure_runtime_wrapper(ver, force=args.force) print(f"Runtime cached at: {path}") if args.in_process: print("Downloading in-process (FFI) runtime library...") diff --git a/python/copilot/_ffi_runtime_host.py b/python/copilot/_ffi_runtime_host.py index e04d1655e6..5665f8fba7 100644 --- a/python/copilot/_ffi_runtime_host.py +++ b/python/copilot/_ffi_runtime_host.py @@ -3,9 +3,9 @@ Instead of spawning the Copilot CLI as a child process and talking JSON-RPC over stdio/TCP, the in-process transport loads the runtime's native shared library (``runtime.node`` — a Rust ``cdylib``) into this process and drives JSON-RPC over -its C ABI (FFI). The native ``host_start`` export spawns the residual worker -itself, so the SDK never launches the worker directly; it only pumps opaque LSP -``Content-Length:``-framed JSON-RPC bytes across the boundary: +its C ABI (FFI). The native ``host_start`` export constructs the Rust server +synchronously; the SDK only pumps opaque LSP ``Content-Length:``-framed JSON-RPC +bytes across the boundary: - client → server frames go to ``copilot_runtime_connection_write`` - server → client frames arrive on a native callback that feeds a thread-safe @@ -114,23 +114,28 @@ def _natural_library_name() -> str: return "libcopilot_runtime.so" -def resolve_library_path(cli_entrypoint: str) -> str | None: - """Resolve the native runtime library next to the given CLI entrypoint. +def resolve_library_path(runtime_entrypoint: str) -> str | None: + """Resolve the native runtime library next to the given runtime entrypoint. Checks, in order: 1. The natural platform library name next to the CLI (bundled/flat layout, what the Python download-at-first-use path writes). - 2. ``prebuilds//runtime.node`` next to the CLI (dev/package layout). + 2. ``runtime.node`` next to the CLI (prepared npm runtime layout). + 3. ``prebuilds//runtime.node`` next to the CLI (package-root layout). Returns the absolute path, or ``None`` when neither exists. """ - directory = Path(cli_entrypoint).resolve().parent + directory = Path(runtime_entrypoint).resolve().parent flat = directory / _natural_library_name() if flat.is_file(): return str(flat) + adjacent_prebuilt = directory / "runtime.node" + if adjacent_prebuilt.is_file(): + return str(adjacent_prebuilt) + folder = get_prebuilds_folder() if folder is not None: prebuilt = directory / "prebuilds" / folder / "runtime.node" @@ -327,15 +332,15 @@ def wait(self, timeout: float | None = None) -> int: # noqa: ARG002 class FfiRuntimeHost: """Hosts the Copilot runtime in-process via its native C ABI. - Construct with :meth:`create`, then :meth:`start` to spawn the worker and open - the FFI connection. Expose :attr:`process` to :class:`JsonRpcClient`, and call - :meth:`dispose` to tear everything down. + Construct with :meth:`create`, then :meth:`start` to start the native engine + and open the FFI connection. Expose :attr:`process` to + :class:`JsonRpcClient`, and call :meth:`dispose` to tear everything down. """ def __init__( self, library_path: str, - cli_entrypoint: str, + cli_entrypoint: str | None, environment: dict[str, str] | None = None, args: Sequence[str] = (), ) -> None: @@ -367,31 +372,30 @@ def process(self) -> _FfiProcessAdapter: @staticmethod def create( - cli_entrypoint: str, + library_path: str, + cli_entrypoint: str | None = None, environment: dict[str, str] | None = None, args: Sequence[str] = (), ) -> FfiRuntimeHost: - """Resolve the cdylib next to the CLI entrypoint and prepare the host. + """Load the runtime cdylib and prepare the host. Raises: RuntimeError: If the native runtime library cannot be found. """ - full_entrypoint = str(Path(cli_entrypoint).resolve()) - library_path = resolve_library_path(full_entrypoint) - if library_path is None: + full_library_path = str(Path(library_path).resolve()) + if not Path(full_library_path).is_file(): raise RuntimeError( - "In-process FFI runtime library not found next to " - f"'{full_entrypoint}'. Download it with " - "`python -m copilot download-runtime --in-process`, or set " - "COPILOT_CLI_PATH to a runtime package that ships it." + f"In-process FFI runtime library not found at '{full_library_path}'." ) - return FfiRuntimeHost(library_path, full_entrypoint, environment, args) + full_entrypoint = ( + str(Path(cli_entrypoint).resolve()) if cli_entrypoint is not None else None + ) + return FfiRuntimeHost(full_library_path, full_entrypoint, environment, args) def _build_argv(self) -> bytes: - # A `.js` entrypoint (dev) is launched via node; the packaged single-file - # CLI embeds its own Node and is invoked directly. `--no-auto-update` - # pins the worker to the runtime package matching the loaded cdylib. - if self._cli_entrypoint.lower().endswith(".js"): + if self._cli_entrypoint is None: + argv: list[str] = [] + elif self._cli_entrypoint.lower().endswith(".js"): argv = ["node", self._cli_entrypoint, "--embedded-host", "--no-auto-update"] else: argv = [self._cli_entrypoint, "--embedded-host", "--no-auto-update"] @@ -407,11 +411,9 @@ def _build_env(self) -> bytes | None: return json.dumps(obj).encode("utf-8") def start_blocking(self) -> None: - """Spawn the worker and open the FFI connection (blocks up to ~30s). + """Start the native engine and open the FFI connection. - Must be run off the event loop (e.g. via :func:`asyncio.to_thread`); - ``host_start`` blocks until the worker connects back and signals - readiness. + Must be run off the event loop (e.g. via :func:`asyncio.to_thread`). """ argv = self._build_argv() env = self._build_env() @@ -419,8 +421,7 @@ def start_blocking(self) -> None: self._server_id = self._lib.host_start(argv, len(argv), env, len(env) if env else 0) if not self._server_id: raise RuntimeError( - f"copilot_runtime_host_start failed (library '{self._library_path}', " - f"entrypoint '{self._cli_entrypoint}')." + f"copilot_runtime_host_start failed (library '{self._library_path}')." ) self._outbound_callback = _OutboundCallback(self._on_outbound) diff --git a/python/copilot/_jsonrpc.py b/python/copilot/_jsonrpc.py index ed70e4e8d0..11baf0bd10 100644 --- a/python/copilot/_jsonrpc.py +++ b/python/copilot/_jsonrpc.py @@ -296,18 +296,7 @@ def _read_loop(self): def _fail_pending_requests(self): """Fail all pending requests when process exits""" - # Build error message with stderr output - stderr_output = self.get_stderr_output() - return_code = None - if hasattr(self.process, "poll"): - return_code = self.process.poll() - - if stderr_output: - error_msg = f"CLI process exited with code {return_code}\nstderr: {stderr_output}" - elif return_code is not None: - error_msg = f"CLI process exited with code {return_code}" - else: - error_msg = "CLI process exited unexpectedly" + error_msg = self._get_process_exit_error() # Fail all pending requests with self._pending_lock: @@ -317,6 +306,22 @@ def _fail_pending_requests(self): loop = future.get_loop() loop.call_soon_threadsafe(future.set_exception, exc) + def _get_process_exit_error(self) -> str: + """Build an error message after the process and stderr readers finish.""" + if self._stderr_thread and self._stderr_thread is not threading.current_thread(): + self._stderr_thread.join(timeout=1.0) + + stderr_output = self.get_stderr_output() + return_code = None + if hasattr(self.process, "poll"): + return_code = self.process.poll() + + if stderr_output: + return f"CLI process exited with code {return_code}\nstderr: {stderr_output}" + if return_code is not None: + return f"CLI process exited with code {return_code}" + return "CLI process exited unexpectedly" + def _read_exact(self, num_bytes: int) -> bytes: """ Read exactly num_bytes, handling partial/short reads from pipes. diff --git a/python/copilot/_mode.py b/python/copilot/_mode.py index 1a9ed6e1f5..5e7de02d02 100644 --- a/python/copilot/_mode.py +++ b/python/copilot/_mode.py @@ -298,11 +298,14 @@ def _post_create_options_patch( custom_agents_local_only: bool | None, coauthor_enabled: bool | None, manage_schedule_enabled: bool | None, + included_builtin_skills: list[str] | None = None, ) -> dict[str, Any] | None: """Build the patch sent via ``session.options.update`` after create/resume. In empty mode the four overridable flags default to safe values (caller-supplied values win); ``installedPlugins=[]`` is unconditional. + ``includedBuiltinSkills`` defaults to an empty list, but callers can + explicitly allow selected runtime-bundled skills. Returns ``None`` if no patch should be sent. """ if mode == "empty": @@ -318,6 +321,9 @@ def _post_create_options_patch( manage_schedule_enabled if manage_schedule_enabled is not None else False ), "installedPlugins": [], + "includedBuiltinSkills": ( + included_builtin_skills if included_builtin_skills is not None else [] + ), } return patch patch = {} @@ -329,6 +335,8 @@ def _post_create_options_patch( patch["coauthorEnabled"] = coauthor_enabled if manage_schedule_enabled is not None: patch["manageScheduleEnabled"] = manage_schedule_enabled + if included_builtin_skills is not None: + patch["includedBuiltinSkills"] = included_builtin_skills return patch or None diff --git a/python/copilot/client.py b/python/copilot/client.py index 737619ef35..4a391e6a29 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -16,6 +16,7 @@ import asyncio import inspect +import ipaddress import logging import os import re @@ -28,8 +29,9 @@ from collections.abc import Awaitable, Callable, Mapping, Sequence from dataclasses import dataclass, field from datetime import UTC, datetime +from pathlib import Path from types import TracebackType -from typing import Any, ClassVar, Literal, TypedDict, cast, overload +from typing import Any, ClassVar, Literal, NotRequired, TypedDict, cast, overload from ._diagnostics import log_timing from ._ffi_runtime_host import FfiRuntimeHost @@ -69,6 +71,9 @@ ClientGlobalApiHandlers, ClientSessionApiHandlers, GitHubTelemetryNotification, + GitHubTokenAcquireReason, + GitHubTokenAcquireRequest, + GitHubTokenAcquireResult, ModelBillingTokenPrices, ModelBillingTokenPricesLongContext, # noqa: F401 OpenCanvasInstance, @@ -123,6 +128,50 @@ logger = logging.getLogger(__name__) + +class GitHubTokenProviderArgs(TypedDict): + """Arguments passed to a session-scoped :data:`GitHubTokenProvider`. + + The opaque callback registration identifier is intentionally not exposed. + """ + + host: str + session_id: str | None + reason: GitHubTokenAcquireReason + + +class GitHubTokenResult(TypedDict): + """A GitHub token returned by a session-scoped provider.""" + + kind: Literal["token"] + accessToken: str + expiresIn: int + tokenType: NotRequired[str] + + +class GitHubTokenCancelledResult(TypedDict): + """An explicit cancellation returned by a session-scoped provider.""" + + kind: Literal["cancelled"] + + +GitHubTokenProviderResult = GitHubTokenResult | GitHubTokenCancelledResult +"""Result returned by a session-scoped GitHub token provider.""" + + +GitHubTokenProvider = Callable[ + [GitHubTokenProviderArgs], + GitHubTokenProviderResult | Awaitable[GitHubTokenProviderResult], +] +"""Acquire a GitHub credential for one session. + +Token results require ``expiresIn`` to be the positive number of seconds of +remaining lifetime when the callback completes. Production GitHub tokens +typically last eight hours. Initial cancellation, callback errors, and invalid +token responses reject session creation or resume instead of falling back to +ambient authentication. +""" + # ============================================================================ # Connection Types # ============================================================================ @@ -130,6 +179,8 @@ _ConnectionState = Literal["disconnected", "connecting", "connected", "error"] LogLevel = Literal["none", "error", "warning", "info", "debug", "all"] +AskUserVariant = Literal["legacy", "elicitation"] +"""Model-facing shape of the runtime's built-in ``ask_user`` tool.""" @dataclass @@ -213,9 +264,23 @@ def _exp_assignment_response_to_dict( return wire +AutoTier = Literal["efficiency", "balance", "intelligence"] +"""Routing preference used when the session model is ``auto``.""" + + class CapiSessionOptions(TypedDict, total=False): """Provider-scoped Copilot API (CAPI) session options.""" + auto_tier: AutoTier + """Routing preference used when the session model is ``auto``. + + Requires a runtime with Auto tier support and V2 Auto routing. When omitted + on create, the runtime uses its default routing behavior. The runtime persists + this preference across cold resume; an explicit tier on cold resume overrides + the persisted value. For an already-resident session, omission preserves the + current tier and a different tier is rejected. + """ + enable_web_socket_responses: bool """Whether to use WebSocket transport for the CAPI Responses API. @@ -242,11 +307,81 @@ def _cloud_session_options_to_dict(options: CloudSessionOptions) -> dict[str, An def _capi_session_options_to_wire(options: CapiSessionOptions) -> dict[str, Any]: wire: dict[str, Any] = {} + if "auto_tier" in options: + wire["autoTier"] = options["auto_tier"] if "enable_web_socket_responses" in options: wire["enableWebSocketResponses"] = options["enable_web_socket_responses"] return wire +class DisableBypassPermissionsModes: + """Well-known managed bypass-permissions policies.""" + + DISABLE: ClassVar[str] = "disable" + """Turn off bypass-permissions mode entirely.""" + + ALLOW_AUTO_ONLY: ClassVar[str] = "allow-auto-only" + """Permit automatic bypass but block full allow-all.""" + + +@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: str | None = None + """Restricts bypass-permissions mode for the session. See + :class:`DisableBypassPermissionsModes` for well-known values. Unknown values + are forwarded so newer runtime policies fail closed. 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" @@ -373,6 +508,46 @@ class TelemetryConfig(TypedDict, total=False): """Whether to capture message content. Sets OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT.""" # noqa: E501 +class ClientInfo(TypedDict, total=False): + """Identity of the integrating application, declared on ``server.connect``. + + Declaring it lets the telemetry the runtime emits on this connection be + attributed to a single, consistent surface (the application and its Copilot + integration) instead of the runtime's own build. All fields are optional; + omit any of them (or the whole object) to keep the default attribution. + """ + + application_name: str + """Name of the application using the SDK, e.g. ``"acme-developer-portal"``.""" + application_version: str + """Version of the application using the SDK, e.g. ``"2.4.0"``.""" + integration_name: str + """Optional name of an application integration, such as an extension or plugin.""" + integration_version: str + """Optional version of the integration named by ``integration_name``.""" + + +def _client_info_to_wire(client_info: ClientInfo | None) -> dict[str, str] | None: + """Map a snake_case :class:`ClientInfo` onto the camelCase connect wire shape. + + Empty fields are dropped. Returns ``None`` when no field carries a non-empty + value so the caller omits the ``clientInfo`` field entirely and keeps the + runtime's default attribution. + """ + if not client_info: + return None + wire: dict[str, str] = {} + if client_info.get("application_name"): + wire["editorName"] = client_info["application_name"] + if client_info.get("application_version"): + wire["editorVersion"] = client_info["application_version"] + if client_info.get("integration_name"): + wire["extensionName"] = client_info["integration_name"] + if client_info.get("integration_version"): + wire["extensionVersion"] = client_info["integration_version"] + return wire or None + + @dataclass class RuntimeConnection: """Discriminated config describing how to reach the Copilot runtime. @@ -565,6 +740,43 @@ async def event(self, params: GitHubTelemetryNotification) -> None: logger.warning("Error handling gitHubTelemetry.event notification", exc_info=True) +@dataclass +class _GitHubTokenProviderRegistration: + provider: GitHubTokenProvider + session_id: str | None = None + committed: bool = False + + +class _GitHubTokenProviderAdapter: + """Routes global GitHub token requests to opaque session registrations.""" + + def __init__(self, client: CopilotClient) -> None: + self._client = client + + async def get_token(self, params: GitHubTokenAcquireRequest) -> GitHubTokenAcquireResult: + with self._client._github_token_providers_lock: + registration = self._client._github_token_providers.get(params.registration_id) + if registration is None: + raise JsonRpcError( + -32603, + "No GitHub token provider registered for registration ID " + f"{params.registration_id!r}", + ) + + result = registration.provider( + GitHubTokenProviderArgs( + host=params.host, + session_id=params.session_id or registration.session_id, + reason=params.reason, + ) + ) + if inspect.isawaitable(result): + result = await result + # The generated global-handler wrapper forwards callback results directly, + # so the public tagged dictionary is already in the expected wire shape. + return cast(GitHubTokenAcquireResult, result) + + class _HooksAdapter: """Adapts session-scoped hook dispatch to the generated ``HooksHandler`` protocol. @@ -598,12 +810,14 @@ 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 request_handler: CopilotRequestHandler | None = None session_idle_timeout_seconds: int | None = None enable_remote_sessions: bool = False + client_info: ClientInfo | None = None on_list_models: Callable[[], list[ModelInfo] | Awaitable[list[ModelInfo]]] | None = None on_github_telemetry: Callable[[GitHubTelemetryNotification], None | Awaitable[None]] | None = ( None @@ -1201,25 +1415,6 @@ def _session_lifecycle_event_from_dict(data: dict) -> SessionLifecycleEvent: _CLI_PROCESS_EXIT_TIMEOUT_SECONDS = 5 -def _get_or_download_cli(*, include_runtime_lib: bool = False) -> str | None: - """Get the cached CLI binary, downloading if necessary. - - Returns the path to the CLI binary, or None if unavailable (dev install - with no pinned version, or auto-download disabled). - - When ``include_runtime_lib`` is set, also ensures the native in-process FFI - runtime is available (downloading it on first use). - """ - from ._cli_download import get_or_download_cli - - cli_path = get_or_download_cli() - if cli_path and include_runtime_lib: - from ._cli_download import ensure_runtime_library - - ensure_runtime_library(cli_path) - return cli_path - - def _extract_transform_callbacks( system_message: SystemMessageConfig | dict[str, Any] | None, ) -> tuple[dict[str, Any] | None, dict[str, SectionTransformFn] | None]: @@ -1370,12 +1565,14 @@ 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, request_handler: CopilotRequestHandler | None = None, session_idle_timeout_seconds: int | None = None, enable_remote_sessions: bool = False, + client_info: ClientInfo | None = None, on_list_models: Callable[[], list[ModelInfo] | Awaitable[list[ModelInfo]]] | None = None, on_github_telemetry: Callable[[GitHubTelemetryNotification], None | Awaitable[None]] | None = None, @@ -1405,6 +1602,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. @@ -1422,6 +1622,11 @@ def __init__( Control integration). When ``True``, sessions in a GitHub repository working directory are accessible from GitHub web and mobile. + client_info: Identity of the integrating application, forwarded to the + runtime on the ``server.connect`` handshake. Declaring it lets + the telemetry the runtime emits on this connection be attributed + to a consistent surface instead of the runtime's own build. All + fields are optional; omit it to keep the default attribution. on_list_models: Custom handler for :meth:`list_models`. When provided, the handler is called instead of querying the runtime server. @@ -1452,12 +1657,14 @@ 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, request_handler=request_handler, session_idle_timeout_seconds=session_idle_timeout_seconds, enable_remote_sessions=enable_remote_sessions, + client_info=client_info, on_list_models=on_list_models, on_github_telemetry=on_github_telemetry, mode=mode, @@ -1468,6 +1675,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, @@ -1486,6 +1698,7 @@ def __init__( self._cli_path_source: str | None = None self._ffi_host: FfiRuntimeHost | None = None self._inprocess_runtime_path: str | None = None + self._inprocess_cli_entrypoint: str | None = None if isinstance(connection, UriRuntimeConnection): if connection.connection_token is not None and len(connection.connection_token) == 0: @@ -1497,9 +1710,7 @@ def __init__( # In-process (FFI): no child process and no per-connection token. self._runtime_port = None self._effective_connection_token = None - self._inprocess_runtime_path = self._resolve_runtime_entrypoint( - None, include_runtime_lib=True - ) + self._inprocess_runtime_path = self._resolve_inprocess_runtime() if options.use_logged_in_user is None: options.use_logged_in_user = not bool(options.github_token) else: @@ -1520,7 +1731,7 @@ def __init__( else: self._effective_connection_token = None - # Resolve CLI path: explicit > COPILOT_CLI_PATH env var > downloaded binary. + # Resolve runtime path: explicit CLI > COPILOT_CLI_PATH > downloaded runtime. # Select the environment by identity, not truthiness, so an intentionally # empty per-connection or client env stays authoritative (the spawned child # receives that empty mapping) instead of falling back to os.environ and @@ -1543,6 +1754,9 @@ def __init__( self._state: _ConnectionState = "disconnected" self._sessions: dict[str, CopilotSession] = {} self._sessions_lock = threading.Lock() + self._github_token_providers: dict[str, _GitHubTokenProviderRegistration] = {} + self._github_token_providers_lock = threading.Lock() + self._github_token_provider_adapter = _GitHubTokenProviderAdapter(self) self._models_cache: list[ModelInfo] | None = None self._models_cache_lock = asyncio.Lock() self._lifecycle_handlers: list[SessionLifecycleHandler] = [] @@ -1562,52 +1776,47 @@ def _resolve_runtime_entrypoint( path: str | None, *, env: Mapping[str, str] | None = None, - include_runtime_lib: bool = False, ) -> str: """Resolve the runtime executable path (explicit > env > downloaded). Sets ``self._cli_path_source`` for diagnostics. When - ``include_runtime_lib`` is set (in-process transport), also ensures the - native runtime library is downloaded alongside the CLI. - Raises: RuntimeError: If no runtime path can be resolved. """ if path is not None: self._cli_path_source = "explicit" - return self._ensure_runtime_lib(path) if include_runtime_lib else path + return path lookup = env if env is not None else os.environ env_cli_path = lookup.get("COPILOT_CLI_PATH") if env_cli_path: self._cli_path_source = "environment" - return self._ensure_runtime_lib(env_cli_path) if include_runtime_lib else env_cli_path - - downloaded_path = _get_or_download_cli(include_runtime_lib=include_runtime_lib) - if downloaded_path: - self._cli_path_source = "downloaded" - return downloaded_path - - raise RuntimeError( - "Copilot CLI not found. Install a published wheel (which " - "auto-downloads the CLI on first use), set COPILOT_CLI_PATH, " - "or pass an explicit path via " - "RuntimeConnection.for_stdio(path=...) / " - "RuntimeConnection.for_tcp(path=...)." - ) + return env_cli_path - @staticmethod - def _ensure_runtime_lib(cli_path: str) -> str: - """Ensure the in-process runtime library sits next to a user-supplied CLI. + from ._cli_download import ensure_runtime_wrapper - For explicit/``COPILOT_CLI_PATH`` entrypoints, the native library may - already be bundled (dev ``prebuilds`` layout); otherwise it is fetched on - first use. Returns ``cli_path`` unchanged. - """ - from ._cli_download import ensure_runtime_library + self._cli_path_source = "downloaded" + return ensure_runtime_wrapper() - ensure_runtime_library(cli_path) - return cli_path + def _resolve_inprocess_runtime(self) -> str: + explicit_cli = os.environ.get("COPILOT_CLI_PATH") + if explicit_cli: + from ._cli_download import ensure_runtime_library + + runtime_path = ensure_runtime_library(explicit_cli) + if runtime_path is None: + raise RuntimeError( + f"In-process runtime library not found next to '{explicit_cli}'." + ) + self._cli_path_source = "environment" + self._inprocess_cli_entrypoint = explicit_cli + return runtime_path + + from ._cli_download import ensure_runtime_wrapper + + wrapper_path = Path(ensure_runtime_wrapper()) + self._cli_path_source = "downloaded" + return str(wrapper_path.with_name("runtime.node")) @property def rpc(self) -> ServerRpc: @@ -1630,8 +1839,8 @@ def _parse_cli_url(self, url: str) -> tuple[str, int]: """ Parse CLI URL into host and port. - Supports formats: "host:port", "http://host:port", "https://host:port", - or just "port". + Supports formats: "host:port", "[ipv6]:port", "http://host:port", + "https://host:port", or just "port". Args: url: The CLI URL to parse. @@ -1642,9 +1851,6 @@ def _parse_cli_url(self, url: str) -> tuple[str, int]: Raises: ValueError: If the URL format is invalid or the port is out of range. """ - import re - - # Remove protocol if present clean_url = re.sub(r"^https?://", "", url) # Check if it's just a port number @@ -1654,14 +1860,24 @@ def _parse_cli_url(self, url: str) -> tuple[str, int]: raise ValueError(f"Invalid port in cli_url: {url}") return ("localhost", port) - # Parse host:port format - parts = clean_url.split(":") - if len(parts) != 2: - raise ValueError(f"Invalid cli_url format: {url}") + ipv6_match = re.match(r"^\[([^\]]+)\]:(.*)$", clean_url) + if ipv6_match: + host = ipv6_match.group(1) + port_text = ipv6_match.group(2) + try: + ipaddress.IPv6Address(host) + except ValueError as e: + raise ValueError(f"Invalid cli_url format: {url}") from e + else: + # Parse host:port format + parts = clean_url.split(":") + if len(parts) != 2: + raise ValueError(f"Invalid cli_url format: {url}") + host = parts[0] if parts[0] else "localhost" + port_text = parts[1] - host = parts[0] if parts[0] else "localhost" try: - port = int(parts[1]) + port = int(port_text) except ValueError as e: raise ValueError(f"Invalid port in cli_url: {url}") from e @@ -1750,6 +1966,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() @@ -1793,13 +2020,14 @@ async def start(self) -> None: # Check if process exited and capture any remaining stderr process = self._cli_process if self._cli_process is not None else self._process if process and hasattr(process, "poll"): + if isinstance(e, BrokenPipeError) and process.poll() is None: + try: + await asyncio.to_thread(process.wait, timeout=1.0) + except subprocess.TimeoutExpired: + pass return_code = process.poll() if return_code is not None and self._client: - stderr_output = self._client.get_stderr_output() - if stderr_output: - raise RuntimeError( - f"CLI process exited with code {return_code}\nstderr: {stderr_output}" - ) from e + raise RuntimeError(self._client._get_process_exit_error()) from e raise async def stop(self) -> None: @@ -1846,6 +2074,8 @@ async def stop(self) -> None: errors.append( StopError(message=f"Failed to disconnect session {session.session_id}: {e}") ) + with self._github_token_providers_lock: + self._github_token_providers.clear() if ( self._rpc is not None @@ -1962,6 +2192,8 @@ async def force_stop(self) -> None: # Clear sessions immediately without trying to destroy them with self._sessions_lock: self._sessions.clear() + with self._github_token_providers_lock: + self._github_token_providers.clear() # Close the transport first to signal the server immediately. # For external servers (TCP), this closes the socket. @@ -2028,6 +2260,7 @@ async def create_session( available_tools: list[str] | ToolSet | None = None, excluded_tools: list[str] | ToolSet | None = None, on_user_input_request: UserInputHandler | None = None, + ask_user_variant: AskUserVariant | None = None, hooks: SessionHooks | None = None, working_directory: str | None = None, additional_directories: list[str] | None = None, @@ -2037,6 +2270,7 @@ async def create_session( 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, @@ -2061,10 +2295,12 @@ async def create_session( enable_host_git_operations: bool | None = None, enable_session_store: bool | None = None, enable_skills: bool | None = None, + included_builtin_skills: list[str] | None = None, skill_directories: list[str] | None = None, 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, @@ -2077,6 +2313,7 @@ async def create_session( on_auto_mode_switch_request: AutoModeSwitchHandler | None = None, create_session_fs_handler: CreateSessionFsHandler | None = None, github_token: str | None = None, + github_token_provider: GitHubTokenProvider | None = None, remote_session: RemoteSessionMode | None = None, cloud: CloudSessionOptions | None = None, canvases: list[CanvasDeclaration] | None = None, @@ -2086,9 +2323,11 @@ async def create_session( extension_info: ExtensionInfo | None = None, canvas_provider: CanvasProviderIdentity | None = None, canvas_handler: CanvasHandler | None = None, + feature_flags: dict[str, bool] | 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. @@ -2125,10 +2364,16 @@ async def create_session( including custom tools registered via ``tools=``. Ignored if ``available_tools`` is set. on_user_input_request: Handler for user input requests. + ask_user_variant: Model-facing shape of the ``ask_user`` tool. + Accepted values are ``"legacy"`` and ``"elicitation"``. The + default is ``"legacy"``. To use ``"elicitation"``, also provide + ``on_elicitation_request`` so the host can answer structured forms. hooks: Lifecycle hooks for the session. working_directory: Working directory for the session. provider: Provider configuration for Azure or custom endpoints. - capi: CAPI provider-scoped options. WebSocket transport is the + capi: CAPI provider-scoped options. Set ``auto_tier`` to ``efficiency``, + ``balance``, or ``intelligence`` to select an Auto routing preference + on a runtime with Auto tier support. WebSocket transport is the default for the CAPI Responses API whenever the model advertises the ``ws:/responses`` endpoint. Set ``enable_web_socket_responses=False`` to force the HTTP @@ -2151,6 +2396,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 @@ -2193,6 +2440,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 @@ -2218,6 +2469,9 @@ async def create_session( 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. + feature_flags: Feature-flag values resolved by the host for this + session. Re-supply them when resuming after a runtime restart. + Sent on the wire as ``featureFlags``. exp_assignments: ExP assignment ("flight") data injected by a trusted integrator, in the same JSON shape the Copilot CLI fetches from the experimentation service @@ -2236,6 +2490,20 @@ 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``. + github_token_provider: Callback that acquires a short-lived GitHub + credential for this session. Mutually exclusive with + ``github_token``. It receives the effective host, session ID + when assigned, and acquisition reason; the registration ID is + kept internal. Returns: A :class:`CopilotSession` instance for the new session. @@ -2257,6 +2525,10 @@ async def create_session( """ if on_permission_request is not None and not callable(on_permission_request): raise ValueError("on_permission_request must be callable when provided.") + if github_token is not None and github_token_provider is not None: + raise ValueError("github_token and github_token_provider are mutually exclusive") + if ask_user_variant not in (None, "legacy", "elicitation"): + raise ValueError('ask_user_variant must be "legacy" or "elicitation"') if not self._client: await self.start() @@ -2277,6 +2549,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 @@ -2342,6 +2616,8 @@ async def create_session( # Enable user input request callback if handler provided if on_user_input_request: payload["requestUserInput"] = True + if ask_user_variant is not None: + payload["askUserVariant"] = ask_user_variant # Enable elicitation request callback if handler provided payload["requestElicitation"] = bool(on_elicitation_request) @@ -2355,7 +2631,7 @@ async def create_session( # Serialize commands (name + description only) into payload if commands: payload["commands"] = [ - {"name": cmd.name, "description": cmd.description} for cmd in commands + {"name": cmd.name, "description": cmd.description or ""} for cmd in commands ] # Enable hooks callback if any hook handler provided @@ -2374,6 +2650,9 @@ async def create_session( if cloud is not None: payload["cloud"] = _cloud_session_options_to_dict(cloud) + if feature_flags is not None: + payload["featureFlags"] = feature_flags + # Add ExP assignment data if provided (trusted integrator) if exp_assignments is not None: payload["expAssignments"] = _exp_assignment_response_to_dict(exp_assignments) @@ -2382,6 +2661,10 @@ 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 @@ -2422,6 +2705,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: @@ -2496,6 +2781,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: @@ -2548,6 +2835,11 @@ async def create_session( ) if local_session_id is not None: payload["sessionId"] = local_session_id + github_token_provider_registration_id = self._register_github_token_provider( + github_token_provider, local_session_id + ) + if github_token_provider_registration_id is not None: + payload["gitHubTokenProviderRegistrationId"] = github_token_provider_registration_id # Propagate W3C Trace Context to CLI if OpenTelemetry is active trace_ctx = get_trace_context() @@ -2566,7 +2858,15 @@ def _initialize_session(sid: str) -> CopilotSession: sid, self._client, workspace_path=None, - managed_settings_enabled=enable_managed_settings is True, + managed_settings_enabled=enable_managed_settings is True + or managed_settings is not None, + on_disconnect=( + None + if github_token_provider_registration_id is None + else lambda: self._unregister_github_token_provider( + github_token_provider_registration_id + ) + ), ) if self._session_fs_config: if create_session_fs_handler is None: @@ -2628,8 +2928,12 @@ def _initialize_session(sid: str) -> CopilotSession: # processing (e.g. sessionFs.writeFile for workspace metadata) can be # routed to the correct handlers. if local_session_id is not None: - session = _initialize_session(local_session_id) - registered_session_id = local_session_id + try: + session = _initialize_session(local_session_id) + registered_session_id = local_session_id + except BaseException: + self._unregister_github_token_provider(github_token_provider_registration_id) + raise try: rpc_start = time.perf_counter() @@ -2669,6 +2973,9 @@ def _register_inline(raw_response: Any) -> None: f"session.create returned sessionId {response.get('sessionId')} " f"but the caller requested {local_session_id}" ) + self._assign_github_token_provider( + github_token_provider_registration_id, session.session_id + ) if on_mcp_auth_request is not None: await self._client.request( "session.eventLog.registerInterest", @@ -2681,6 +2988,7 @@ def _register_inline(raw_response: Any) -> None: if registered_session_id is not None: with self._sessions_lock: self._sessions.pop(registered_session_id, None) + self._unregister_github_token_provider(github_token_provider_registration_id) if not isinstance(exc, asyncio.CancelledError): log_timing( logger, @@ -2699,6 +3007,10 @@ def _register_inline(raw_response: Any) -> None: custom_agents_local_only, coauthor_enabled, manage_schedule_enabled, + included_builtin_skills, + ) + self._commit_github_token_provider( + session.session_id, github_token_provider_registration_id ) log_timing( @@ -2727,6 +3039,7 @@ async def resume_session( available_tools: list[str] | ToolSet | None = None, excluded_tools: list[str] | ToolSet | None = None, on_user_input_request: UserInputHandler | None = None, + ask_user_variant: AskUserVariant | None = None, hooks: SessionHooks | None = None, working_directory: str | None = None, additional_directories: list[str] | None = None, @@ -2736,6 +3049,7 @@ async def resume_session( 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, @@ -2760,10 +3074,12 @@ async def resume_session( enable_host_git_operations: bool | None = None, enable_session_store: bool | None = None, enable_skills: bool | None = None, + included_builtin_skills: list[str] | None = None, skill_directories: list[str] | None = None, 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, @@ -2776,6 +3092,7 @@ async def resume_session( on_auto_mode_switch_request: AutoModeSwitchHandler | None = None, create_session_fs_handler: CreateSessionFsHandler | None = None, github_token: str | None = None, + github_token_provider: GitHubTokenProvider | None = None, remote_session: RemoteSessionMode | None = None, continue_pending_work: bool | None = None, canvases: list[CanvasDeclaration] | None = None, @@ -2786,9 +3103,11 @@ async def resume_session( canvas_provider: CanvasProviderIdentity | None = None, canvas_handler: CanvasHandler | None = None, open_canvases: list[OpenCanvasInstance] | None = None, + feature_flags: dict[str, bool] | 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. @@ -2825,10 +3144,17 @@ async def resume_session( including custom tools registered via ``tools=``. Ignored if ``available_tools`` is set. on_user_input_request: Handler for user input requests. + ask_user_variant: Model-facing shape of the ``ask_user`` tool. + Accepted values are ``"legacy"`` and ``"elicitation"``. The + default is ``"legacy"``. To use ``"elicitation"``, also provide + ``on_elicitation_request`` so the host can answer structured forms. hooks: Lifecycle hooks for the session. working_directory: Working directory for the session. provider: Provider configuration for Azure or custom endpoints. - capi: CAPI provider-scoped options. WebSocket transport is the + capi: CAPI provider-scoped options. Omit ``auto_tier`` to preserve the + current or persisted Auto routing preference. An explicit tier + overrides it on cold resume, but cannot change it on an + already-resident session. WebSocket transport is the default for the CAPI Responses API whenever the model advertises the ``ws:/responses`` endpoint. Set ``enable_web_socket_responses=False`` to force the HTTP @@ -2851,6 +3177,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 @@ -2893,6 +3222,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. @@ -2919,6 +3252,8 @@ async def resume_session( tool calls or permission prompts that were still pending when the session was last suspended. When False (the default), the runtime treats pending work as interrupted on resume. + feature_flags: Feature-flag values resolved by the host to apply + on resume. Sent on the wire as ``featureFlags``. exp_assignments: ExP assignment ("flight") data injected by a trusted integrator, in the same JSON shape the Copilot CLI fetches from the experimentation service @@ -2937,6 +3272,15 @@ 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``. + github_token_provider: Callback that acquires a short-lived GitHub + credential for this resumed session. Mutually exclusive with + ``github_token``. The new registration replaces the prior + provider only after resume succeeds. Returns: A :class:`CopilotSession` instance for the resumed session. @@ -2960,6 +3304,10 @@ async def resume_session( """ if on_permission_request is not None and not callable(on_permission_request): raise ValueError("on_permission_request must be callable when provided.") + if github_token is not None and github_token_provider is not None: + raise ValueError("github_token and github_token_provider are mutually exclusive") + if ask_user_variant not in (None, "legacy", "elicitation"): + raise ValueError('ask_user_variant must be "legacy" or "elicitation"') if not self._client: await self.start() @@ -2980,6 +3328,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 @@ -3045,6 +3395,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: @@ -3071,6 +3423,8 @@ async def resume_session( if on_user_input_request: payload["requestUserInput"] = True + if ask_user_variant is not None: + payload["askUserVariant"] = ask_user_variant # Enable elicitation request callback if handler provided payload["requestElicitation"] = bool(on_elicitation_request) @@ -3084,7 +3438,7 @@ async def resume_session( # Serialize commands (name + description only) into payload if commands: payload["commands"] = [ - {"name": cmd.name, "description": cmd.description} for cmd in commands + {"name": cmd.name, "description": cmd.description or ""} for cmd in commands ] if hooks and any(hooks.values()): @@ -3098,6 +3452,9 @@ async def resume_session( if remote_session is not None: payload["remoteSession"] = remote_session.value + if feature_flags is not None: + payload["featureFlags"] = feature_flags + # Add ExP assignment data if provided (trusted integrator) if exp_assignments is not None: payload["expAssignments"] = _exp_assignment_response_to_dict(exp_assignments) @@ -3106,6 +3463,10 @@ 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: @@ -3165,6 +3526,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] = {} @@ -3216,7 +3579,8 @@ async def resume_session( session_id, self._client, workspace_path=None, - managed_settings_enabled=enable_managed_settings is True, + 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: @@ -3270,6 +3634,16 @@ async def resume_session( commands_count=len(commands or []), has_hooks=hooks is not None, ) + github_token_provider_registration_id = self._register_github_token_provider( + github_token_provider, session_id + ) + if github_token_provider_registration_id is not None: + payload["gitHubTokenProviderRegistrationId"] = github_token_provider_registration_id + session._set_disconnect_callback( + lambda: self._unregister_github_token_provider( + github_token_provider_registration_id + ) + ) try: rpc_start = time.perf_counter() @@ -3297,6 +3671,7 @@ async def resume_session( except BaseException as exc: with self._sessions_lock: self._sessions.pop(session_id, None) + self._unregister_github_token_provider(github_token_provider_registration_id) if not isinstance(exc, asyncio.CancelledError): log_timing( logger, @@ -3315,7 +3690,9 @@ async def resume_session( custom_agents_local_only, coauthor_enabled, manage_schedule_enabled, + included_builtin_skills, ) + self._commit_github_token_provider(session_id, github_token_provider_registration_id) log_timing( logger, @@ -3535,8 +3912,9 @@ async def delete_session(self, session_id: str) -> None: # Remove from local sessions map if present with self._sessions_lock: - if session_id in self._sessions: - del self._sessions[session_id] + session = self._sessions.pop(session_id, None) + if session is not None: + session._run_disconnect_callback() async def get_last_session_id(self) -> str | None: """ @@ -3733,6 +4111,12 @@ async def _verify_protocol_version(self) -> None: # event is forwarded). Also sent on session.create/resume for older CLIs. if self._on_github_telemetry is not None: connect_params["enableGitHubTelemetryForwarding"] = True + # Declare the integrating application's identity so the runtime attributes + # the telemetry it emits on this connection to a consistent surface + # instead of its own build. Omitted when the app didn't supply it. + client_info = _client_info_to_wire(self._options.client_info) + if client_info is not None: + connect_params["clientInfo"] = client_info connect_result = _ConnectResult.from_dict( await self._client.request("connect", connect_params) ) @@ -3995,7 +4379,6 @@ async def _start_cli_server(self) -> None: env = dict(os.environ) else: env = dict(opts.env) - # Set auth token in environment if provided if opts.github_token: env["COPILOT_SDK_AUTH_TOKEN"] = opts.github_token @@ -4146,6 +4529,7 @@ async def _start_inprocess_ffi(self) -> None: host = FfiRuntimeHost.create( runtime_path, + cli_entrypoint=self._inprocess_cli_entrypoint, environment=environment or None, args=tuple(args), ) @@ -4205,7 +4589,7 @@ async def _connect_via_stdio(self) -> None: # Create JSON-RPC client with the process self._client = JsonRpcClient(self._process) - self._client.on_close = lambda: setattr(self, "_state", "disconnected") + self._client.on_close = self._handle_connection_close self._rpc = ServerRpc(self._client) # Set up notification handler for session events @@ -4255,22 +4639,22 @@ async def _connect_via_tcp(self) -> None: if not self._runtime_port: raise RuntimeError("Server port not available") - # Create a TCP socket connection with timeout + # Create a TCP socket connection with timeout. create_connection resolves + # both IPv4 and IPv6 addresses instead of forcing AF_INET. import socket # Connection timeout constant TCP_CONNECTION_TIMEOUT = 10 # seconds - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.settimeout(TCP_CONNECTION_TIMEOUT) - try: tcp_connect_start = time.perf_counter() logger.info( "CopilotClient._connect_via_tcp connecting to CLI server", extra={"host": self._actual_host, "port": self._runtime_port}, ) - sock.connect((self._actual_host, self._runtime_port)) + sock = socket.create_connection( + (self._actual_host, self._runtime_port), timeout=TCP_CONNECTION_TIMEOUT + ) sock.settimeout(None) # Remove timeout after connection log_timing( logger, @@ -4324,9 +4708,9 @@ def kill(self): def wait(self, timeout=None): pass - self._process = SocketWrapper(sock_file, sock) # type: ignore + self._process = SocketWrapper(sock_file, sock) self._client = JsonRpcClient(self._process) - self._client.on_close = lambda: setattr(self, "_state", "disconnected") + self._client.on_close = self._handle_connection_close self._rpc = ServerRpc(self._client) # Set up notification handler for session events @@ -4370,6 +4754,7 @@ async def _apply_post_create_options_patch( custom_agents_local_only: bool | None, coauthor_enabled: bool | None, manage_schedule_enabled: bool | None, + included_builtin_skills: list[str] | None = None, ) -> None: """Apply empty-mode safe defaults (or caller-supplied overrides in copilot-cli mode) via ``session.options.update`` after create/resume. @@ -4385,6 +4770,7 @@ async def _apply_post_create_options_patch( custom_agents_local_only, coauthor_enabled, manage_schedule_enabled, + included_builtin_skills, ) if patch is None: return @@ -4403,6 +4789,9 @@ async def _apply_post_create_options_patch( SessionInstalledPlugin.from_dict(p) if isinstance(p, dict) else p for p in patch["installedPlugins"] ] + if "includedBuiltinSkills" in patch: + skills = patch["includedBuiltinSkills"] + params.included_builtin_skills = list(skills) if skills is not None else None try: await session.rpc.options.update(params) @@ -4412,7 +4801,10 @@ async def _apply_post_create_options_patch( try: await session.disconnect() except BaseException: - pass + logger.debug( + "Error disconnecting session after options update failure", + exc_info=True, + ) raise async def _set_session_fs_provider(self) -> None: @@ -4447,9 +4839,56 @@ def _register_client_global_handlers(self) -> None: hooks=_HooksAdapter(self._get_session), llm_inference=llm_inference_adapter, git_hub_telemetry=github_telemetry_adapter, + git_hub_token=self._github_token_provider_adapter, ), ) + def _register_github_token_provider( + self, provider: GitHubTokenProvider | None, session_id: str | None + ) -> str | None: + if provider is None: + return None + registration_id = str(uuid.uuid4()) + with self._github_token_providers_lock: + self._github_token_providers[registration_id] = _GitHubTokenProviderRegistration( + provider, session_id + ) + return registration_id + + def _handle_connection_close(self) -> None: + self._state = "disconnected" + with self._github_token_providers_lock: + self._github_token_providers.clear() + + def _assign_github_token_provider(self, registration_id: str | None, session_id: str) -> None: + if registration_id is None: + return + with self._github_token_providers_lock: + registration = self._github_token_providers.get(registration_id) + if registration is not None: + registration.session_id = session_id + + def _unregister_github_token_provider(self, registration_id: str | None) -> None: + if registration_id is None: + return + with self._github_token_providers_lock: + self._github_token_providers.pop(registration_id, None) + + def _commit_github_token_provider(self, session_id: str, registration_id: str | None) -> None: + with self._github_token_providers_lock: + stale = [ + candidate_id + for candidate_id, registration in self._github_token_providers.items() + if registration.session_id == session_id and registration.committed + ] + for candidate_id in stale: + self._github_token_providers.pop(candidate_id, None) + if registration_id is not None: + registration = self._github_token_providers.get(registration_id) + if registration is not None: + registration.session_id = session_id + registration.committed = True + def _get_session(self, session_id: str) -> CopilotSession | None: with self._sessions_lock: return self._sessions.get(session_id) diff --git a/python/copilot/copilot_request_handler.py b/python/copilot/copilot_request_handler.py index e6465b7bbc..893309ca76 100644 --- a/python/copilot/copilot_request_handler.py +++ b/python/copilot/copilot_request_handler.py @@ -458,8 +458,12 @@ async def write_response(self, data: str | bytes) -> None: raise RuntimeError("Copilot request response write() called before start().") if self.finished: raise RuntimeError("Copilot request response write() called after end()/error().") - is_binary = isinstance(data, (bytes, bytearray)) - payload = base64.b64encode(bytes(data)).decode("ascii") if is_binary else str(data) + if isinstance(data, bytes): + payload = base64.b64encode(data).decode("ascii") + is_binary = True + else: + payload = data + is_binary = False await self._require_rpc().http_response_chunk( LlmInferenceHTTPResponseChunkRequest( data=payload, diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py index 41e27fc12d..b393fb6eed 100644 --- a/python/copilot/generated/rpc.py +++ b/python/copilot/generated/rpc.py @@ -6,7 +6,7 @@ from typing import ClassVar, TYPE_CHECKING -from .session_events import AbortReason, Attachment, ContextTier, EmbeddedBlobResourceContents, EmbeddedTextResourceContents, McpServerSource, McpServerStatus, PermissionPromptRequest, PermissionRule, ReasoningSummary, SessionEvent, SessionLimitsConfig, SessionMode, ShutdownType, SkillSource, UserToolSessionApproval, Verbosity +from .session_events import AbortReason, AgentModelPolicy, Attachment, AutoTier, ContextTier, EmbeddedBlobResourceContents, EmbeddedTextResourceContents, McpOauthHttpResponse, McpOauthWWWAuthenticateParams, McpServerSource, McpServerStatus, ModelChangeSource, PermissionMode, PermissionPromptRequest, PermissionRule, ReasoningSummary, SessionEvent, SessionLimitsConfig, SessionMode, ShutdownType, SkillSource, TaskCompletionOutcome, UserToolSessionApproval, Verbosity if TYPE_CHECKING: from .._jsonrpc import JsonRpcClient @@ -69,8 +69,9 @@ def to_enum(c: type[EnumT], x: Any) -> EnumT: return x.value def from_int(x: Any) -> int: - assert isinstance(x, int) and not isinstance(x, bool) - return x + assert isinstance(x, (int, float)) and not isinstance(x, bool) + assert not isinstance(x, float) or x.is_integer() + return int(x) def from_datetime(x: Any) -> datetime: return dateutil.parser.parse(x) @@ -126,10 +127,19 @@ class CopilotUserResponseEndpoints: """Endpoint URLs from the raw Copilot `/copilot_internal/v2/token` user-response passthrough.""" api: str | None = None + """Copilot API endpoint URL.""" + exp: str | None = None + """Experimental-service endpoint URL.""" + origin_tracker: str | None = None + """Origin-tracker endpoint URL.""" + proxy: str | None = None + """Copilot proxy endpoint URL.""" + telemetry: str | None = None + """Copilot telemetry endpoint URL.""" @staticmethod def from_dict(obj: Any) -> 'CopilotUserResponseEndpoints': @@ -261,19 +271,22 @@ class AuthInfoType(Enum): GH_CLI = "gh-cli" HMAC = "hmac" TOKEN = "token" + TOKEN_PROVIDER = "token-provider" USER = "user" # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class AccountAllUsers: - """Authenticated account entry returned by `account.getAllUsers`, with auth info and an - optional associated token. + """Authenticated account entry returned by `account.getAllUsers`. List of all authenticated users """ auth_info: AuthInfo """Authentication information for this user""" + selection_id: str | None = None + """Opaque identifier accepted by account and model selection APIs""" + token: str | None = None """Associated token, if available""" @@ -281,12 +294,15 @@ class AccountAllUsers: def from_dict(obj: Any) -> 'AccountAllUsers': assert isinstance(obj, dict) auth_info = _load_AuthInfo(obj.get("authInfo")) + selection_id = from_union([from_str, from_none], obj.get("selectionId")) token = from_union([from_str, from_none], obj.get("token")) - return AccountAllUsers(auth_info, token) + return AccountAllUsers(auth_info, selection_id, token) def to_dict(self) -> dict: result: dict = {} result["authInfo"] = (self.auth_info).to_dict() + if self.selection_id is not None: + result["selectionId"] = from_union([from_str, from_none], self.selection_id) if self.token is not None: result["token"] = from_union([from_str, from_none], self.token) return result @@ -321,20 +337,27 @@ def to_dict(self) -> dict: @dataclass class AccountGetQuotaRequest: git_hub_token: str | None = None - """GitHub token for per-user quota lookup. When provided, resolves this token to determine - the user's quota instead of using the global auth. + """GitHub token accepted for compatibility with existing SDK clients. When provided, + resolves this token instead of using the current account. + """ + selection_id: str | None = None + """Opaque account identifier returned by `account.getAllUsers`. When omitted, the current + account is used. """ @staticmethod def from_dict(obj: Any) -> 'AccountGetQuotaRequest': assert isinstance(obj, dict) git_hub_token = from_union([from_str, from_none], obj.get("gitHubToken")) - return AccountGetQuotaRequest(git_hub_token) + selection_id = from_union([from_str, from_none], obj.get("selectionId")) + return AccountGetQuotaRequest(git_hub_token, selection_id) def to_dict(self) -> dict: result: dict = {} if self.git_hub_token is not None: result["gitHubToken"] = from_union([from_str, from_none], self.git_hub_token) + if self.selection_id is not None: + result["selectionId"] = from_union([from_str, from_none], self.selection_id) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -396,30 +419,34 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class AccountLoginRequest: - """Credentials to store after successful authentication""" - + """Credentials to validate and store. Omit login to resolve the authenticated user from the + token. + """ host: str """GitHub host URL""" - login: str - """User login/username""" - token: str """GitHub authentication token""" + login: str | None = None + """User login/username. When omitted, the runtime validates the token and resolves the login + from GitHub. + """ + @staticmethod def from_dict(obj: Any) -> 'AccountLoginRequest': assert isinstance(obj, dict) host = from_str(obj.get("host")) - login = from_str(obj.get("login")) token = from_str(obj.get("token")) - return AccountLoginRequest(host, login, token) + login = from_union([from_str, from_none], obj.get("login")) + return AccountLoginRequest(host, token, login) def to_dict(self) -> dict: result: dict = {} result["host"] = from_str(self.host) - result["login"] = from_str(self.login) result["token"] = from_str(self.token) + if self.login is not None: + result["login"] = from_union([from_str, from_none], self.login) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -449,18 +476,25 @@ def to_dict(self) -> dict: class AccountLogoutRequest: """User to log out""" - auth_info: AuthInfo + auth_info: AuthInfo | None = None """Authentication information for the user to log out""" + selection_id: str | None = None + """Opaque account identifier returned by `account.getAllUsers`""" + @staticmethod def from_dict(obj: Any) -> 'AccountLogoutRequest': assert isinstance(obj, dict) - auth_info = _load_AuthInfo(obj.get("authInfo")) - return AccountLogoutRequest(auth_info) + auth_info = from_union([_load_AuthInfo, from_none], obj.get("authInfo")) + selection_id = from_union([from_str, from_none], obj.get("selectionId")) + return AccountLogoutRequest(auth_info, selection_id) def to_dict(self) -> dict: result: dict = {} - result["authInfo"] = (self.auth_info).to_dict() + if self.auth_info is not None: + result["authInfo"] = from_union([lambda x: (x).to_dict(), from_none], self.auth_info) + if self.selection_id is not None: + result["selectionId"] = from_union([from_str, from_none], self.selection_id) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -679,23 +713,101 @@ def to_dict(self) -> dict: result["projectPaths"] = from_union([lambda x: from_list(from_str, x), from_none], self.project_paths) return result +class APIKeyAuthInfoType(Enum): + API_KEY = "api-key" + # Experimental: this type is part of an experimental API and may change or be removed. -class PermissionsAllowAllMode(Enum): - """Authoritative allow-all mode after the mutation +@dataclass +class AuthValidationError: + """Validation error from an authentication attempt. - Current or requested allow-all mode. + Validation errors from the most recent authentication attempt. + """ + message: str + """Authentication validation error message""" - Current allow-all mode + github_message: str | None = None + """Optional message returned by GitHub""" - Allow-all mode to apply. `on` enables full allow-all; `auto` enables advisory LLM - auto-approval; `off` disables both. + @staticmethod + def from_dict(obj: Any) -> 'AuthValidationError': + assert isinstance(obj, dict) + message = from_str(obj.get("message")) + github_message = from_union([from_str, from_none], obj.get("githubMessage")) + return AuthValidationError(message, github_message) + + def to_dict(self) -> dict: + result: dict = {} + result["message"] = from_str(self.message) + if self.github_message is not None: + result["githubMessage"] = from_union([from_str, from_none], self.github_message) + 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 provider or provider-model metadata. This is not + necessarily the provider-facing deployment or model name and does not indicate CAPI + entitlement or provider availability. """ - AUTO = "auto" - OFF = "off" - ON = "on" -class APIKeyAuthInfoType(Enum): - API_KEY = "api-key" + @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. +class BuiltinToolFormatType(Enum): + """Custom input-format discriminator. + + Custom input-format kind. + """ + GRAMMAR = "grammar" + +# Experimental: this type is part of an experimental API and may change or be removed. +class BuiltinToolInputSchemaType(Enum): + """Root type of the tool input schema. + + Root JSON Schema type for a built-in tool input. + """ + OBJECT = "object" + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class BuiltinToolSafeTelemetryFields: + """Per-field telemetry safety + + Per-field telemetry-safety policy for a built-in tool. + """ + inputs_names: bool | None = None + """Whether tool input names may be included in telemetry without obfuscation.""" + + name: bool | None = None + """Whether the tool name may be included in telemetry without obfuscation.""" + + @staticmethod + def from_dict(obj: Any) -> 'BuiltinToolSafeTelemetryFields': + assert isinstance(obj, dict) + inputs_names = from_union([from_bool, from_none], obj.get("inputsNames")) + name = from_union([from_bool, from_none], obj.get("name")) + return BuiltinToolSafeTelemetryFields(inputs_names, name) + + def to_dict(self) -> dict: + result: dict = {} + if self.inputs_names is not None: + result["inputsNames"] = from_union([from_bool, from_none], self.inputs_names) + if self.name is not None: + result["name"] = from_union([from_bool, from_none], self.name) + return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass @@ -886,11 +998,64 @@ def to_dict(self) -> dict: result["url"] = from_union([from_str, from_none], self.url) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class CanvasProviderRegisterRequest: + """Internal canvas provider registration parameters.""" + + canvases: list[Any] + """Canvas contributions supplied by the provider""" + + connection_id: str + """Connection identifier for callback routing""" + + info: Any + """Provider metadata supplied by the host""" + + @staticmethod + def from_dict(obj: Any) -> 'CanvasProviderRegisterRequest': + assert isinstance(obj, dict) + canvases = from_list(lambda x: x, obj.get("canvases")) + connection_id = from_str(obj.get("connectionId")) + info = obj.get("info") + return CanvasProviderRegisterRequest(canvases, connection_id, info) + + def to_dict(self) -> dict: + result: dict = {} + result["canvases"] = from_list(lambda x: x, self.canvases) + result["connectionId"] = from_str(self.connection_id) + result["info"] = self.info + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class CanvasProviderUnregisterRequest: + """Internal canvas provider unregistration parameters.""" + + connection_id: str + """Connection identifier to unregister""" + + @staticmethod + def from_dict(obj: Any) -> 'CanvasProviderUnregisterRequest': + assert isinstance(obj, dict) + connection_id = from_str(obj.get("connectionId")) + return CanvasProviderUnregisterRequest(connection_id) + + def to_dict(self) -> dict: + result: dict = {} + result["connectionId"] = from_str(self.connection_id) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class CapiSessionOptions: """Options scoped to the built-in CAPI (Copilot API) provider.""" + auto_tier: AutoTier | None = None + """Routing preference used when the session model is `auto`. The runtime persists the + preference across cold resume. When omitted, the default routing behavior is used. + Resuming an already-resident session cannot change its preference. + """ enable_web_socket_responses: bool | None = None """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 @@ -902,15 +1067,436 @@ class CapiSessionOptions: @staticmethod def from_dict(obj: Any) -> 'CapiSessionOptions': assert isinstance(obj, dict) + auto_tier = from_union([AutoTier, from_none], obj.get("autoTier")) enable_web_socket_responses = from_union([from_bool, from_none], obj.get("enableWebSocketResponses")) - return CapiSessionOptions(enable_web_socket_responses) + return CapiSessionOptions(auto_tier, enable_web_socket_responses) def to_dict(self) -> dict: result: dict = {} + if self.auto_tier is not None: + result["autoTier"] = from_union([lambda x: to_enum(AutoTier, x), from_none], self.auto_tier) if self.enable_web_socket_responses is not None: result["enableWebSocketResponses"] = from_union([from_bool, from_none], self.enable_web_socket_responses) return result +# Experimental: this type is part of an experimental API and may change or be removed. +class CardDigestAlgorithm(Enum): + """Canonical digest algorithm for a validated MCP card""" + + SHA256_RFC8785 = "sha256-rfc8785" + +class Installability(Enum): + NOT_INSTALLABLE_KIND = "not-installable-kind" + +class CatalogAISkillCandidateKind(Enum): + AI_SKILL = "ai-skill" + +class MediaType(Enum): + APPLICATION_AI_SKILL = "application/ai-skill" + +class CatalogCandidateSourceKind(Enum): + """Discriminator for a URL-backed MCP server card + + Discriminator for an embedded MCP server card + """ + EMBEDDED = "embedded" + URL = "url" + +class CatalogAuthenticationRequiredErrorKind(Enum): + AUTHENTICATION_REQUIRED = "authentication-required" + +# Experimental: this type is part of an experimental API and may change or be removed. +class CatalogAuthenticationRequiredReason(Enum): + """Why authentication failed. Only an expired credential justifies attempting a silent + refresh; an absent or rejected credential requires sign-in. + + Why the catalog authority did not accept the caller's identity + """ + CREDENTIAL_EXPIRED = "credential-expired" + CREDENTIAL_REJECTED = "credential-rejected" + NO_CREDENTIAL = "no-credential" + +class CatalogCandidateInstallability(Enum): + """Whether this MCP server can be planned for installation, and if policy prevents it. + + Whether an MCP server candidate can be planned for installation + """ + INSTALLABLE = "installable" + NOT_INSTALLABLE_KIND = "not-installable-kind" + NOT_INSTALLABLE_POLICY = "not-installable-policy" + +# Experimental: this type is part of an experimental API and may change or be removed. +class CatalogCandidateKind(Enum): + """What kind of resource a catalog candidate describes""" + + AI_SKILL = "ai-skill" + MCP_SERVER = "mcp-server" + +# Experimental: this type is part of an experimental API and may change or be removed. +class CatalogMediaType(Enum): + """JSON MCP media type of the underlying card. + + JSON MCP card media type accepted for install planning + + JSON MCP media type advertised for the referenced card. + + JSON MCP media type the validated card was interpreted as. + + Media type the card is expected to conform to. + + Media type the card was interpreted as, when it declared one this runtime recognises. + + Media type a catalog card is interpreted as + """ + APPLICATION_AI_SKILL = "application/ai-skill" + APPLICATION_MCP_SERVER_CARD_JSON = "application/mcp-server-card+json" + APPLICATION_MCP_SERVER_JSON = "application/mcp-server+json" + +# Experimental: this type is part of an experimental API and may change or be removed. +class MCPServerCardEmbeddedKind(Enum): + """Discriminator for an embedded MCP server card""" + + EMBEDDED = "embedded" + +# Experimental: this type is part of an experimental API and may change or be removed. +class MCPServerCardURLKind(Enum): + """Discriminator for a URL-backed MCP server card""" + + URL = "url" + +# Experimental: this type is part of an experimental API and may change or be removed. +class CatalogCapability(Enum): + """A wire feature a caller can require of the catalog surface, negotiated per request. A + grant means the runtime understands the feature's contract, not that the deployment has + enabled the operation; typed unavailable results report availability separately. + """ + AI_SKILL_DISCOVERY = "ai-skill-discovery" + LEGACY_MCP_SERVER_CARD = "legacy-mcp-server-card" + MCP_INSTALL_PLANNING = "mcp-install-planning" + MCP_SERVER_CARD = "mcp-server-card" + MULTIPLE_TRANSPORT_CHOICE = "multiple-transport-choice" + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class CatalogClientContract: + """The protocol version and capability set a caller requires, supplied on every catalog + request so negotiation cannot be skipped by omission. + + Protocol version and capabilities the caller requires. + """ + protocol_version: int + """SDK protocol version the caller was generated against. A caller below the runtime's + minimum supported version is refused rather than served a partial result. + """ + required_capabilities: list[str] + """Wire features the caller requires the runtime to understand. Identifiers are bounded but + extensible so a newer caller can negotiate with an older runtime. Requiring an unknown + feature yields a typed refusal listing what is understood, never a partial grant. A grant + does not promise that a deployment has enabled the operation; typed unavailable results + report that separately. + """ + + @staticmethod + def from_dict(obj: Any) -> 'CatalogClientContract': + assert isinstance(obj, dict) + protocol_version = from_int(obj.get("protocolVersion")) + required_capabilities = from_list(from_str, obj.get("requiredCapabilities")) + return CatalogClientContract(protocol_version, required_capabilities) + + def to_dict(self) -> dict: + result: dict = {} + result["protocolVersion"] = from_int(self.protocol_version) + result["requiredCapabilities"] = from_list(from_str, self.required_capabilities) + return result + +class CatalogContractViolationErrorKind(Enum): + CONTRACT_VIOLATION = "contract-violation" + +# Experimental: this type is part of an experimental API and may change or be removed. +class CatalogContractViolationReason(Enum): + """Which rule the response broke. + + Which wire-contract rule an upstream response broke + """ + BOTH_URL_AND_DATA = "both-url-and-data" + DUPLICATE_IDENTITY = "duplicate-identity" + NEITHER_URL_NOR_DATA = "neither-url-nor-data" + UNKNOWN_MEDIA_TYPE = "unknown-media-type" + +# Experimental: this type is part of an experimental API and may change or be removed. +class CatalogHandleType(Enum): + """Which kind of handle was presented. + + Which kind of opaque handle was presented + """ + CANDIDATE = "candidate" + PLAN = "plan" + +class CatalogHandleRejectedErrorKind(Enum): + HANDLE_REJECTED = "handle-rejected" + +# Experimental: this type is part of an experimental API and may change or be removed. +class CatalogHandleRejectionReason(Enum): + """Why the handle was rejected. + + Why a presented handle was rejected + """ + FOREIGN = "foreign" + INVALID = "invalid" + REPLAYED = "replayed" + STALE = "stale" + +# Experimental: this type is part of an experimental API and may change or be removed. +class CatalogInvalidRequestField(Enum): + """Which request field was rejected. + + Which request field was rejected before any work was done + """ + CARD = "card" + CONTRACT = "contract" + KINDS = "kinds" + LIMIT = "limit" + QUERY = "query" + SCOPE = "scope" + SOURCE = "source" + +class CatalogInvalidRequestErrorKind(Enum): + INVALID_REQUEST = "invalid-request" + +class CatalogMalformedCardErrorKind(Enum): + MALFORMED_CARD = "malformed-card" + +# Experimental: this type is part of an experimental API and may change or be removed. +class CatalogMalformedCardReason(Enum): + """How the card failed validation. + + How a card failed validation + """ + INVALID_JSON = "invalid-json" + MISSING_REQUIRED_FIELD = "missing-required-field" + SCHEMA_VIOLATION = "schema-violation" + SIZE_LIMIT_EXCEEDED = "size-limit-exceeded" + UNSUPPORTED_MEDIA_TYPE = "unsupported-media-type" + +# Experimental: this type is part of an experimental API and may change or be removed. +class CatalogMCPServerInstallabilityEnum(Enum): + """Whether this MCP server can be planned for installation, and if policy prevents it. + + Whether an MCP server candidate can be planned for installation + """ + INSTALLABLE = "installable" + NOT_INSTALLABLE_POLICY = "not-installable-policy" + +class CatalogMCPServerCandidateKind(Enum): + MCP_SERVER = "mcp-server" + +# Experimental: this type is part of an experimental API and may change or be removed. +class MCPServerCardMediaType(Enum): + """JSON MCP media type of the underlying card. + + JSON MCP card media type accepted for install planning + + JSON MCP media type advertised for the referenced card. + + JSON MCP media type the validated card was interpreted as. + + Media type the card is expected to conform to. + """ + APPLICATION_MCP_SERVER_CARD_JSON = "application/mcp-server-card+json" + APPLICATION_MCP_SERVER_JSON = "application/mcp-server+json" + +class CatalogNegotiationRefusedErrorKind(Enum): + NEGOTIATION_REFUSED = "negotiation-refused" + +# Experimental: this type is part of an experimental API and may change or be removed. +class CatalogNegotiationRefusedReason(Enum): + """Whether the version or the capability set was the problem. + + Why capability and protocol-version negotiation refused a caller + """ + UNSUPPORTED_CAPABILITY = "unsupported-capability" + UNSUPPORTED_PROTOCOL_VERSION = "unsupported-protocol-version" + +class CatalogNetworkFailureErrorKind(Enum): + NETWORK_FAILURE = "network-failure" + +# Experimental: this type is part of an experimental API and may change or be removed. +class CatalogNetworkFailureReason(Enum): + """Categorised failure, low cardinality so it can be aggregated without carrying a URL. + + Categorised network failure, low cardinality so it can be aggregated without carrying a + URL + """ + CONNECTION_REFUSED = "connection-refused" + DNS = "dns" + HTTP_STATUS = "http-status" + OFFLINE = "offline" + PROXY_AUTHENTICATION_REQUIRED = "proxy-authentication-required" + RATE_LIMITED = "rate-limited" + REDIRECT_REJECTED = "redirect-rejected" + RESPONSE_TOO_LARGE = "response-too-large" + SERVICE_UNAVAILABLE = "service-unavailable" + TIMEOUT = "timeout" + TLS = "tls" + +class CatalogNotInstallableErrorKind(Enum): + NOT_INSTALLABLE = "not-installable" + +# Experimental: this type is part of an experimental API and may change or be removed. +class CatalogNotInstallableReason(Enum): + """Why the candidate cannot be installed. + + Why a discoverable candidate cannot be installed + """ + AI_SKILL_NOT_INSTALLABLE = "ai-skill-not-installable" + KIND_NOT_INSTALLABLE = "kind-not-installable" + POLICY_FORBIDS = "policy-forbids" + +class CatalogPolicyRejectedErrorKind(Enum): + POLICY_REJECTED = "policy-rejected" + +# Experimental: this type is part of an experimental API and may change or be removed. +class MCPPlanPolicySource(Enum): + """Which authority produced the decision. + + Which authority produced a policy decision + """ + ENTERPRISE_ALLOWLIST = "enterprise-allowlist" + LOCAL_TRUST = "local-trust" + NONE = "none" + REGISTRY_POLICY = "registry-policy" + +class CatalogSearchResultKind(Enum): + AUTHENTICATION_REQUIRED = "authentication-required" + CONTRACT_VIOLATION = "contract-violation" + INVALID_REQUEST = "invalid-request" + MALFORMED_CARD = "malformed-card" + NEGOTIATION_REFUSED = "negotiation-refused" + NETWORK_FAILURE = "network-failure" + POLICY_REJECTED = "policy-rejected" + SUCCEEDED = "succeeded" + UNAVAILABLE = "unavailable" + UNSAFE_RETRIEVAL = "unsafe-retrieval" + UNSUPPORTED_KIND = "unsupported-kind" + +class CatalogSearchResultReason(Enum): + """Whether the version or the capability set was the problem. + + Why capability and protocol-version negotiation refused a caller + + Why authentication failed. Only an expired credential justifies attempting a silent + refresh; an absent or rejected credential requires sign-in. + + Why the catalog authority did not accept the caller's identity + + Categorised failure, low cardinality so it can be aggregated without carrying a URL. + + Categorised network failure, low cardinality so it can be aggregated without carrying a + URL + + Which control refused the retrieval, low cardinality so it can be aggregated without + carrying a URL. + + Which hardened-fetch control refused a retrieval + + How the card failed validation. + + How a card failed validation + + Which rule the response broke. + + Which wire-contract rule an upstream response broke + + Why the operation is unavailable. + + Why a catalog operation is not available on this runtime + """ + AUTHORITY_NOT_CONFIGURED = "authority-not-configured" + BLOCKED_ADDRESS = "blocked-address" + BLOCKED_SCHEME = "blocked-scheme" + BOTH_URL_AND_DATA = "both-url-and-data" + CONNECTION_REFUSED = "connection-refused" + CREDENTIALS_IN_URL = "credentials-in-url" + CREDENTIAL_EXPIRED = "credential-expired" + CREDENTIAL_REJECTED = "credential-rejected" + DISABLED_BY_POLICY = "disabled-by-policy" + DNS = "dns" + DUPLICATE_IDENTITY = "duplicate-identity" + HOST_NOT_PERMITTED = "host-not-permitted" + HTTP_STATUS = "http-status" + INVALID_JSON = "invalid-json" + MISSING_REQUIRED_FIELD = "missing-required-field" + NEITHER_URL_NOR_DATA = "neither-url-nor-data" + NO_CREDENTIAL = "no-credential" + OFFLINE = "offline" + PLANNING_UNAVAILABLE = "planning-unavailable" + PROXY_AUTHENTICATION_REQUIRED = "proxy-authentication-required" + PROXY_REJECTED = "proxy-rejected" + RATE_LIMITED = "rate-limited" + REDIRECT_REJECTED = "redirect-rejected" + REDIRECT_TO_BLOCKED_ADDRESS = "redirect-to-blocked-address" + RESPONSE_TOO_LARGE = "response-too-large" + SCHEMA_VIOLATION = "schema-violation" + SEARCH_UNAVAILABLE = "search-unavailable" + SERVICE_UNAVAILABLE = "service-unavailable" + SIZE_LIMIT_EXCEEDED = "size-limit-exceeded" + TIMEOUT = "timeout" + TLS = "tls" + UNKNOWN_MEDIA_TYPE = "unknown-media-type" + UNSUPPORTED_CAPABILITY = "unsupported-capability" + UNSUPPORTED_MEDIA_TYPE = "unsupported-media-type" + UNSUPPORTED_PROTOCOL_VERSION = "unsupported-protocol-version" + +class CatalogSearchSucceededKind(Enum): + SUCCEEDED = "succeeded" + +class CatalogUnavailableErrorKind(Enum): + UNAVAILABLE = "unavailable" + +# Experimental: this type is part of an experimental API and may change or be removed. +class CatalogUnavailableReason(Enum): + """Why the operation is unavailable. + + Why a catalog operation is not available on this runtime + """ + AUTHORITY_NOT_CONFIGURED = "authority-not-configured" + DISABLED_BY_POLICY = "disabled-by-policy" + PLANNING_UNAVAILABLE = "planning-unavailable" + SEARCH_UNAVAILABLE = "search-unavailable" + +class CatalogUnavailableTransportErrorKind(Enum): + UNAVAILABLE_TRANSPORT = "unavailable-transport" + +# Experimental: this type is part of an experimental API and may change or be removed. +class CatalogUnavailableTransportReason(Enum): + """Why no transport could be offered. + + Why no usable transport could be offered + """ + NO_ELIGIBLE_TRANSPORT = "no-eligible-transport" + REMOTE_ENUMERATION_UNAVAILABLE = "remote-enumeration-unavailable" + TRANSPORT_NOT_SUPPORTED = "transport-not-supported" + +class CatalogUnsafeRetrievalErrorKind(Enum): + UNSAFE_RETRIEVAL = "unsafe-retrieval" + +# Experimental: this type is part of an experimental API and may change or be removed. +class CatalogUnsafeRetrievalReason(Enum): + """Which control refused the retrieval, low cardinality so it can be aggregated without + carrying a URL. + + Which hardened-fetch control refused a retrieval + """ + BLOCKED_ADDRESS = "blocked-address" + BLOCKED_SCHEME = "blocked-scheme" + CREDENTIALS_IN_URL = "credentials-in-url" + HOST_NOT_PERMITTED = "host-not-permitted" + PROXY_REJECTED = "proxy-rejected" + REDIRECT_TO_BLOCKED_ADDRESS = "redirect-to-blocked-address" + +class CatalogUnsupportedKindErrorKind(Enum): + UNSUPPORTED_KIND = "unsupported-kind" + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SlashCommandInputChoice: @@ -950,6 +1536,41 @@ class SlashCommandKind(Enum): CLIENT = "client" SKILL = "skill" +# Experimental: this type is part of an experimental API and may change or be removed. +class CommandsInvocationEffectOutcome(Enum): + """Whether the host applied or cancelled the pending invocation effect. + + Whether a pending slash-command invocation effect was applied or cancelled by the host. + """ + APPLIED = "applied" + CANCELLED = "cancelled" + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class CommandsFinalizeInvocationEffectResult: + """Whether finalizing the invocation effect succeeded, and the failure reason when it did + not. + """ + success: bool + """Whether the pending invocation effect was finalized successfully.""" + + error: str | None = None + """Failure reason when the invocation effect could not be finalized.""" + + @staticmethod + def from_dict(obj: Any) -> 'CommandsFinalizeInvocationEffectResult': + assert isinstance(obj, dict) + success = from_bool(obj.get("success")) + error = from_union([from_str, from_none], obj.get("error")) + return CommandsFinalizeInvocationEffectResult(success, error) + + def to_dict(self) -> dict: + result: dict = {} + result["success"] = from_bool(self.success) + 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. @dataclass class CommandsHandlePendingCommandRequest: @@ -995,29 +1616,10 @@ def to_dict(self) -> dict: return result # Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class CommandsInvokeRequest: - """Slash command name and optional raw input string to invoke.""" +class CommandsInvocationOrigin(Enum): + """Optional client surface that initiated the invocation""" - name: str - """Command name. Leading slashes are stripped and the name is matched case-insensitively.""" - - input: str | None = None - """Raw input after the command name""" - - @staticmethod - def from_dict(obj: Any) -> 'CommandsInvokeRequest': - assert isinstance(obj, dict) - name = from_str(obj.get("name")) - input = from_union([from_str, from_none], obj.get("input")) - return CommandsInvokeRequest(name, input) - - def to_dict(self) -> dict: - result: dict = {} - result["name"] = from_str(self.name) - if self.input is not None: - result["input"] = from_union([from_str, from_none], self.input) - return result + SETTINGS = "settings" # Experimental: this type is part of an experimental API and may change or be removed. @dataclass @@ -1165,59 +1767,68 @@ 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 ConnectRemoteSessionParams: - """Remote session connection parameters.""" +class _ConnectClientInfo: + """Identity of the integrating host, declared once on the `server.connect` handshake so + telemetry from this connection is attributed to a single, consistent surface. All fields + are optional; omit them to keep the default attribution. - session_id: str - """Session ID to connect to.""" + Identity of the integrating host. Optional; omit it to keep the default attribution. + """ + editor_name: str | None = None + """Name of the host editor, e.g. `"vscode"`.""" + + editor_version: str | None = None + """Version of the host editor, e.g. `"1.124.2"`. Ignored unless it looks like a version + string. + """ + extension_name: str | None = None + """Name of the Copilot extension within the host, e.g. `"copilot-chat"`.""" + + extension_version: str | None = None + """Version of the Copilot extension within the host, e.g. `"0.54.0"`. Ignored unless it + looks like a version string. + """ @staticmethod - def from_dict(obj: Any) -> 'ConnectRemoteSessionParams': + def from_dict(obj: Any) -> '_ConnectClientInfo': assert isinstance(obj, dict) - session_id = from_str(obj.get("sessionId")) - return ConnectRemoteSessionParams(session_id) + editor_name = from_union([from_str, from_none], obj.get("editorName")) + editor_version = from_union([from_str, from_none], obj.get("editorVersion")) + extension_name = from_union([from_str, from_none], obj.get("extensionName")) + extension_version = from_union([from_str, from_none], obj.get("extensionVersion")) + return _ConnectClientInfo(editor_name, editor_version, extension_name, extension_version) def to_dict(self) -> dict: result: dict = {} - result["sessionId"] = from_str(self.session_id) + if self.editor_name is not None: + result["editorName"] = from_union([from_str, from_none], self.editor_name) + if self.editor_version is not None: + result["editorVersion"] = from_union([from_str, from_none], self.editor_version) + if self.extension_name is not None: + result["extensionName"] = from_union([from_str, from_none], self.extension_name) + if self.extension_version is not None: + result["extensionVersion"] = from_union([from_str, from_none], self.extension_version) 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 _ConnectRequest: - """Parameters for the `server.connect` handshake: an optional connection token and optional - connection-level opt-ins (e.g. GitHub telemetry forwarding). - """ - 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. - 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""" +class ConnectRemoteSessionParams: + """Remote session connection parameters.""" + + session_id: str + """Session ID to connect to.""" @staticmethod - def from_dict(obj: Any) -> '_ConnectRequest': + def from_dict(obj: Any) -> 'ConnectRemoteSessionParams': assert isinstance(obj, dict) - enable_git_hub_telemetry_forwarding = from_union([from_bool, from_none], obj.get("enableGitHubTelemetryForwarding")) - token = from_union([from_str, from_none], obj.get("token")) - return _ConnectRequest(enable_git_hub_telemetry_forwarding, token) + session_id = from_str(obj.get("sessionId")) + return ConnectRemoteSessionParams(session_id) def to_dict(self) -> dict: result: dict = {} - if self.enable_git_hub_telemetry_forwarding is not None: - result["enableGitHubTelemetryForwarding"] = from_union([from_bool, from_none], self.enable_git_hub_telemetry_forwarding) - if self.token is not None: - result["token"] = from_union([from_str, from_none], self.token) + result["sessionId"] = from_str(self.session_id) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -1887,6 +2498,42 @@ def to_dict(self) -> 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 HookType(Enum): + """Hook event that invokes this action. + + Hook event name. Discovery emits the file-configurable subset; SDK callbacks additionally + support callback-only events. + """ + AGENT_STOP = "agentStop" + ERROR_OCCURRED = "errorOccurred" + NOTIFICATION = "notification" + PERMISSION_REQUEST = "permissionRequest" + POST_RESULT = "postResult" + POST_TOOL_USE = "postToolUse" + POST_TOOL_USE_FAILURE = "postToolUseFailure" + PRE_COMPACT = "preCompact" + PRE_MCP_TOOL_CALL = "preMcpToolCall" + PRE_PR_DESCRIPTION = "prePRDescription" + PRE_TOOL_USE = "preToolUse" + SESSION_END = "sessionEnd" + SESSION_START = "sessionStart" + SUBAGENT_START = "subagentStart" + SUBAGENT_STOP = "subagentStop" + USER_PROMPT_SUBMITTED = "userPromptSubmitted" + USER_PROMPT_TRANSFORMED = "userPromptTransformed" + +# Experimental: this type is part of an experimental API and may change or be removed. +class HookOrigin(Enum): + """Configuration tier that contributed this hook action. + + Configuration tier that contributed a discovered hook action. + """ + PLUGIN = "plugin" + POLICY = "policy" + REPOSITORY = "repository" + USER = "user" + # 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""" @@ -1905,16 +2552,21 @@ 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. """ + display_text: str | None = None + """Optional user-facing text for the queue row. The command string is shown when omitted.""" @staticmethod def from_dict(obj: Any) -> 'EnqueueCommandParams': assert isinstance(obj, dict) command = from_str(obj.get("command")) - return EnqueueCommandParams(command) + display_text = from_union([from_none, from_str], obj.get("displayText")) + return EnqueueCommandParams(command, display_text) def to_dict(self) -> dict: result: dict = {} result["command"] = from_str(self.command) + if self.display_text is not None: + result["displayText"] = from_union([from_none, from_str], self.display_text) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -1969,6 +2621,9 @@ class EventsReadDirection(Enum): 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. + + Direction to page through persisted history. Forward starts at the beginning; backward + starts with the newest events. Events in each page remain chronological. """ BACKWARD = "backward" FORWARD = "forward" @@ -2096,6 +2751,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" @@ -2114,6 +2771,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: @@ -2243,29 +2933,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 @@ -2290,65 +2998,6 @@ 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: @@ -2374,7 +3023,10 @@ class FactoryCurrentPhase: """Current factory phase identity.""" id: str + """Current phase identifier.""" + ordinal: int | None = None + """Zero-based declared phase ordinal, or null for an undeclared phase.""" @staticmethod def from_dict(obj: Any) -> 'FactoryCurrentPhase': @@ -2392,12 +3044,21 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class FactoryDeclaredLimits: - """Declared or approved factory resource ceilings.""" + """Declared or approved factory resource ceilings. + Resource ceilings declared by the factory. + """ max_ai_credits: float | None = None + """Maximum AI credits consumed by subagents and descendants.""" + max_concurrent_subagents: int | None = None + """Maximum concurrently active subagents.""" + max_total_subagents: int | None = None + """Maximum total subagents spawned by the run.""" + timeout_seconds: float | None = None + """Maximum accumulated active execution time in seconds.""" @staticmethod def from_dict(obj: Any) -> 'FactoryDeclaredLimits': @@ -2434,6 +3095,7 @@ class FactoryDurableOperation(Enum): JOURNAL_PUT = "journalPut" MARK_RUN_STARTED = "markRunStarted" RECONCILE_CREDIT_TOTAL = "reconcileCreditTotal" + REFRESH_LEASE = "refreshLease" RELEASE_AGENT = "releaseAgent" RESERVE_AGENT = "reserveAgent" @@ -2649,24 +3311,50 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class FactoryListRunsRequest: - """Empty parameters for listing factory runs.""" + """Parameters for paging factory runs.""" + + 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) - return FactoryListRunsRequest() + 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.""" + """Durable resource consumption. + Durable factory resource consumption. + """ active_ms: int + """Accumulated active execution time in milliseconds.""" + nano_aiu: int + """AI usage consumed by the run in nano-AIU.""" + subagents: int + """Total subagents spawned by the run.""" @staticmethod def from_dict(obj: Any) -> 'FactoryRunConsumed': @@ -2685,7 +3373,9 @@ def to_dict(self) -> dict: # 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 factory run status. + + Current or terminal state of a factory run. Current or terminal factory run status. """ @@ -2707,8 +3397,10 @@ class FactoryRunFailureKind(Enum): 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_PROVIDER_DISCONNECTED = "factory_provider_disconnected" FACTORY_RESUME_DECLINED = "factory_resume_declined" # Experimental: this type is part of an experimental API and may change or be removed. @@ -2724,8 +3416,10 @@ class FactoryLogLineKind(Enum): # 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.""" + """Derived lifecycle state of the phase. + Derived lifecycle state of a factory phase. + """ ACTIVE = "active" COMPLETED = "completed" PENDING = "pending" @@ -2907,6 +3601,12 @@ class GitHubTelemetryClientInfo: copilot_plan: str | None = None """Copilot subscription plan, when known.""" + cpu_count: int | None = None + """Number of logical CPU cores on the host.""" + + cpu_model: str | None = None + """Distinct CPU model names for the host, comma-separated.""" + dev_device_id: str | None = None """Stable machine identifier for the device.""" @@ -2924,9 +3624,11 @@ def from_dict(obj: Any) -> 'GitHubTelemetryClientInfo': client_name = from_union([from_str, from_none], obj.get("client_name")) client_type = from_union([from_str, from_none], obj.get("client_type")) copilot_plan = from_union([from_str, from_none], obj.get("copilot_plan")) + cpu_count = from_union([from_int, from_none], obj.get("cpu_count")) + cpu_model = from_union([from_str, from_none], obj.get("cpu_model")) dev_device_id = from_union([from_str, from_none], obj.get("dev_device_id")) is_staff = from_union([from_bool, from_none], obj.get("is_staff")) - return GitHubTelemetryClientInfo(cli_version, node_version, os_arch, os_platform, os_version, client_name, client_type, copilot_plan, dev_device_id, is_staff) + return GitHubTelemetryClientInfo(cli_version, node_version, os_arch, os_platform, os_version, client_name, client_type, copilot_plan, cpu_count, cpu_model, dev_device_id, is_staff) def to_dict(self) -> dict: result: dict = {} @@ -2941,12 +3643,26 @@ def to_dict(self) -> dict: result["client_type"] = from_union([from_str, from_none], self.client_type) if self.copilot_plan is not None: result["copilot_plan"] = from_union([from_str, from_none], self.copilot_plan) + if self.cpu_count is not None: + result["cpu_count"] = from_union([from_int, from_none], self.cpu_count) + if self.cpu_model is not None: + result["cpu_model"] = from_union([from_str, from_none], self.cpu_model) if self.dev_device_id is not None: result["dev_device_id"] = from_union([from_str, from_none], self.dev_device_id) if self.is_staff is not None: result["is_staff"] = from_union([from_bool, from_none], self.is_staff) return result +class GitHubTokenAcquireReason(Enum): + """Why the runtime is requesting a GitHub credential.""" + + INITIAL = "initial" + REFRESH = "refresh" + +class GitHubTokenAcquireResultKind(Enum): + CANCELLED = "cancelled" + TOKEN = "token" + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class HandlePendingToolCallResult: @@ -3332,28 +4048,6 @@ def to_dict(self) -> dict: class HMACAuthInfoType(Enum): HMAC = "hmac" -# Internal: this type is an internal SDK API and is not part of the public surface. -class _HookType(Enum): - """Hook event name dispatched through the SDK callback transport.""" - - AGENT_STOP = "agentStop" - ERROR_OCCURRED = "errorOccurred" - NOTIFICATION = "notification" - PERMISSION_REQUEST = "permissionRequest" - POST_RESULT = "postResult" - POST_TOOL_USE = "postToolUse" - POST_TOOL_USE_FAILURE = "postToolUseFailure" - PRE_COMPACT = "preCompact" - PRE_MCP_TOOL_CALL = "preMcpToolCall" - PRE_PR_DESCRIPTION = "prePRDescription" - PRE_TOOL_USE = "preToolUse" - SESSION_END = "sessionEnd" - SESSION_START = "sessionStart" - SUBAGENT_START = "subagentStart" - SUBAGENT_STOP = "subagentStop" - USER_PROMPT_SUBMITTED = "userPromptSubmitted" - USER_PROMPT_TRANSFORMED = "userPromptTransformed" - # Internal: this type is an internal SDK API and is not part of the public surface. @dataclass class _HookInvokeResponse: @@ -3373,20 +4067,49 @@ def to_dict(self) -> dict: result["output"] = self.output return result -class PurpleSource(Enum): +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class HooksDiscoverRequest: + """Optional project paths and host-exclusion behavior for server-scoped hook discovery.""" + + exclude_host_hooks: bool | None = None + """When true, omit host-owned user and plugin hook rows and their diagnostics. + Managed-policy hooks and trusted repository hooks remain visible, and host disabledHooks + still contribute to each remaining row's effective enabled state. This filters sources + rather than simulating a host with no settings. + """ + project_paths: list[str] | None = None + """Optional project directory paths whose trusted repository and project-expanded plugin + hooks should be discovered. When omitted or empty, user, managed-policy, and globally + enabled installed or explicit plugin hooks are returned without project expansion. + """ + + @staticmethod + def from_dict(obj: Any) -> 'HooksDiscoverRequest': + assert isinstance(obj, dict) + exclude_host_hooks = from_union([from_bool, from_none], obj.get("excludeHostHooks")) + project_paths = from_union([lambda x: from_list(from_str, x), from_none], obj.get("projectPaths")) + return HooksDiscoverRequest(exclude_host_hooks, project_paths) + + def to_dict(self) -> dict: + result: dict = {} + if self.exclude_host_hooks is not None: + result["excludeHostHooks"] = from_union([from_bool, from_none], self.exclude_host_hooks) + if self.project_paths is not None: + result["projectPaths"] = from_union([lambda x: from_list(from_str, x), from_none], self.project_paths) + return result + +class InstalledPluginSourceURLSource(Enum): GITHUB = "github" LOCAL = "local" URL = "url" -class FluffySource(Enum): +class PurpleSource(Enum): GITHUB = "github" -class TentacledSource(Enum): +class FluffySource(Enum): LOCAL = "local" -class StickySource(Enum): - URL = "url" - # Experimental: this type is part of an experimental API and may change or be removed. class InstructionLocation(Enum): """Which tier this target belongs to @@ -3666,6 +4389,8 @@ class LlmInferenceHTTPResponseStartRequest: """Response head.""" headers: dict[str, list[str]] + """HTTP response headers, preserving multiple values per name.""" + request_id: str """Matches the requestId from the originating httpRequestStart frame.""" @@ -3808,6 +4533,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: @@ -4219,14 +4972,6 @@ def to_dict(self) -> dict: result["redirectPort"] = from_union([from_int, from_none], self.redirect_port) return result -# Experimental: this type is part of an experimental API and may change or be removed. -class MCPServerConfigDeferTools(Enum): - """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) - """ - AUTO = "auto" - NEVER = "never" - # Experimental: this type is part of an experimental API and may change or be removed. class MCPGrantType(Enum): """OAuth grant type to use when authenticating to the remote MCP server. @@ -4240,11 +4985,40 @@ class MCPGrantType(Enum): CLIENT_CREDENTIALS = "client_credentials" # Experimental: this type is part of an experimental API and may change or be removed. -class MCPServerConfigHTTPType(Enum): - """Remote transport type. Defaults to "http" when omitted.""" +@dataclass +class MCPSafeForTelemetryFields: + """Per-field MCP telemetry-obfuscation policy.""" + + inputs_names: bool + """Whether MCP tool input names may be included in telemetry without obfuscation.""" + + name: bool + """Whether the MCP tool name may be included in telemetry without obfuscation.""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPSafeForTelemetryFields': + assert isinstance(obj, dict) + inputs_names = from_bool(obj.get("inputsNames")) + name = from_bool(obj.get("name")) + return MCPSafeForTelemetryFields(inputs_names, name) + + def to_dict(self) -> dict: + result: dict = {} + result["inputsNames"] = from_bool(self.inputs_names) + result["name"] = from_bool(self.name) + return result + +class MCPSerializableServerConfigType(Enum): + """Local transport type. Defaults to stdio when omitted. + + Local MCP transport type. + Remote transport type. Defaults to "http" when omitted. + """ HTTP = "http" + LOCAL = "local" SSE = "sse" + STDIO = "stdio" # Experimental: this type is part of an experimental API and may change or be removed. @dataclass @@ -4312,7 +5086,7 @@ def to_dict(self) -> dict: # Internal: this type is an internal SDK API and is not part of the public surface. @dataclass class MCPConfigureGitHubRequest: - """Opaque auth info used to configure GitHub MCP.""" + """Credential-free authentication identity used to configure GitHub MCP.""" auth_info: Any = None """Opaque runtime auth info for GitHub MCP configuration. Marked internal: an in-process @@ -4388,6 +5162,14 @@ 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. +class MCPElicitationFormMode(Enum): + """Structured MCP elicitation mode. + + Elicitation mode. Omitted and form are equivalent for structured elicitation. + """ + FORM = "form" + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MCPEnableRequest: @@ -4407,6 +5189,31 @@ def to_dict(self) -> dict: result["serverName"] = from_str(self.server_name) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MCPFailedServer: + """MCP server whose connection attempt failed.""" + + name: str + """The config key of the server that failed to connect.""" + + error: str | None = None + """The captured connection failure detail.""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPFailedServer': + assert isinstance(obj, dict) + name = from_str(obj.get("name")) + error = from_union([from_str, from_none], obj.get("error")) + return MCPFailedServer(name, error) + + def to_dict(self) -> dict: + result: dict = {} + result["name"] = from_str(self.name) + 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. @dataclass class MCPFilteredServer: @@ -4511,6 +5318,164 @@ def to_dict(self) -> dict: result["timestamp"] = from_int(self.timestamp) return result +# Experimental: this type is part of an experimental API and may change or be removed. +class MCPPlanConfigurationOperation(Enum): + """Whether the change would create a new entry or modify an existing one. + + Whether a planned configuration change would create or modify an entry + """ + ADD = "add" + UPDATE = "update" + +# Experimental: this type is part of an experimental API and may change or be removed. +class MCPPlanScope(Enum): + """Scope the change would be written to. + + Configuration scope an MCP install plan targets + + Configuration scope the plan targets. + + Configuration scope the plan targets. Defaults to user scope when omitted. + """ + USER = "user" + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MCPPlanResourceIdentity: + """Normalised identity of the server the plan would install. + + Normalised identity of the MCP server a plan targets, independent of how the card spelled + it. + """ + canonical_name: str + """Canonical, normalised name of the server, for example `io.github.owner/server`.""" + + server_name: str + """Local configuration key the server would be recorded under.""" + + registry_id: str | None = None + """Registry identifier of the server, when it came from a registry.""" + + version: str | None = None + """Version advertised by the card, when it declares one.""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPPlanResourceIdentity': + assert isinstance(obj, dict) + canonical_name = from_str(obj.get("canonicalName")) + server_name = from_str(obj.get("serverName")) + registry_id = from_union([from_str, from_none], obj.get("registryId")) + version = from_union([from_str, from_none], obj.get("version")) + return MCPPlanResourceIdentity(canonical_name, server_name, registry_id, version) + + def to_dict(self) -> dict: + result: dict = {} + result["canonicalName"] = from_str(self.canonical_name) + result["serverName"] = from_str(self.server_name) + if self.registry_id is not None: + result["registryId"] = from_union([from_str, from_none], self.registry_id) + if self.version is not None: + result["version"] = from_union([from_str, from_none], self.version) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +class MCPPlanPolicyDecision(Enum): + """What policy decided for this server. + + What policy decided for a planned server + """ + ALLOWED = "allowed" + BLOCKED = "blocked" + REQUIRES_APPROVAL = "requires-approval" + +class InstallMethod(Enum): + """Discriminator for a package-backed transport choice + + Discriminator for a remote-endpoint transport choice + """ + PACKAGE = "package" + REMOTE = "remote" + +# Experimental: this type is part of an experimental API and may change or be removed. +class MCPPlanValueCategory(Enum): + """Where the value is applied when the server is launched. + + Where a required value is applied when the planned server is launched + """ + ENVIRONMENT_VARIABLE = "environment-variable" + HEADER = "header" + PACKAGE_ARGUMENT = "package-argument" + RUNTIME_ARGUMENT = "runtime-argument" + URL_VARIABLE = "url-variable" + +class MCPPlanRequiredValueKind(Enum): + """Discriminator for a scalar required value + + Discriminator for an enumerated required value + """ + ENUM = "enum" + SCALAR = "scalar" + +class MCPPlanRequiredValueValueType(Enum): + """Scalar type the value must conform to. + + Scalar type a required value must conform to + + Discriminator for an enumerated required value + """ + BOOLEAN = "boolean" + ENUM = "enum" + NUMBER = "number" + PATH = "path" + STRING = "string" + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MCPPlanSecretPlaceholder: + """A secret a transport choice needs, referenced by placeholder. No secret value ever + appears in a plan, and the placeholder resolves against the keychain only when a plan is + applied. + """ + key: str + """Key the secret is supplied under. Inert untrusted data.""" + + placeholder: str + """The runtime-assigned `${secret:}` placeholder written into configuration in place of + the value. + """ + title: str | None = None + """Human-readable label from the card. Inert untrusted text.""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPPlanSecretPlaceholder': + assert isinstance(obj, dict) + key = from_str(obj.get("key")) + placeholder = from_str(obj.get("placeholder")) + title = from_union([from_str, from_none], obj.get("title")) + return MCPPlanSecretPlaceholder(key, placeholder, title) + + def to_dict(self) -> dict: + result: dict = {} + result["key"] = from_str(self.key) + result["placeholder"] = from_str(self.placeholder) + if self.title is not None: + result["title"] = from_union([from_str, from_none], self.title) + return result + +class MCPPlanETransport(Enum): + """Local process transport this package choice would use. + + Transport exposed by a locally launched package + + Endpoint transport this remote choice would use. + + Transport exposed by a remote endpoint + """ + HTTP = "http" + SSE = "sse" + STDIO = "stdio" + STREAMABLE_HTTP = "streamable-http" + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MCPIsServerRunningRequest: @@ -4604,10 +5569,6 @@ def to_dict(self) -> dict: result["serverName"] = from_union([from_str, from_none], self.server_name) return result -class MCPOauthPendingRequestResponseKind(Enum): - CANCELLED = "cancelled" - TOKEN = "token" - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MCPOauthHandlePendingResult: @@ -4655,6 +5616,41 @@ def to_dict(self) -> dict: result["authorizationUrl"] = from_union([from_str, from_none], self.authorization_url) return result +# Experimental: this type is part of an experimental API and may change or be removed. +class MCPOauthProbeNeedsAuthReason(Enum): + """Why a passive MCP OAuth probe determined authentication is needed. + + Why authentication is needed. + """ + INITIAL = "initial" + REFRESH = "refresh" + UPSCOPE = "upscope" + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MCPOauthProbeRequest: + """Remote MCP server name for a passive OAuth status probe.""" + + server_name: str + """Name of the configured remote MCP server to probe.""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPOauthProbeRequest': + assert isinstance(obj, dict) + server_name = from_str(obj.get("serverName")) + return MCPOauthProbeRequest(server_name) + + def to_dict(self) -> dict: + result: dict = {} + result["serverName"] = from_str(self.server_name) + return result + +class Status(Enum): + AUTHENTICATED = "authenticated" + FAILED = "failed" + NEEDS_AUTH = "needs-auth" + NO_AUTH_REQUIRED = "no-auth-required" + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MCPOauthRespondRequest: @@ -4695,6 +5691,202 @@ 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. +class MCPPlan(Enum): + """Discriminator for an enumerated required value""" + + ENUM = "enum" + +class MCPPlanInstallPlannedKind(Enum): + PLANNED = "planned" + +class MCPPlanInstallSourceKind(Enum): + """Discriminator for a candidate-backed install-plan source + + Discriminator for a caller-supplied-card install-plan source + """ + CANDIDATE = "candidate" + CARD = "card" + +class MCPPlanInstallResultKind(Enum): + AUTHENTICATION_REQUIRED = "authentication-required" + CONTRACT_VIOLATION = "contract-violation" + HANDLE_REJECTED = "handle-rejected" + INVALID_REQUEST = "invalid-request" + MALFORMED_CARD = "malformed-card" + NEGOTIATION_REFUSED = "negotiation-refused" + NETWORK_FAILURE = "network-failure" + NOT_INSTALLABLE = "not-installable" + PLANNED = "planned" + POLICY_REJECTED = "policy-rejected" + UNAVAILABLE = "unavailable" + UNAVAILABLE_TRANSPORT = "unavailable-transport" + UNSAFE_RETRIEVAL = "unsafe-retrieval" + +class MCPPlanInstallResultReason(Enum): + """Whether the version or the capability set was the problem. + + Why capability and protocol-version negotiation refused a caller + + Why the handle was rejected. + + Why a presented handle was rejected + + Why authentication failed. Only an expired credential justifies attempting a silent + refresh; an absent or rejected credential requires sign-in. + + Why the catalog authority did not accept the caller's identity + + Categorised failure, low cardinality so it can be aggregated without carrying a URL. + + Categorised network failure, low cardinality so it can be aggregated without carrying a + URL + + Which control refused the retrieval, low cardinality so it can be aggregated without + carrying a URL. + + Which hardened-fetch control refused a retrieval + + How the card failed validation. + + How a card failed validation + + Which rule the response broke. + + Which wire-contract rule an upstream response broke + + Why no transport could be offered. + + Why no usable transport could be offered + + Why the candidate cannot be installed. + + Why a discoverable candidate cannot be installed + + Why the operation is unavailable. + + Why a catalog operation is not available on this runtime + """ + AI_SKILL_NOT_INSTALLABLE = "ai-skill-not-installable" + AUTHORITY_NOT_CONFIGURED = "authority-not-configured" + BLOCKED_ADDRESS = "blocked-address" + BLOCKED_SCHEME = "blocked-scheme" + BOTH_URL_AND_DATA = "both-url-and-data" + CONNECTION_REFUSED = "connection-refused" + CREDENTIALS_IN_URL = "credentials-in-url" + CREDENTIAL_EXPIRED = "credential-expired" + CREDENTIAL_REJECTED = "credential-rejected" + DISABLED_BY_POLICY = "disabled-by-policy" + DNS = "dns" + DUPLICATE_IDENTITY = "duplicate-identity" + FOREIGN = "foreign" + HOST_NOT_PERMITTED = "host-not-permitted" + HTTP_STATUS = "http-status" + INVALID = "invalid" + INVALID_JSON = "invalid-json" + KIND_NOT_INSTALLABLE = "kind-not-installable" + MISSING_REQUIRED_FIELD = "missing-required-field" + NEITHER_URL_NOR_DATA = "neither-url-nor-data" + NO_CREDENTIAL = "no-credential" + NO_ELIGIBLE_TRANSPORT = "no-eligible-transport" + OFFLINE = "offline" + PLANNING_UNAVAILABLE = "planning-unavailable" + POLICY_FORBIDS = "policy-forbids" + PROXY_AUTHENTICATION_REQUIRED = "proxy-authentication-required" + PROXY_REJECTED = "proxy-rejected" + RATE_LIMITED = "rate-limited" + REDIRECT_REJECTED = "redirect-rejected" + REDIRECT_TO_BLOCKED_ADDRESS = "redirect-to-blocked-address" + REMOTE_ENUMERATION_UNAVAILABLE = "remote-enumeration-unavailable" + REPLAYED = "replayed" + RESPONSE_TOO_LARGE = "response-too-large" + SCHEMA_VIOLATION = "schema-violation" + SEARCH_UNAVAILABLE = "search-unavailable" + SERVICE_UNAVAILABLE = "service-unavailable" + SIZE_LIMIT_EXCEEDED = "size-limit-exceeded" + STALE = "stale" + TIMEOUT = "timeout" + TLS = "tls" + TRANSPORT_NOT_SUPPORTED = "transport-not-supported" + UNKNOWN_MEDIA_TYPE = "unknown-media-type" + UNSUPPORTED_CAPABILITY = "unsupported-capability" + UNSUPPORTED_MEDIA_TYPE = "unsupported-media-type" + UNSUPPORTED_PROTOCOL_VERSION = "unsupported-protocol-version" + +# Experimental: this type is part of an experimental API and may change or be removed. +class MCPPlanInstallSourceCandidateKind(Enum): + """Discriminator for a candidate-backed install-plan source""" + + CANDIDATE = "candidate" + +# Experimental: this type is part of an experimental API and may change or be removed. +class MCPPlanInstallSourceCardKind(Enum): + """Discriminator for a caller-supplied-card install-plan source""" + + CARD = "card" + +# Experimental: this type is part of an experimental API and may change or be removed. +class MCPPlanPackageInstallMethod(Enum): + """Discriminator for a package-backed transport choice""" + + PACKAGE = "package" + +# Experimental: this type is part of an experimental API and may change or be removed. +class MCPPlanPackageTransport(Enum): + """Local process transport this package choice would use. + + Transport exposed by a locally launched package + """ + STDIO = "stdio" + +# Experimental: this type is part of an experimental API and may change or be removed. +class MCPPlanRemoteInstallMethod(Enum): + """Discriminator for a remote-endpoint transport choice""" + + REMOTE = "remote" + +# Experimental: this type is part of an experimental API and may change or be removed. +class MCPPlanRemoteTransport(Enum): + """Endpoint transport this remote choice would use. + + Transport exposed by a remote endpoint + """ + HTTP = "http" + SSE = "sse" + STREAMABLE_HTTP = "streamable-http" + +# Experimental: this type is part of an experimental API and may change or be removed. +class MCPPlanRequiredValueScalarKind(Enum): + """Discriminator for a scalar required value""" + + SCALAR = "scalar" + +# Experimental: this type is part of an experimental API and may change or be removed. +class MCPPlanScalarValueTypeEnum(Enum): + """Scalar type the value must conform to. + + Scalar type a required value must conform to + """ + BOOLEAN = "boolean" + NUMBER = "number" + PATH = "path" + STRING = "string" + +class MCPServerConfigType(Enum): + """Local transport type. Defaults to stdio when omitted. + + Local MCP transport type. + + Remote transport type. Defaults to "http" when omitted. + + In-process MCP transport type. + """ + HTTP = "http" + LOCAL = "local" + MEMORY = "memory" + SSE = "sse" + STDIO = "stdio" + # 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 @@ -4868,6 +6060,29 @@ class MCPSamplingExecutionAction(Enum): FAILURE = "failure" SUCCESS = "success" +# Experimental: this type is part of an experimental API and may change or be removed. +class MCPServerConfigHTTPType(Enum): + """Remote transport type. Defaults to "http" when omitted.""" + + HTTP = "http" + SSE = "sse" + +# 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. +class MCPServerConfigMemoryType(Enum): + """In-process MCP transport type.""" + + MEMORY = "memory" + +# Experimental: this type is part of an experimental API and may change or be removed. +class MCPServerConfigStdioType(Enum): + """Local transport type. Defaults to stdio when omitted. + + Local MCP transport type. + """ + LOCAL = "local" + STDIO = "stdio" + # Experimental: this type is part of an experimental API and may change or be removed. class MCPSetEnvValueModeDetails(Enum): """How environment-variable values supplied to MCP servers are resolved. "direct" passes @@ -4905,6 +6120,28 @@ def to_dict(self) -> dict: result["serverName"] = from_str(self.server_name) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MCPTaskMetadata: + """Metadata controlling an MCP task's lifetime. + + MCP task metadata. + """ + ttl: int | None = None + """Task time-to-live.""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPTaskMetadata': + assert isinstance(obj, dict) + ttl = from_union([from_int, from_none], obj.get("ttl")) + return MCPTaskMetadata(ttl) + + def to_dict(self) -> dict: + result: dict = {} + if self.ttl is not None: + result["ttl"] = from_union([from_int, from_none], self.ttl) + 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 @@ -4944,6 +6181,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.""" @@ -5281,21 +6569,67 @@ class TaskType(Enum): # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class ModeSetRequest: - """Agent interaction mode to apply to the session.""" +class ModelPickerSettingsContext: + """Filesystem and environment context used to resolve settings persistence. - mode: SessionMode - """The session mode the agent is operating in""" + Filesystem and environment context used to resolve model-picker settings. + + Settings context used when persisting the selected plan model. + """ + environment: dict[str, Any] + """Environment variables consulted while resolving model-picker settings.""" + + home_directory: str + """User home directory used when resolving persisted settings.""" + + config_dir: str | None = None + """Optional Copilot configuration directory containing persisted settings.""" @staticmethod - def from_dict(obj: Any) -> 'ModeSetRequest': + def from_dict(obj: Any) -> 'ModelPickerSettingsContext': assert isinstance(obj, dict) - mode = SessionMode(obj.get("mode")) - return ModeSetRequest(mode) + environment = from_dict(lambda x: x, obj.get("environment")) + home_directory = from_str(obj.get("homeDirectory")) + config_dir = from_union([from_str, from_none], obj.get("configDir")) + return ModelPickerSettingsContext(environment, home_directory, config_dir) def to_dict(self) -> dict: result: dict = {} - result["mode"] = to_enum(SessionMode, self.mode) + result["environment"] = from_dict(lambda x: x, self.environment) + result["homeDirectory"] = from_str(self.home_directory) + if self.config_dir is not None: + result["configDir"] = from_union([from_str, from_none], self.config_dir) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ModelSwitchConfirmation: + """Compaction confirmation projection when status is confirmation_required + + Compaction confirmation required before the mode change can complete. + """ + current_tokens: float + """Current conversation token count before switching models.""" + + target_limit: float + """Target model token limit used by the compaction preflight.""" + + target_model_display_name: str + """Display name of the model that requires compaction confirmation.""" + + @staticmethod + def from_dict(obj: Any) -> 'ModelSwitchConfirmation': + assert isinstance(obj, dict) + current_tokens = from_float(obj.get("currentTokens")) + target_limit = from_float(obj.get("targetLimit")) + target_model_display_name = from_str(obj.get("targetModelDisplayName")) + return ModelSwitchConfirmation(current_tokens, target_limit, target_model_display_name) + + def to_dict(self) -> dict: + result: dict = {} + result["currentTokens"] = to_float(self.current_tokens) + result["targetLimit"] = to_float(self.target_limit) + result["targetModelDisplayName"] = from_str(self.target_model_display_name) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -5321,6 +6655,13 @@ class ModelBillingPromo: """Human-readable promotion message. Does not include the expiry timestamp; consumers may format endsAt and append it when present. """ + show_banner: bool | None = None + """Whether the service asked hosts to give this promotion a prominent surface, such as a + dedicated banner, in addition to listing it with the model. `true` requests that surface + and `false` asks for the model list only. Absent means the service expressed no + preference — for example a response that predates the field — so hosts should apply their + own default rather than read it as `false`. + """ @staticmethod def from_dict(obj: Any) -> 'ModelBillingPromo': @@ -5329,7 +6670,8 @@ def from_dict(obj: Any) -> 'ModelBillingPromo': 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(discount_percent, ends_at, id, message) + show_banner = from_union([from_bool, from_none], obj.get("showBanner")) + return ModelBillingPromo(discount_percent, ends_at, id, message, show_banner) def to_dict(self) -> dict: result: dict = {} @@ -5341,6 +6683,8 @@ def to_dict(self) -> dict: result["id"] = from_union([from_str, from_none], self.id) if self.message is not None: result["message"] = from_union([from_str, from_none], self.message) + if self.show_banner is not None: + result["showBanner"] = from_union([from_bool, from_none], self.show_banner) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -5354,6 +6698,9 @@ class ModelBillingTokenPricesLongContext: cache_read_price: float | None = None """AI Credits cost per billing batch of cached (read) tokens""" + cache_write1_h_price: float | None = None + """AI Credits cost per billing batch of 1-hour cache-write (cache creation) tokens.""" + cache_write_price: float | None = None """AI Credits cost per billing batch of cache-write (cache creation) tokens.""" @@ -5376,12 +6723,13 @@ def from_dict(obj: Any) -> 'ModelBillingTokenPricesLongContext': assert isinstance(obj, dict) cache_price = from_union([from_float, from_none], obj.get("cachePrice")) cache_read_price = from_union([from_float, from_none], obj.get("cacheReadPrice")) + cache_write1_h_price = from_union([from_float, from_none], obj.get("cacheWrite1hPrice")) cache_write_price = from_union([from_float, from_none], obj.get("cacheWritePrice")) context_max = from_union([from_int, from_none], obj.get("contextMax")) input_price = from_union([from_float, from_none], obj.get("inputPrice")) max_prompt_tokens = from_union([from_int, from_none], obj.get("maxPromptTokens")) output_price = from_union([from_float, from_none], obj.get("outputPrice")) - return ModelBillingTokenPricesLongContext(cache_price, cache_read_price, cache_write_price, context_max, input_price, max_prompt_tokens, output_price) + return ModelBillingTokenPricesLongContext(cache_price, cache_read_price, cache_write1_h_price, cache_write_price, context_max, input_price, max_prompt_tokens, output_price) def to_dict(self) -> dict: result: dict = {} @@ -5389,6 +6737,8 @@ def to_dict(self) -> dict: result["cachePrice"] = from_union([to_float, from_none], self.cache_price) if self.cache_read_price is not None: result["cacheReadPrice"] = from_union([to_float, from_none], self.cache_read_price) + if self.cache_write1_h_price is not None: + result["cacheWrite1hPrice"] = from_union([to_float, from_none], self.cache_write1_h_price) if self.cache_write_price is not None: result["cacheWritePrice"] = from_union([to_float, from_none], self.cache_write_price) if self.context_max is not None: @@ -5430,10 +6780,39 @@ def to_dict(self) -> dict: result["supported_media_types"] = from_list(from_str, self.supported_media_types) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ModelMessage: + """A service-published message about a model, carrying a stable machine-readable code + alongside human-readable text. + """ + code: str + """Stable machine-readable identifier for the message, such as `client_version_deprecated`. + Hosts can key custom presentation off this; unrecognized codes should fall back to + displaying `message`. + """ + message: str + """Human-readable message text intended for display to the user.""" + + @staticmethod + def from_dict(obj: Any) -> 'ModelMessage': + assert isinstance(obj, dict) + code = from_str(obj.get("code")) + message = from_str(obj.get("message")) + return ModelMessage(code, message) + + def to_dict(self) -> dict: + result: dict = {} + result["code"] = from_str(self.code) + result["message"] = from_str(self.message) + return result + # Experimental: this type is part of an experimental API and may change or be removed. class ModelPickerPriceCategory(Enum): - """Relative cost tier for token-based billing users""" + """Relative cost tier for token-based billing users + Cost category assigned to the model. + """ HIGH = "high" LOW = "low" MEDIUM = "medium" @@ -5447,6 +6826,31 @@ class ModelPolicyState(Enum): ENABLED = "enabled" UNCONFIGURED = "unconfigured" +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ModelWarningText: + """Warning text the service requires hosts to surface for this model. Present only when the + service published at least one warning. + + Service-published warning text that hosts should display when presenting a model. + """ + data_retention: str | None = None + """Data-retention warning for the model. The text may contain Markdown links and should be + rendered as Markdown when supported. + """ + + @staticmethod + def from_dict(obj: Any) -> 'ModelWarningText': + assert isinstance(obj, dict) + data_retention = from_union([from_str, from_none], obj.get("dataRetention")) + return ModelWarningText(data_retention) + + def to_dict(self) -> dict: + result: dict = {} + if self.data_retention is not None: + result["dataRetention"] = from_union([from_str, from_none], self.data_retention) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ModelCapabilitiesOverrideLimitsVision: @@ -5523,51 +6927,51 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -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. +class ModelsListRequest: + git_hub_token: str | None = None + """GitHub token accepted for compatibility with existing SDK clients. When provided, + resolves this token instead of using the current account. + """ + selection_id: str | None = None + """Opaque account identifier returned by `account.getAllUsers`. When omitted, the current + account is used. """ - model_id: str | None = None - """Currently active model identifier after the switch""" @staticmethod - def from_dict(obj: Any) -> 'ModelSwitchToResult': + def from_dict(obj: Any) -> 'ModelsListRequest': 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(deferred, model_id) + git_hub_token = from_union([from_str, from_none], obj.get("gitHubToken")) + selection_id = from_union([from_str, from_none], obj.get("selectionId")) + return ModelsListRequest(git_hub_token, selection_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) + if self.git_hub_token is not None: + result["gitHubToken"] = from_union([from_str, from_none], self.git_hub_token) + if self.selection_id is not None: + result["selectionId"] = from_union([from_str, from_none], self.selection_id) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class ModelsListRequest: - git_hub_token: str | None = None - """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. +class MoveMCPLoadingToBackgroundResult: + """Result of moving in-flight MCP loading to the background.""" + + moved_to_background: bool + """Whether an in-flight MCP load was moved to the background, releasing turns that were + waiting on it. False when no MCP load was in flight or the waiting turns had already been + released. """ @staticmethod - def from_dict(obj: Any) -> 'ModelsListRequest': + def from_dict(obj: Any) -> 'MoveMCPLoadingToBackgroundResult': assert isinstance(obj, dict) - git_hub_token = from_union([from_str, from_none], obj.get("gitHubToken")) - return ModelsListRequest(git_hub_token) + moved_to_background = from_bool(obj.get("movedToBackground")) + return MoveMCPLoadingToBackgroundResult(moved_to_background) def to_dict(self) -> dict: result: dict = {} - if self.git_hub_token is not None: - result["gitHubToken"] = from_union([from_str, from_none], self.git_hub_token) + result["movedToBackground"] = from_bool(self.moved_to_background) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -5654,8 +7058,10 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ProviderConfigAzure: - """Azure-specific provider options.""" + """Azure authentication configuration for the provider. + Azure-specific provider options. + """ api_version: str | None = None """API version. When set, uses the versioned deployment route. When omitted, uses the GA versionless v1 route. @@ -5675,7 +7081,9 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. class ProviderTransport(Enum): - """Provider transport. Defaults to "http". + """Transport used to communicate with the provider. + + Provider transport. Defaults to "http". Transport to be used for provider requests. """ @@ -5684,7 +7092,9 @@ class ProviderTransport(Enum): # Experimental: this type is part of an experimental API and may change or be removed. class ProviderType(Enum): - """Provider type. Defaults to "openai" for generic OpenAI-compatible APIs. + """Provider protocol family. + + Provider type. Defaults to "openai" for generic OpenAI-compatible APIs. Provider family. Matches the `type` field of a BYOK provider config. """ @@ -5694,7 +7104,9 @@ class ProviderType(Enum): # Experimental: this type is part of an experimental API and may change or be removed. class ProviderWireAPI(Enum): - """Wire API format (openai/azure only). Defaults to "completions". + """Wire API used to communicate with the provider. + + Wire API format (openai/azure only). Defaults to "completions". Wire API to be used, when required for the provider type. """ @@ -5708,7 +7120,10 @@ class OptionsUpdateAdditionalContentExclusionPolicyRuleSource: and type. """ name: str + """Name of the policy source.""" + type: str + """Type of the policy source.""" @staticmethod def from_dict(obj: Any) -> 'OptionsUpdateAdditionalContentExclusionPolicyRuleSource': @@ -5782,6 +7197,7 @@ def to_dict(self) -> dict: class ApprovalKind(Enum): COMMANDS = "commands" CUSTOM_TOOL = "custom-tool" + EXTENSION_ENV_ACCESS = "extension-env-access" EXTENSION_MANAGEMENT = "extension-management" EXTENSION_PERMISSION_ACCESS = "extension-permission-access" FACTORY = "factory" @@ -5817,6 +7233,9 @@ class PermissionDecisionApproveForLocationApprovalCommandsKind(Enum): class PermissionDecisionApproveForLocationApprovalCustomToolKind(Enum): CUSTOM_TOOL = "custom-tool" +class PermissionDecisionApproveForLocationApprovalExtensionEnvAccessKind(Enum): + EXTENSION_ENV_ACCESS = "extension-env-access" + class PermissionDecisionApproveForLocationApprovalExtensionManagementKind(Enum): EXTENSION_MANAGEMENT = "extension-management" @@ -5832,9 +7251,6 @@ class PermissionDecisionApproveForLocationApprovalMCPKind(Enum): class PermissionDecisionApproveForLocationApprovalMCPSamplingKind(Enum): MCP_SAMPLING = "mcp-sampling" -class PermissionDecisionApproveForLocationApprovalMemoryKind(Enum): - MEMORY = "memory" - class PermissionDecisionApproveForLocationApprovalReadKind(Enum): READ = "read" @@ -5862,6 +7278,50 @@ 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 PermissionResponseCapability(Enum): + """Whether the responding client could ask a user interactively, was running headlessly, or + had no response path. Omit when the client cannot determine this authoritatively. + + Response capability available to the client when it settled a permission request. + """ + HEADLESS = "headless" + INTERACTIVE = "interactive" + NONE = "none" + +# 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. + """ + ASSISTED_APPROVAL = "assisted_approval" + HOST_POLICY = "host_policy" + HUMAN_RESPONSE = "human_response" + 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. + """ + ACP = "acp" + 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" @@ -5880,30 +7340,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" @@ -5959,7 +7395,9 @@ class PermissionPathsAddParams: path: str """Directory to add to the allow-list. The runtime resolves and validates the path before - adding. + adding, then loads conventional `.github/skills/` and `.github/agents/` definitions under + it when their subsystem gates are enabled. Adding the directory is therefore also a trust + decision for configuration stored there. """ @staticmethod @@ -6218,7 +7656,10 @@ class PermissionsConfigureAdditionalContentExclusionPolicyRuleSource: source name and type. """ name: str + """Name of the policy source.""" + type: str + """Type of the policy source.""" @staticmethod def from_dict(obj: Any) -> 'PermissionsConfigureAdditionalContentExclusionPolicyRuleSource': @@ -6273,15 +7714,34 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class PermissionsGetAllowAllRequest: +class PermissionsGetModeRequest: """No parameters.""" @staticmethod - def from_dict(obj: Any) -> 'PermissionsGetAllowAllRequest': + def from_dict(obj: Any) -> 'PermissionsGetModeRequest': + assert isinstance(obj, dict) + return PermissionsGetModeRequest() + + 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 PermissionsGetModeResult: + """Current permission mode.""" + + mode: PermissionMode + """Current permission mode""" + + @staticmethod + def from_dict(obj: Any) -> 'PermissionsGetModeResult': assert isinstance(obj, dict) - return PermissionsGetAllowAllRequest() + mode = PermissionMode(obj.get("mode")) + return PermissionsGetModeResult(mode) def to_dict(self) -> dict: result: dict = {} + result["mode"] = to_enum(PermissionMode, self.mode) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -6471,6 +7931,31 @@ 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 PermissionsSetModeResult: + """Indicates whether the requested permission mode was applied and reports the authoritative + post-mutation mode. + """ + mode: PermissionMode + """Authoritative permission mode after the mutation""" + + success: bool + """Whether the operation succeeded""" + + @staticmethod + def from_dict(obj: Any) -> 'PermissionsSetModeResult': + assert isinstance(obj, dict) + mode = PermissionMode(obj.get("mode")) + success = from_bool(obj.get("success")) + return PermissionsSetModeResult(mode, success) + + def to_dict(self) -> dict: + result: dict = {} + result["mode"] = to_enum(PermissionMode, self.mode) + 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 PermissionsSetRequiredRequest: @@ -6616,6 +8101,12 @@ class PlanSQLTodosRow: """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. """ + created_at: str | None = None + """Todo creation time, as stored by the session SQL schema's `datetime('now')` default: + `YYYY-MM-DD HH:MM:SS` in UTC. Lets clients attribute todos to the work item that created + them (e.g. scoping a goal's progress to the todos it produced) rather than to the whole + session. + """ description: str | None = None """Todo description.""" @@ -6631,14 +8122,17 @@ class PlanSQLTodosRow: @staticmethod def from_dict(obj: Any) -> 'PlanSQLTodosRow': assert isinstance(obj, dict) + created_at = from_union([from_str, from_none], obj.get("createdAt")) description = from_union([from_str, from_none], obj.get("description")) id = from_union([from_str, from_none], obj.get("id")) status = from_union([from_str, from_none], obj.get("status")) title = from_union([from_str, from_none], obj.get("title")) - return PlanSQLTodosRow(description, id, status, title) + return PlanSQLTodosRow(created_at, description, id, status, title) def to_dict(self) -> dict: result: dict = {} + if self.created_at is not None: + result["createdAt"] = from_union([from_str, from_none], self.created_at) if self.description is not None: result["description"] = from_union([from_str, from_none], self.description) if self.id is not None: @@ -6759,6 +8253,27 @@ def to_dict(self) -> dict: result["previousVersion"] = from_union([from_str, from_none], self.previous_version) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class PluginsBuiltinSetRequest: + """Trusted built-in plugin directories to use for this runtime process.""" + + paths: list[str] + """Complete replacement set of trusted built-in plugin directories. Every entry must be an + absolute local filesystem path no longer than 4096 characters. + """ + + @staticmethod + def from_dict(obj: Any) -> 'PluginsBuiltinSetRequest': + assert isinstance(obj, dict) + paths = from_list(from_str, obj.get("paths")) + return PluginsBuiltinSetRequest(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 PluginsMarketplacesAddRequest: @@ -6915,6 +8430,33 @@ def to_dict(self) -> dict: result["model"] = from_union([from_str, from_none], self.model) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ProviderTokenAcquireRequest: + """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. + """ + provider_name: str + """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 the configured provider name. + """ + session_id: str + """Target session identifier""" + + @staticmethod + def from_dict(obj: Any) -> 'ProviderTokenAcquireRequest': + assert isinstance(obj, dict) + provider_name = from_str(obj.get("providerName")) + session_id = from_str(obj.get("sessionId")) + return ProviderTokenAcquireRequest(provider_name, session_id) + + def to_dict(self) -> dict: + result: dict = {} + result["providerName"] = from_str(self.provider_name) + 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 ProviderTokenAcquireResult: @@ -7198,6 +8740,7 @@ class QueueDuplicateAtRequest: """Parameters for duplicating a queued item.""" id: str + """Stable opaque ID of the queued item to duplicate.""" @staticmethod def from_dict(obj: Any) -> 'QueueDuplicateAtRequest': @@ -7426,6 +8969,7 @@ class QueueRemoveAtRequest: """Parameters for removing a queued item by stable id.""" id: str + """Stable opaque ID of the queued item to remove.""" @staticmethod def from_dict(obj: Any) -> 'QueueRemoveAtRequest': @@ -7484,6 +9028,7 @@ class QueueSendNowRequest: """Parameters for steering a queued message into a live turn.""" id: str + """Stable opaque ID of the queued item to steer into the live turn.""" @staticmethod def from_dict(obj: Any) -> 'QueueSendNowRequest': @@ -7526,6 +9071,7 @@ class QueueSetDrainPausedRequest: one that never acquired it. """ paused: bool + """Whether queued-lane draining should be paused.""" @staticmethod def from_dict(obj: Any) -> 'QueueSetDrainPausedRequest': @@ -7544,8 +9090,13 @@ class QueueUpdateTextRequest: """Parameters for editing a single queued message.""" id: str + """Stable opaque ID of the queued item to edit.""" + prompt: str + """Replacement prompt sent to the model.""" + display_prompt: str | None = None + """Optional replacement prompt displayed to the user.""" @staticmethod def from_dict(obj: Any) -> 'QueueUpdateTextRequest': @@ -7693,9 +9244,7 @@ class SessionsRegisterExtensionToolsOnSessionOptions: # 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. - """ + """In-process `() => boolean` gating callback used only by the CLI.""" @staticmethod def from_dict(obj: Any) -> 'SessionsRegisterExtensionToolsOnSessionOptions': @@ -7709,6 +9258,26 @@ def to_dict(self) -> dict: result["enabled"] = self.enabled 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.""" + + unsubscribe: Any + """In-process unsubscribe function used only by the CLI.""" + + @staticmethod + def from_dict(obj: Any) -> '_RegisterExtensionToolsResult': + assert isinstance(obj, dict) + unsubscribe = obj.get("unsubscribe") + return _RegisterExtensionToolsResult(unsubscribe) + + def to_dict(self) -> dict: + result: dict = {} + result["unsubscribe"] = self.unsubscribe + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ReleaseEventInterestParams: @@ -7913,6 +9482,23 @@ def to_dict(self) -> dict: result: dict = {} return result +# Experimental: this type is part of an experimental API and may change or be removed. +class RemoteSessionHostStatus(Enum): + """What a remote host says one of its sessions is doing right now. Deliberately coarse: this + is what a host can report for EVERY session in a catalogue listing, without a client + subscribing to each one. AHP's `SessionSummary.status` is the source today; + `input-needed` covers both a permission prompt and an `ask_user` question, since the + summary does not say which. + + Live status as the owning host reports it in its session listing, so a row for a session + running elsewhere can show that it is running. Absent for hosts that publish no such + status (the cloud task managers), which read as idle. + """ + ERROR = "error" + IDLE = "idle" + INPUT_NEEDED = "input-needed" + WORKING = "working" + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class RemoteSessionMetadataRepository: @@ -7974,6 +9560,42 @@ 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. For the same + capability independent of sandboxing, and matched to the credential's GitHub host, see + `shell.credentials`; the two are additive. + """ + 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: @@ -8035,22 +9657,25 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass 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. + """HTTP proxy for sandboxed process traffic. Linux restricts egress to the proxy endpoint, + requires that endpoint to be reachable over IPv4 (the [::] dual-stack wildcard is + accepted and routed through the IPv4 gateway), and does not support proxy credentials. + macOS relies on applications honoring proxy environment variables. Windows also + configures a per-AppContainer WinHTTP proxy, but enforcement depends on the application's + networking stack. Configure supported credentials in the separate `username` and + `password` fields. A credential-free http:// loopback URL uses the localhost proxy form, + while an https:// or authenticated loopback URL uses the URL form. 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. + scheme's standard port when omitted; an explicit port must be between 1 and 65535. + 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 proxy 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 @@ -8104,6 +9729,52 @@ 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. +# Internal: this type is an internal SDK API and is not part of the public surface. +class _SandboxConfigSource(Enum): + """Origin of the sandbox choice supplied by an internal client. + + Origin of the sandbox choice. The runtime uses this only for internal telemetry + provenance; managed policy is derived independently. + """ + NEVER_CONFIGURED = "never_configured" + REPOSITORY_POLICY = "repository_policy" + SESSION_DISABLED = "session_disabled" + SESSION_FLAG = "session_flag" + UNSUPPORTED_HOST = "unsupported_host" + USER_DISABLED = "user_disabled" + USER_ENABLED = "user_enabled" + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SandboxEnforcementStatus: + """Managed sandbox enforcement state for a session.""" + + blocked: bool + """Whether an enforcement failure has permanently blocked the session.""" + + required: bool + """Whether the effective managed policy requires an available sandbox backend.""" + + reason: str | None = None + """The first sandbox enforcement failure that blocked the session.""" + + @staticmethod + def from_dict(obj: Any) -> 'SandboxEnforcementStatus': + assert isinstance(obj, dict) + blocked = from_bool(obj.get("blocked")) + required = from_bool(obj.get("required")) + reason = from_union([from_str, from_none], obj.get("reason")) + return SandboxEnforcementStatus(blocked, required, reason) + + def to_dict(self) -> dict: + result: dict = {} + result["blocked"] = from_bool(self.blocked) + result["required"] = from_bool(self.required) + if self.reason is not None: + 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 ScheduleAddAtRequest: @@ -8604,6 +10275,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""" @@ -8619,9 +10293,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 = {} @@ -8632,6 +10307,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: @@ -8662,6 +10339,85 @@ def to_dict(self) -> dict: result["hasActiveWork"] = from_bool(self.has_active_work) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionAuthLoginRequest: + """Internal GitHub login parameters.""" + + host: str + """GitHub host URL""" + + login: str + """GitHub login""" + + token: str + """GitHub authentication token""" + + persist: bool | None = None + """Whether to persist the token after login""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionAuthLoginRequest': + assert isinstance(obj, dict) + host = from_str(obj.get("host")) + login = from_str(obj.get("login")) + token = from_str(obj.get("token")) + persist = from_union([from_bool, from_none], obj.get("persist")) + return SessionAuthLoginRequest(host, login, token, persist) + + def to_dict(self) -> dict: + result: dict = {} + result["host"] = from_str(self.host) + result["login"] = from_str(self.login) + result["token"] = from_str(self.token) + if self.persist is not None: + result["persist"] = from_union([from_bool, from_none], self.persist) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionAuthLogoutUserRequest: + """Parameters identifying a GitHub authentication to log out.""" + + auth_info: AuthInfo + """Authentication information to log out""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionAuthLogoutUserRequest': + assert isinstance(obj, dict) + auth_info = _load_AuthInfo(obj.get("authInfo")) + return SessionAuthLogoutUserRequest(auth_info) + + def to_dict(self) -> dict: + result: dict = {} + result["authInfo"] = (self.auth_info).to_dict() + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionAuthSwitchRequest: + """Parameters for switching the session's active authentication.""" + + auth_info: AuthInfo + """Authentication information to activate""" + + token: str | None = None + """Optional token paired with the authentication information""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionAuthSwitchRequest': + assert isinstance(obj, dict) + auth_info = _load_AuthInfo(obj.get("authInfo")) + token = from_union([from_str, from_none], obj.get("token")) + return SessionAuthSwitchRequest(auth_info, token) + + def to_dict(self) -> dict: + result: dict = {} + result["authInfo"] = (self.auth_info).to_dict() + if self.token is not None: + result["token"] = from_union([from_str, from_none], self.token) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionBulkDeleteResult: @@ -9092,8 +10848,10 @@ class SessionFSSqliteQueryType(Enum): # Experimental: this type is part of an experimental API and may change or be removed. class SessionFSSqliteTransactionErrorClass(Enum): - """SQLite transaction failure classification.""" + """Machine-readable classification of the transaction failure. + SQLite transaction failure classification. + """ BUSY_OR_LOCKED = "busyOrLocked" FATAL = "fatal" POST_COMMIT_AMBIGUOUS = "postCommitAmbiguous" @@ -9207,6 +10965,8 @@ class SessionLimitPredictionTier(Enum): """Tier chosen as the recommended cap. Semantic usage tier used for a recommended cap or additional headroom. + + Semantic usage tier. """ ADDITIONAL_HEADROOM = "additional_headroom" GENEROUS_HEADROOM = "generous_headroom" @@ -9318,6 +11078,53 @@ 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 SessionManagedPermissions: + """Enterprise permission policy expressed with the runtime's managed permission-rule + syntax. + + Managed permission policy injected by the SDK host. + """ + 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: str | None = None + """When set to `disable`, prevents bypass/allow-all permission modes. `allow-auto-only` + blocks full allow-all but permits advisory auto-approval. Any other value is accepted + rather than failing the session, but is enforced as `disable`: the key is only present to + restrict something, so a mode this runtime cannot interpret fails closed to the most + restrictive one it knows. Omit the key entirely to impose no restriction. + """ + + @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([from_str, 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([from_str, 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 SessionModelListRequest: @@ -9342,7 +11149,10 @@ class SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource: """Source descriptor for a `sessions.open` content-exclusion rule, with source name and type.""" name: str + """Name of the policy source.""" + type: str + """Type of the policy source.""" @staticmethod def from_dict(obj: Any) -> 'SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource': @@ -9357,6 +11167,75 @@ 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. +@dataclass +class ShellCredentials: + """Command-scoped GitHub credential injection for shell commands. + + Command-scoped GitHub credential injection for the shell commands an agent runs. + + Each channel is opt-in and independent, and injection is scoped to the individual command + spawn: the credential is resolved from the session's *current* authentication at every + spawn + and reaches only spawns whose script actually invokes `git` or `gh`. Because nothing is + retained between spawns, replacing the session credential + (`session.gitHubAuth.setCredentials`) + changes what the next spawned command presents — which seeding a credential into the + runtime + process's own environment cannot do, since a child's environment is fixed at `exec`. + + The credential is matched to the host it authenticates to, so a github.com credential is + never + presented to a GitHub Enterprise host and vice versa. Where a channel cannot express that + boundary it injects nothing rather than crossing it -- see `gh` below. + + This is independent of `sandboxConfig`: it is a decision about which identity the agent + presents, not about what the agent may touch, and it works on every platform whether or + not + an OS sandboxing backend is available. `sandboxConfig.auth` remains the sandbox-scoped + spelling and is additive with this one. + """ + gh: bool | None = None + """Whether to authenticate the agent's `gh` commands as the session's GitHub credential, by + exporting `GH_TOKEN` to a spawn that runs `gh`. Any inherited `gh` credential is removed + from + spawns that do not, so the credential stays command-scoped. + + Applies to a github.com credential only. `gh` picks its credential variable from the host + a + command targets rather than the one the credential belongs to, and the command can choose + that + target, so `GH_ENTERPRISE_TOKEN` would offer a single-tenant enterprise credential to + every + other enterprise host. A session whose credential is enterprise-scoped therefore runs + `gh` + unauthenticated; its `git` commands are unaffected, because `http..extraheader` is + scoped + to one host by construction. Default: false (opt-in). + """ + git: bool | None = None + """Whether to authenticate the agent's `git` commands as the session's GitHub credential, by + injecting an `http..extraheader` (plus `insteadOf` rewrites so SSH-spelled remotes + for + that host use the authenticated HTTPS transport). Applied only to a spawn that runs a + remote-contacting `git` subcommand. Default: false (opt-in). + """ + + @staticmethod + def from_dict(obj: Any) -> 'ShellCredentials': + 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 ShellCredentials(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. class ShellInitProfile(Enum): """Controls automatic non-interactive profile loading where supported. Explicit initScripts @@ -9497,12 +11376,21 @@ def to_dict(self) -> dict: result["skipped"] = from_list(from_str, self.skipped) return result +class SettableAuthInfoType(Enum): + API_KEY = "api-key" + COPILOT_API_TOKEN = "copilot-api-token" + ENV = "env" + GH_CLI = "gh-cli" + HMAC = "hmac" + TOKEN = "token" + USER = "user" + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionSetCredentialsParams: """New auth credentials to install on the session. Omit to leave credentials unchanged.""" - credentials: AuthInfo | None = None + credentials: SettableAuthInfo | None = None """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 @@ -9517,7 +11405,7 @@ class SessionSetCredentialsParams: @staticmethod def from_dict(obj: Any) -> 'SessionSetCredentialsParams': assert isinstance(obj, dict) - credentials = from_union([_load_AuthInfo, from_none], obj.get("credentials")) + credentials = from_union([_load_SettableAuthInfo, from_none], obj.get("credentials")) return SessionSetCredentialsParams(credentials) def to_dict(self) -> dict: @@ -9562,10 +11450,15 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionSettingsBuiltInToolAvailabilitySnapshot: - """Availability of built-in job tools surfaced to boundary consumers.""" + """Availability of built-in job tools surfaced to boundary consumers. + Availability of job-specific built-in tools. + """ create_pull_request: bool | None = None + """Whether the create-pull-request tool is available.""" + report_progress: bool | None = None + """Whether the report-progress tool is available.""" @staticmethod def from_dict(obj: Any) -> 'SessionSettingsBuiltInToolAvailabilitySnapshot': @@ -9615,6 +11508,7 @@ class SessionSettingsEvaluatePredicateResult: """Result of evaluating a Rust-owned settings predicate.""" enabled: bool + """Whether the named settings predicate evaluated to enabled.""" @staticmethod def from_dict(obj: Any) -> 'SessionSettingsEvaluatePredicateResult': @@ -9630,12 +11524,21 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionSettingsModelSnapshot: - """Redacted model routing settings for a session.""" + """Redacted model routing settings for a session. + Redacted model routing settings. + """ callback_url: str | None = None + """Agent service callback URL for job and progress updates.""" + default_reasoning_effort: str | None = None + """Default reasoning effort for the selected model.""" + instance_id: str | None = None + """Agent job identifier for the session.""" + model: str | None = None + """Selected model identifier.""" @staticmethod def from_dict(obj: Any) -> 'SessionSettingsModelSnapshot': @@ -9661,10 +11564,15 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionSettingsOnlineEvaluationSnapshot: - """Online-evaluation settings safe to expose across the SDK boundary.""" + """Online-evaluation settings safe to expose across the SDK boundary. + Online-evaluation settings safe for SDK consumers. + """ disable_online_evaluation: bool | None = None + """Whether online evaluation is disabled.""" + enable_online_evaluation_output_file: bool | None = None + """Whether online-evaluation output-file generation is enabled.""" @staticmethod def from_dict(obj: Any) -> 'SessionSettingsOnlineEvaluationSnapshot': @@ -9684,20 +11592,45 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionSettingsRepoSnapshot: - """Redacted repository and GitHub host settings for a session.""" + """Redacted repository and GitHub host settings for a session. + Redacted repository and host settings. + """ branch: str | None = None + """Checked-out repository branch.""" + commit: str | None = None + """Checked-out commit SHA.""" + host: str | None = None + """GitHub server host name.""" + host_protocol: str | None = None + """Protocol used to access the GitHub host.""" + id: float | None = None + """GitHub repository database ID.""" + name: str | None = None + """Repository name.""" + owner_id: float | None = None + """GitHub repository owner database ID.""" + owner_name: str | None = None + """Repository owner login.""" + pr_commit_count: float | None = None + """Number of commits in the pull request.""" + read_write: bool | None = None + """Whether the repository is writable.""" + secret_scanning_url: str | None = None + """GitHub secret-scanning service URL.""" + server_url: str | None = None + """GitHub server base URL.""" @staticmethod def from_dict(obj: Any) -> 'SessionSettingsRepoSnapshot': @@ -9744,57 +11677,6 @@ def to_dict(self) -> dict: result["serverUrl"] = from_union([from_str, from_none], self.server_url) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class SessionSettingsValidationSnapshot: - """Redacted validation and memory-tool settings for a session.""" - - advisory_enabled: bool | None = None - codeql_enabled: bool | None = None - code_review_enabled: bool | None = None - code_review_model: str | None = None - dependabot_timeout: float | None = None - memory_store_enabled: bool | None = None - memory_vote_enabled: bool | None = None - secret_scanning_enabled: bool | None = None - timeout: float | None = None - - @staticmethod - def from_dict(obj: Any) -> 'SessionSettingsValidationSnapshot': - assert isinstance(obj, dict) - advisory_enabled = from_union([from_bool, from_none], obj.get("advisoryEnabled")) - codeql_enabled = from_union([from_bool, from_none], obj.get("codeqlEnabled")) - code_review_enabled = from_union([from_bool, from_none], obj.get("codeReviewEnabled")) - code_review_model = from_union([from_str, from_none], obj.get("codeReviewModel")) - dependabot_timeout = from_union([from_float, from_none], obj.get("dependabotTimeout")) - memory_store_enabled = from_union([from_bool, from_none], obj.get("memoryStoreEnabled")) - memory_vote_enabled = from_union([from_bool, from_none], obj.get("memoryVoteEnabled")) - secret_scanning_enabled = from_union([from_bool, from_none], obj.get("secretScanningEnabled")) - timeout = from_union([from_float, from_none], obj.get("timeout")) - return SessionSettingsValidationSnapshot(advisory_enabled, codeql_enabled, code_review_enabled, code_review_model, dependabot_timeout, memory_store_enabled, memory_vote_enabled, secret_scanning_enabled, timeout) - - def to_dict(self) -> dict: - result: dict = {} - if self.advisory_enabled is not None: - result["advisoryEnabled"] = from_union([from_bool, from_none], self.advisory_enabled) - if self.codeql_enabled is not None: - result["codeqlEnabled"] = from_union([from_bool, from_none], self.codeql_enabled) - if self.code_review_enabled is not None: - result["codeReviewEnabled"] = from_union([from_bool, from_none], self.code_review_enabled) - if self.code_review_model is not None: - result["codeReviewModel"] = from_union([from_str, from_none], self.code_review_model) - if self.dependabot_timeout is not None: - result["dependabotTimeout"] = from_union([to_float, from_none], self.dependabot_timeout) - if self.memory_store_enabled is not None: - result["memoryStoreEnabled"] = from_union([from_bool, from_none], self.memory_store_enabled) - if self.memory_vote_enabled is not None: - result["memoryVoteEnabled"] = from_union([from_bool, from_none], self.memory_vote_enabled) - if self.secret_scanning_enabled is not None: - result["secretScanningEnabled"] = from_union([from_bool, from_none], self.secret_scanning_enabled) - if self.timeout is not None: - result["timeout"] = from_union([to_float, from_none], self.timeout) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionSizes: @@ -10371,9 +12253,6 @@ class SessionsOpenCreateKind(Enum): class SessionsOpenHandoffKind(Enum): HANDOFF = "handoff" -class SessionsOpenRemoteKind(Enum): - REMOTE = "remote" - class SessionsOpenResumeKind(Enum): RESUME = "resume" @@ -10620,6 +12499,9 @@ def to_dict(self) -> dict: result["expectedFromSessionId"] = from_union([from_str, from_none], self.expected_from_session_id) return result +class SettableTokenAuthInfoType(Enum): + TOKEN = "token" + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ShellCancelUserRequestedRequest: @@ -10794,6 +12676,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""" @@ -10809,9 +12694,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 = {} @@ -10822,6 +12708,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: @@ -10837,6 +12725,139 @@ class SkillDiscoveryScope(Enum): PERSONAL_COPILOT = "personal-copilot" PROJECT = "project" +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SkillProviderDescriptor: + """Catalog-only metadata for one SDK-provided skill. The complete SKILL.md is fetched + separately and lazily. + """ + description: str + """Description used in skill catalogs without fetching content.""" + + name: str + """Invocation and display name.""" + + argument_hint: str | None = None + """Optional freeform argument hint used by slash-command catalogs.""" + + disable_model_invocation: bool | None = None + """Whether model invocation is disabled. Defaults to false.""" + + user_invocable: bool | None = None + """Whether users may invoke the skill directly. Defaults to true.""" + + @staticmethod + def from_dict(obj: Any) -> 'SkillProviderDescriptor': + assert isinstance(obj, dict) + description = from_str(obj.get("description")) + name = from_str(obj.get("name")) + argument_hint = from_union([from_str, from_none], obj.get("argumentHint")) + disable_model_invocation = from_union([from_bool, from_none], obj.get("disableModelInvocation")) + user_invocable = from_union([from_bool, from_none], obj.get("userInvocable")) + return SkillProviderDescriptor(description, name, argument_hint, disable_model_invocation, user_invocable) + + def to_dict(self) -> dict: + result: dict = {} + result["description"] = from_str(self.description) + result["name"] = from_str(self.name) + if self.argument_hint is not None: + result["argumentHint"] = from_union([from_str, from_none], self.argument_hint) + if self.disable_model_invocation is not None: + result["disableModelInvocation"] = from_union([from_bool, from_none], self.disable_model_invocation) + if self.user_invocable is not None: + result["userInvocable"] = from_union([from_bool, from_none], self.user_invocable) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SkillProviderListRequest: + """Identifies the target session.""" + + session_id: str + """Target session identifier""" + + @staticmethod + def from_dict(obj: Any) -> 'SkillProviderListRequest': + assert isinstance(obj, dict) + session_id = from_str(obj.get("sessionId")) + return SkillProviderListRequest(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. +# Internal: this type is an internal SDK API and is not part of the public surface. +@dataclass +class _SkillProviderReadRequest: + """Identifies one SDK-provided skill by invocation name.""" + + name: str + """Invocation name of the skill to read.""" + + session_id: str + """Target session identifier""" + + @staticmethod + def from_dict(obj: Any) -> '_SkillProviderReadRequest': + assert isinstance(obj, dict) + name = from_str(obj.get("name")) + session_id = from_str(obj.get("sessionId")) + return _SkillProviderReadRequest(name, session_id) + + def to_dict(self) -> dict: + result: dict = {} + result["name"] = from_str(self.name) + result["sessionId"] = from_str(self.session_id) + 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 _SkillProviderReadResult: + """Complete text-only SKILL.md content returned by an SDK session's skill provider. Related + files and assets are not supported. + """ + markdown: str + """Complete SKILL.md text. The runtime enforces a 1 MiB UTF-8 byte limit.""" + + @staticmethod + def from_dict(obj: Any) -> '_SkillProviderReadResult': + assert isinstance(obj, dict) + markdown = from_str(obj.get("markdown")) + return _SkillProviderReadResult(markdown) + + def to_dict(self) -> dict: + result: dict = {} + result["markdown"] = from_str(self.markdown) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SkillsConfigSetSkillDisabledRequest: + """Adds or removes a single skill from the global disabled list, leaving every other entry + untouched. + """ + disabled: bool + """True to disable the skill, false to enable it""" + + name: str + """Name of the skill to add to or remove from the disabled list""" + + @staticmethod + def from_dict(obj: Any) -> 'SkillsConfigSetSkillDisabledRequest': + assert isinstance(obj, dict) + disabled = from_bool(obj.get("disabled")) + name = from_str(obj.get("name")) + return SkillsConfigSetSkillDisabledRequest(disabled, name) + + def to_dict(self) -> dict: + result: dict = {} + result["disabled"] = from_bool(self.disabled) + result["name"] = from_str(self.name) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SkillsDisableRequest: @@ -10961,16 +12982,56 @@ def to_dict(self) -> dict: result["warnings"] = from_list(from_str, self.warnings) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SlashCommandTimelineEntry: + """Timeline entry the host should append.""" + + text: str + """Text displayed for the timeline entry.""" + + type: str + """Timeline entry presentation type.""" + + url: str | None = None + """Optional URL associated with the timeline entry.""" + + @staticmethod + def from_dict(obj: Any) -> 'SlashCommandTimelineEntry': + assert isinstance(obj, dict) + text = from_str(obj.get("text")) + type = from_str(obj.get("type")) + url = from_union([from_str, from_none], obj.get("url")) + return SlashCommandTimelineEntry(text, type, url) + + def to_dict(self) -> dict: + result: dict = {} + result["text"] = from_str(self.text) + result["type"] = from_str(self.type) + if self.url is not None: + result["url"] = from_union([from_str, from_none], self.url) + return result + +class SlashCommandAddTimelineEntryResultKind(Enum): + ADD_TIMELINE_ENTRY = "add-timeline-entry" + class SlashCommandAgentPromptResultKind(Enum): AGENT_PROMPT = "agent-prompt" class SlashCommandCompletedResultKind(Enum): COMPLETED = "completed" +class SlashCommandModelPickerDialogKind(Enum): + MODEL_PICKER = "model-picker" + class SlashCommandInvocationResultKind(Enum): + ADD_TIMELINE_ENTRY = "add-timeline-entry" AGENT_PROMPT = "agent-prompt" COMPLETED = "completed" SELECT_SUBCOMMAND = "select-subcommand" + SET_MODEL = "set-model" + SET_PLAN_MODEL = "set-plan-model" + SHOW_DIALOG = "show-dialog" TEXT = "text" # Experimental: this type is part of an experimental API and may change or be removed. @@ -11007,6 +13068,15 @@ def to_dict(self) -> dict: class SlashCommandSelectSubcommandResultKind(Enum): SELECT_SUBCOMMAND = "select-subcommand" +class SlashCommandSetModelResultKind(Enum): + SET_MODEL = "set-model" + +class SlashCommandSetPlanModelResultKind(Enum): + SET_PLAN_MODEL = "set-plan-model" + +class SlashCommandShowDialogResultKind(Enum): + SHOW_DIALOG = "show-dialog" + # Experimental: this type is part of an experimental API and may change or be removed. class SubagentSettingsEntryContextTier(Enum): """Context tier override for matching subagents""" @@ -11059,6 +13129,109 @@ def to_dict(self) -> dict: result["timestamp"] = self.timestamp.isoformat() return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class TaskCompleteData: + """Task completion notification with summary from the agent""" + + objective_id: int | None = None + """Active autopilot objective ID evaluated by the completion reviewer""" + + outcome: TaskCompletionOutcome | None = None + """Semantic completion decision. Absent on legacy events and invalid tool calls""" + + reason: str | None = None + """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 + """ + success: bool | None = None + """Whether the task was accepted as complete. False when validation failed or completion was + rejected or blocked by the reviewer + """ + summary: str | None = None + """Summary of the completed task, provided by the agent""" + + @staticmethod + def from_dict(obj: Any) -> 'TaskCompleteData': + assert isinstance(obj, dict) + objective_id = from_union([from_int, from_none], obj.get("objectiveId")) + outcome = from_union([TaskCompletionOutcome, from_none], obj.get("outcome")) + reason = from_union([from_str, from_none], obj.get("reason")) + success = from_union([from_bool, from_none], obj.get("success")) + summary = from_union([from_str, from_none], obj.get("summary")) + return TaskCompleteData(objective_id, outcome, reason, success, summary) + + def to_dict(self) -> dict: + result: dict = {} + if self.objective_id is not None: + result["objectiveId"] = from_union([from_int, from_none], self.objective_id) + if self.outcome is not None: + result["outcome"] = from_union([lambda x: to_enum(TaskCompletionOutcome, x), from_none], self.outcome) + if self.reason is not None: + result["reason"] = from_union([from_str, from_none], self.reason) + if self.success is not None: + result["success"] = from_union([from_bool, from_none], self.success) + if self.summary is not None: + 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 TaskCompletionDecision: + """Completion-review decision produced by the task-completion tool.""" + + outcome: TaskCompletionOutcome + """Semantic result of evaluating the task completion request.""" + + completion_eligibility_token: int | None = None + """Objective eligibility token captured when the decision was evaluated.""" + + completion_rejection_budget_exhausted: bool | None = None + """Whether completion was accepted after the reviewer-rejection budget was exhausted.""" + + objective_id: int | None = None + """Active autopilot objective evaluated by the completion reviewer.""" + + reason: str | None = None + """Rationale for the completion decision, when one is available.""" + + reviewer_derived: bool | None = None + """Whether the rationale was derived from completion-reviewer output.""" + + reviewer_result_meta: Any = None + """Information-flow metadata captured from the completion reviewer.""" + + @staticmethod + def from_dict(obj: Any) -> 'TaskCompletionDecision': + assert isinstance(obj, dict) + outcome = TaskCompletionOutcome(obj.get("outcome")) + completion_eligibility_token = from_union([from_int, from_none], obj.get("completionEligibilityToken")) + completion_rejection_budget_exhausted = from_union([from_bool, from_none], obj.get("completionRejectionBudgetExhausted")) + objective_id = from_union([from_int, from_none], obj.get("objectiveId")) + reason = from_union([from_str, from_none], obj.get("reason")) + reviewer_derived = from_union([from_bool, from_none], obj.get("reviewerDerived")) + reviewer_result_meta = obj.get("reviewerResultMeta") + return TaskCompletionDecision(outcome, completion_eligibility_token, completion_rejection_budget_exhausted, objective_id, reason, reviewer_derived, reviewer_result_meta) + + def to_dict(self) -> dict: + result: dict = {} + result["outcome"] = to_enum(TaskCompletionOutcome, self.outcome) + if self.completion_eligibility_token is not None: + result["completionEligibilityToken"] = from_union([from_int, from_none], self.completion_eligibility_token) + if self.completion_rejection_budget_exhausted is not None: + result["completionRejectionBudgetExhausted"] = from_union([from_bool, from_none], self.completion_rejection_budget_exhausted) + if self.objective_id is not None: + result["objectiveId"] = from_union([from_int, from_none], self.objective_id) + if self.reason is not None: + result["reason"] = from_union([from_str, from_none], self.reason) + if self.reviewer_derived is not None: + result["reviewerDerived"] = from_union([from_bool, from_none], self.reviewer_derived) + if self.reviewer_result_meta is not None: + result["reviewerResultMeta"] = self.reviewer_result_meta + return result + # Experimental: this type is part of an experimental API and may change or be removed. class TaskShellInfoAttachmentMode(Enum): """Whether the shell runs inside a managed PTY session or as an independent background @@ -11355,7 +13528,7 @@ class TasksStartAgentRequest: """Type of agent to start (e.g., 'explore', 'task', 'general-purpose')""" name: str - """Short name for the agent, used to generate a human-readable ID""" + """Friendly, non-unique name used when displaying the agent""" prompt: str """Task prompt for the agent""" @@ -11445,8 +13618,8 @@ def to_dict(self) -> dict: result["features"] = from_dict(from_str, self.features) return result -class TokenAuthInfoType(Enum): - TOKEN = "token" +class TokenProviderAuthInfoType(Enum): + TOKEN_PROVIDER = "token-provider" # Experimental: this type is part of an experimental API and may change or be removed. @dataclass @@ -11492,6 +13665,70 @@ def to_dict(self) -> dict: result["parameters"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.parameters) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ToolResultNewMessage: + """A message injected by a tool result.""" + + content: str + """Message content to inject after the tool result.""" + + source: str + """Source attributed to the injected message.""" + + @staticmethod + def from_dict(obj: Any) -> 'ToolResultNewMessage': + assert isinstance(obj, dict) + content = from_str(obj.get("content")) + source = from_str(obj.get("source")) + return ToolResultNewMessage(content, source) + + def to_dict(self) -> dict: + result: dict = {} + result["content"] = from_str(self.content) + result["source"] = from_str(self.source) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +class ToolResultType(Enum): + """Execution outcome classification.""" + + DENIED = "denied" + FAILURE = "failure" + REJECTED = "rejected" + SUCCESS = "success" + TIMEOUT = "timeout" + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ToolsExecuteRequest: + """A tool name and arguments to execute through the session's native invocation pipeline.""" + + arguments: Any + """Arguments supplied to the tool.""" + + name: str + """Name of the currently offered tool to execute.""" + + tool_call_id: str | None = None + """Optional identifier used to correlate this invocation with its tool call.""" + + @staticmethod + def from_dict(obj: Any) -> 'ToolsExecuteRequest': + assert isinstance(obj, dict) + arguments = obj.get("arguments") + name = from_str(obj.get("name")) + tool_call_id = from_union([from_str, from_none], obj.get("toolCallId")) + return ToolsExecuteRequest(arguments, name, tool_call_id) + + def to_dict(self) -> dict: + result: dict = {} + result["arguments"] = self.arguments + result["name"] = from_str(self.name) + if self.tool_call_id is not None: + result["toolCallId"] = from_union([from_str, from_none], self.tool_call_id) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ToolsInitializeAndValidateResult: @@ -11530,6 +13767,19 @@ def to_dict(self) -> dict: result["model"] = from_union([from_str, from_none], self.model) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ToolsSetResult: + """Empty result after replacing the calling connection's externally implemented tools.""" + @staticmethod + def from_dict(obj: Any) -> 'ToolsSetResult': + assert isinstance(obj, dict) + return ToolsSetResult() + + 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 ToolsUpdateSubagentSettingsResult: @@ -11626,9 +13876,6 @@ class UIElicitationSchemaPropertyType(Enum): NUMBER = "number" STRING = "string" -class UIElicitationSchemaType(Enum): - OBJECT = "object" - # Experimental: this type is part of an experimental API and may change or be removed. class UIElicitationResponseAction(Enum): """The user's response: accept (submitted), decline (rejected), or cancel (dismissed)""" @@ -11671,11 +13918,48 @@ class UIElicitationSchemaPropertyNumberType(Enum): # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class UIEphemeralQueryResult: - """Transient answer generated from current conversation context.""" +class UIEphemeralQueryRequest: + """Transient question to answer without adding it to conversation history.""" + + question: str + """Question to answer from the current conversation context.""" + + # Internal: this field is an internal SDK API and is not part of the public surface. + abort_signal: Any = None + """In-process `AbortSignal` forwarded to the model client to cancel an in-flight request. + Internal and excluded from the public SDK surface. + """ + # Internal: this field is an internal SDK API and is not part of the public surface. + on_chunk: Any = None + """In-process streaming callback `(text) => void` invoked with each token as the model emits + it. Internal and excluded from the public SDK surface. + """ + + @staticmethod + def from_dict(obj: Any) -> 'UIEphemeralQueryRequest': + assert isinstance(obj, dict) + question = from_str(obj.get("question")) + abort_signal = obj.get("abortSignal") + on_chunk = obj.get("onChunk") + return UIEphemeralQueryRequest(question, abort_signal, on_chunk) + + def to_dict(self) -> dict: + result: dict = {} + result["question"] = from_str(self.question) + if self.abort_signal is not None: + result["abortSignal"] = self.abort_signal + if self.on_chunk is not None: + result["onChunk"] = self.on_chunk + return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class UIEphemeralQueryResult: + """Completed transient query. Ordered chunks and the terminal outcome are also delivered + through `ui.ephemeral_query` session events while it runs. + """ answer: str - """Full assistant response text.""" + """Answer returned by the model""" @staticmethod def from_dict(obj: Any) -> 'UIEphemeralQueryResult': @@ -11852,40 +14136,6 @@ def to_dict(self) -> dict: result["unregistered"] = from_bool(self.unregistered) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class UsageMetricsCodeChanges: - """Aggregated code change metrics""" - - files_modified: list[str] - """Distinct file paths modified during the session""" - - files_modified_count: int - """Number of distinct files modified""" - - lines_added: int - """Total lines of code added""" - - lines_removed: int - """Total lines of code removed""" - - @staticmethod - def from_dict(obj: Any) -> 'UsageMetricsCodeChanges': - assert isinstance(obj, dict) - files_modified = from_list(from_str, obj.get("filesModified")) - files_modified_count = from_int(obj.get("filesModifiedCount")) - lines_added = from_int(obj.get("linesAdded")) - lines_removed = from_int(obj.get("linesRemoved")) - return UsageMetricsCodeChanges(files_modified, files_modified_count, lines_added, lines_removed) - - def to_dict(self) -> dict: - result: dict = {} - result["filesModified"] = from_list(from_str, self.files_modified) - result["filesModifiedCount"] = from_int(self.files_modified_count) - result["linesAdded"] = from_int(self.lines_added) - result["linesRemoved"] = from_int(self.lines_removed) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class UsageMetricsModelMetricRequests: @@ -11969,6 +14219,40 @@ def to_dict(self) -> dict: result["reasoningTokens"] = from_union([from_int, from_none], self.reasoning_tokens) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class UsageMetricsCodeChanges: + """Aggregated code change metrics""" + + files_modified: list[str] + """Distinct file paths modified during the session""" + + files_modified_count: int + """Number of distinct files modified""" + + lines_added: int + """Total lines of code added""" + + lines_removed: int + """Total lines of code removed""" + + @staticmethod + def from_dict(obj: Any) -> 'UsageMetricsCodeChanges': + assert isinstance(obj, dict) + files_modified = from_list(from_str, obj.get("filesModified")) + files_modified_count = from_int(obj.get("filesModifiedCount")) + lines_added = from_int(obj.get("linesAdded")) + lines_removed = from_int(obj.get("linesRemoved")) + return UsageMetricsCodeChanges(files_modified, files_modified_count, lines_added, lines_removed) + + def to_dict(self) -> dict: + result: dict = {} + result["filesModified"] = from_list(from_str, self.files_modified) + result["filesModifiedCount"] = from_int(self.files_modified_count) + result["linesAdded"] = from_int(self.lines_added) + result["linesRemoved"] = from_int(self.lines_removed) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class UsageMetricsTokenDetail: @@ -11988,9 +14272,6 @@ def to_dict(self) -> dict: result["tokenCount"] = from_int(self.token_count) return result -class UserAuthInfoType(Enum): - USER = "user" - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class UserSettingMetadata: @@ -12115,7 +14396,10 @@ class WorkspacesAddSummaryResult: """Persisted summary metadata and refreshed workspace metadata.""" summary: dict[str, Any] | None = None + """Metadata for the persisted summary.""" + workspace: dict[str, Any] | None = None + """Refreshed metadata for the containing workspace.""" @staticmethod def from_dict(obj: Any) -> 'WorkspacesAddSummaryResult': @@ -12433,8 +14717,10 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionAuthStatus: - """Authentication status and account metadata for the session.""" + """Authentication status and account metadata for the session. + Authentication accounts available to the internal session host. + """ is_authenticated: bool """Whether the session has resolved authentication""" @@ -12706,291 +14992,1140 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class AllowAllPermissionSetResult: - """Indicates whether the operation succeeded and reports the post-mutation state.""" +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.""" - enabled: bool - """Authoritative full allow-all state after the mutation""" + @staticmethod + def from_dict(obj: Any) -> 'BuiltInModelCatalog': + assert isinstance(obj, dict) + models = from_list(BuiltInModelCatalogEntry.from_dict, obj.get("models")) + return BuiltInModelCatalog(models) - success: bool - """Whether the operation succeeded""" + def to_dict(self) -> dict: + result: dict = {} + 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. +@dataclass +class BuiltinToolFormat: + """Custom grammar input format accepted by a built-in tool.""" - mode: PermissionsAllowAllMode | None = None - """Authoritative allow-all mode after the mutation""" + definition: str + """Grammar definition accepted by the tool.""" + + syntax: str + """Grammar syntax used by the format definition.""" + + type: BuiltinToolFormatType + """Custom input-format discriminator.""" @staticmethod - def from_dict(obj: Any) -> 'AllowAllPermissionSetResult': + def from_dict(obj: Any) -> 'BuiltinToolFormat': assert isinstance(obj, dict) - enabled = from_bool(obj.get("enabled")) - success = from_bool(obj.get("success")) - mode = from_union([PermissionsAllowAllMode, from_none], obj.get("mode")) - return AllowAllPermissionSetResult(enabled, success, mode) + definition = from_str(obj.get("definition")) + syntax = from_str(obj.get("syntax")) + type = BuiltinToolFormatType(obj.get("type")) + return BuiltinToolFormat(definition, syntax, type) def to_dict(self) -> dict: result: dict = {} - result["enabled"] = from_bool(self.enabled) - result["success"] = from_bool(self.success) - if self.mode is not None: - result["mode"] = from_union([lambda x: to_enum(PermissionsAllowAllMode, x), from_none], self.mode) + result["definition"] = from_str(self.definition) + result["syntax"] = from_str(self.syntax) + result["type"] = to_enum(BuiltinToolFormatType, self.type) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class AllowAllPermissionState: - """Current allow-all permission mode.""" - - enabled: bool - """Whether full allow-all permissions are currently active""" +class BuiltinToolInputSchema: + """JSON Schema object accepted by a built-in tool.""" - mode: PermissionsAllowAllMode | None = None - """Current allow-all mode""" + type: BuiltinToolInputSchemaType + """Root type of the tool input schema.""" @staticmethod - def from_dict(obj: Any) -> 'AllowAllPermissionState': + def from_dict(obj: Any) -> 'BuiltinToolInputSchema': assert isinstance(obj, dict) - enabled = from_bool(obj.get("enabled")) - mode = from_union([PermissionsAllowAllMode, from_none], obj.get("mode")) - return AllowAllPermissionState(enabled, mode) + type = BuiltinToolInputSchemaType(obj.get("type")) + return BuiltinToolInputSchema(type) def to_dict(self) -> dict: result: dict = {} - result["enabled"] = from_bool(self.enabled) - if self.mode is not None: - result["mode"] = from_union([lambda x: to_enum(PermissionsAllowAllMode, x), from_none], self.mode) + result["type"] = to_enum(BuiltinToolInputSchemaType, self.type) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SlashCommandInput: - """Optional unstructured input hint""" +class CardDigest: + """Semantic digest of a strictly parsed and schema-validated JSON MCP card. Both URL-backed + and embedded cards are canonicalised with RFC 8785 JSON Canonicalization Scheme, encoded + as UTF-8, and hashed with SHA-256. - hint: str - """Hint to display when command input has not been provided""" + Semantic digest of the exact validated JSON content bound to the plan handle. + """ + algorithm: CardDigestAlgorithm + """Digest algorithm and canonical representation""" - choices: list[SlashCommandInputChoice] | None = None - """Optional literal choices the input accepts, each with a human-facing description; clients - may render these as selectable options + value: str + """SHA-256 digest of the RFC 8785 canonical UTF-8 bytes, encoded as exactly 64 lowercase + hexadecimal characters. """ - completion: SlashCommandInputCompletion | None = None - """Optional completion hint for the input (e.g. 'directory' for filesystem path completion)""" - preserve_multiline_input: bool | None = None - """When true, clients should pass the full text after the command name as a single argument - rather than splitting on whitespace + @staticmethod + def from_dict(obj: Any) -> 'CardDigest': + assert isinstance(obj, dict) + algorithm = CardDigestAlgorithm(obj.get("algorithm")) + value = from_str(obj.get("value")) + return CardDigest(algorithm, value) + + def to_dict(self) -> dict: + result: dict = {} + result["algorithm"] = to_enum(CardDigestAlgorithm, self.algorithm) + result["value"] = from_str(self.value) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class CatalogAuthenticationRequiredError: + """An optional catalog authentication exchange did not establish the caller's identity. + Anonymous search remains supported; this refusal is reserved for an operation that cannot + continue after the attempted exchange. It is distinct from `policy-rejected` and from a + network failure, and the reason identifies the recovery action. """ - required: bool | None = None - """When true, the command requires non-empty input; clients should render the input hint as - required + kind: ClassVar[str] = "authentication-required" + """Discriminator: the caller is not authenticated""" + + message: str + """Human-readable explanation, safe to surface. Never contains a credential or token, nor a + query, URL, handle, or secret. + """ + reason: CatalogAuthenticationRequiredReason + """Why authentication failed. Only an expired credential justifies attempting a silent + refresh; an absent or rejected credential requires sign-in. """ @staticmethod - def from_dict(obj: Any) -> 'SlashCommandInput': + def from_dict(obj: Any) -> 'CatalogAuthenticationRequiredError': assert isinstance(obj, dict) - hint = from_str(obj.get("hint")) - choices = from_union([lambda x: from_list(SlashCommandInputChoice.from_dict, x), from_none], obj.get("choices")) - completion = from_union([SlashCommandInputCompletion, from_none], obj.get("completion")) - preserve_multiline_input = from_union([from_bool, from_none], obj.get("preserveMultilineInput")) - required = from_union([from_bool, from_none], obj.get("required")) - return SlashCommandInput(hint, choices, completion, preserve_multiline_input, required) + message = from_str(obj.get("message")) + reason = CatalogAuthenticationRequiredReason(obj.get("reason")) + return CatalogAuthenticationRequiredError(message, reason) def to_dict(self) -> dict: result: dict = {} - result["hint"] = from_str(self.hint) - if self.choices is not None: - result["choices"] = from_union([lambda x: from_list(lambda x: to_class(SlashCommandInputChoice, x), x), from_none], self.choices) - if self.completion is not None: - result["completion"] = from_union([lambda x: to_enum(SlashCommandInputCompletion, x), from_none], self.completion) - if self.preserve_multiline_input is not None: - result["preserveMultilineInput"] = from_union([from_bool, from_none], self.preserve_multiline_input) - if self.required is not None: - result["required"] = from_union([from_bool, from_none], self.required) + result["kind"] = self.kind + result["message"] = from_str(self.message) + result["reason"] = to_enum(CatalogAuthenticationRequiredReason, self.reason) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class PushAttachmentDirectory: - """Directory attachment""" +class CatalogCandidateSourceEmbedded: + """Candidate whose card reference arrived inline. The document and its content-derived + properties stay behind the runtime boundary. + """ + kind: ClassVar[str] = "embedded" + """Discriminator: the card is embedded, and carries no URL""" - display_name: str - """User-facing display name for the attachment""" + @staticmethod + def from_dict(obj: Any) -> 'CatalogCandidateSourceEmbedded': + assert isinstance(obj, dict) + return CatalogCandidateSourceEmbedded() - path: str - """Absolute directory path""" + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = self.kind + return result - type: ClassVar[str] = "directory" - """Attachment type discriminator""" +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class CatalogCandidateSourceURL: + """Candidate whose card is retrieved from a URL through the runtime's hardened fetch + boundary. + """ + kind: ClassVar[str] = "url" + """Discriminator: the card is URL-backed, and carries no embedded data""" + + url: str + """Card URL as advertised. Inert untrusted data: the runtime retrieves it only through its + own hardened boundary, and it is never logged. + """ @staticmethod - def from_dict(obj: Any) -> 'PushAttachmentDirectory': + def from_dict(obj: Any) -> 'CatalogCandidateSourceURL': assert isinstance(obj, dict) - display_name = from_str(obj.get("displayName")) - path = from_str(obj.get("path")) - return PushAttachmentDirectory(display_name, path) + url = from_str(obj.get("url")) + return CatalogCandidateSourceURL(url) def to_dict(self) -> dict: result: dict = {} - result["displayName"] = from_str(self.display_name) - result["path"] = from_str(self.path) - result["type"] = self.type + result["kind"] = self.kind + 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 ConnectedRemoteSessionMetadata: - """Metadata for a connected remote session.""" +class InstalledPluginSourceURL: + """Source descriptor for a direct URL plugin install, with URL, optional ref or full commit + SHA, and optional subpath. + """ + source: MCPServerCardURLKind + """Constant value. Always "url".""" - kind: ConnectedRemoteSessionMetadataKind - """Neutral SDK discriminator for the connected remote session kind.""" + url: str + """URL of the plugin source.""" - modified_time: datetime - """Last session update time as an ISO 8601 string.""" + path: str | None = None + """Optional source-relative path to the plugin.""" - repository: ConnectedRemoteSessionMetadataRepository - """Repository associated with the connected remote session.""" + ref: str | None = None + """Optional Git ref to resolve.""" - session_id: str - """SDK session ID for the connected remote session.""" + sha: str | None = None + """Optional full 40-character hexadecimal commit SHA.""" - start_time: datetime - """Session start time as an ISO 8601 string.""" + @staticmethod + def from_dict(obj: Any) -> 'InstalledPluginSourceURL': + assert isinstance(obj, dict) + source = MCPServerCardURLKind(obj.get("source")) + 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")) + sha = from_union([from_str, from_none], obj.get("sha")) + return InstalledPluginSourceURL(source, url, path, ref, sha) - name: str | None = None - """Optional friendly session name.""" + def to_dict(self) -> dict: + result: dict = {} + result["source"] = to_enum(MCPServerCardURLKind, self.source) + result["url"] = from_str(self.url) + 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.sha is not None: + result["sha"] = from_union([from_str, from_none], self.sha) + return result - pull_request_number: int | None = None - """Pull request number associated with the session.""" +# 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 or full commit + SHA, and optional subpath. + """ + source: MCPServerCardURLKind + """Constant value. Always "url".""" - resource_id: str | None = None - """Original remote resource identifier.""" + url: str + """URL of the plugin source.""" - stale_at: datetime | None = None - """Remote session staleness deadline as an ISO 8601 string.""" + path: str | None = None + """Optional source-relative path to the plugin.""" - state: str | None = None - """Remote session state returned by the backing service.""" + ref: str | None = None + """Optional Git ref to resolve.""" - summary: str | None = None - """Optional session summary.""" + sha: str | None = None + """Optional full 40-character hexadecimal commit SHA.""" @staticmethod - def from_dict(obj: Any) -> 'ConnectedRemoteSessionMetadata': + def from_dict(obj: Any) -> 'SessionInstalledPluginSourceURL': assert isinstance(obj, dict) - kind = ConnectedRemoteSessionMetadataKind(obj.get("kind")) - modified_time = from_datetime(obj.get("modifiedTime")) - repository = ConnectedRemoteSessionMetadataRepository.from_dict(obj.get("repository")) - session_id = from_str(obj.get("sessionId")) - start_time = from_datetime(obj.get("startTime")) - name = from_union([from_str, from_none], obj.get("name")) - pull_request_number = from_union([from_int, from_none], obj.get("pullRequestNumber")) - resource_id = from_union([from_str, from_none], obj.get("resourceId")) - stale_at = from_union([from_datetime, from_none], obj.get("staleAt")) - state = from_union([from_str, from_none], obj.get("state")) - summary = from_union([from_str, from_none], obj.get("summary")) - return ConnectedRemoteSessionMetadata(kind, modified_time, repository, session_id, start_time, name, pull_request_number, resource_id, stale_at, state, summary) + source = MCPServerCardURLKind(obj.get("source")) + 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")) + sha = from_union([from_str, from_none], obj.get("sha")) + return SessionInstalledPluginSourceURL(source, url, path, ref, sha) def to_dict(self) -> dict: result: dict = {} - result["kind"] = to_enum(ConnectedRemoteSessionMetadataKind, self.kind) - result["modifiedTime"] = self.modified_time.isoformat() - result["repository"] = to_class(ConnectedRemoteSessionMetadataRepository, self.repository) - result["sessionId"] = from_str(self.session_id) - result["startTime"] = self.start_time.isoformat() - if self.name is not None: - result["name"] = from_union([from_str, from_none], self.name) - if self.pull_request_number is not None: - result["pullRequestNumber"] = from_union([from_int, from_none], self.pull_request_number) - if self.resource_id is not None: - result["resourceId"] = from_union([from_str, from_none], self.resource_id) - if self.stale_at is not None: - result["staleAt"] = from_union([lambda x: x.isoformat(), from_none], self.stale_at) - if self.state is not None: - result["state"] = from_union([from_str, from_none], self.state) - if self.summary is not None: - result["summary"] = from_union([from_str, from_none], self.summary) + result["source"] = to_enum(MCPServerCardURLKind, self.source) + result["url"] = from_str(self.url) + 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.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 ContentExclusionCheckPathsResult: - """Batch content-exclusion result. Callers must fail closed when policy evaluation is - unavailable. +class CatalogNegotiatedContract: + """The protocol version and capability set the runtime actually honoured for a successful + catalog operation. + + Protocol version and capabilities the runtime honoured. """ - 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. + granted_capabilities: list[CatalogCapability] + """Wire features the runtime understood for this operation. Always a superset of the + caller's required features, because any shortfall is a refusal instead. Operation + availability remains a separate typed result. """ - checks: list[ContentExclusionPathCheck] - """Per-path decisions in request order. Empty when available is false.""" + runtime_protocol_version: int + """Protocol version of the runtime that served the request.""" @staticmethod - def from_dict(obj: Any) -> 'ContentExclusionCheckPathsResult': + def from_dict(obj: Any) -> 'CatalogNegotiatedContract': assert isinstance(obj, dict) - available = from_bool(obj.get("available")) - checks = from_list(ContentExclusionPathCheck.from_dict, obj.get("checks")) - return ContentExclusionCheckPathsResult(available, checks) + granted_capabilities = from_list(CatalogCapability, obj.get("grantedCapabilities")) + runtime_protocol_version = from_int(obj.get("runtimeProtocolVersion")) + return CatalogNegotiatedContract(granted_capabilities, runtime_protocol_version) 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) + result["grantedCapabilities"] = from_list(lambda x: to_enum(CatalogCapability, x), self.granted_capabilities) + result["runtimeProtocolVersion"] = from_int(self.runtime_protocol_version) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class MetadataContextHeaviestMessagesResult: - """The heaviest individual messages in the session's context window, most-expensive first.""" - - messages: list[ContextHeaviestMessage] - """Heaviest messages, most-expensive first.""" +class CatalogSearchRequest: + """A bounded catalog search. Both the query length and the result count are capped by the + schema so a caller cannot request an unbounded scan. + """ + contract: CatalogClientContract + """Protocol version and capabilities the caller requires.""" - total_tokens: int - """Total token count of the current context window, so callers can compute each message's - share without a second call. + query: str + """Free-text search query. Persisted as tool input for session continuity, but omitted from + telemetry. + """ + kinds: list[CatalogCandidateKind] | None = None + """Restrict results to these candidate kinds. When omitted, every kind the runtime supports + is searched. """ + limit: int | None = None + """Maximum number of candidates to return. Defaults to 10 when omitted.""" @staticmethod - def from_dict(obj: Any) -> 'MetadataContextHeaviestMessagesResult': + def from_dict(obj: Any) -> 'CatalogSearchRequest': assert isinstance(obj, dict) - messages = from_list(ContextHeaviestMessage.from_dict, obj.get("messages")) - total_tokens = from_int(obj.get("totalTokens")) - return MetadataContextHeaviestMessagesResult(messages, total_tokens) + contract = CatalogClientContract.from_dict(obj.get("contract")) + query = from_str(obj.get("query")) + kinds = from_union([lambda x: from_list(CatalogCandidateKind, x), from_none], obj.get("kinds")) + limit = from_union([from_int, from_none], obj.get("limit")) + return CatalogSearchRequest(contract, query, kinds, limit) def to_dict(self) -> dict: result: dict = {} - result["messages"] = from_list(lambda x: to_class(ContextHeaviestMessage, x), self.messages) - result["totalTokens"] = from_int(self.total_tokens) + result["contract"] = to_class(CatalogClientContract, self.contract) + result["query"] = from_str(self.query) + if self.kinds is not None: + result["kinds"] = from_union([lambda x: from_list(lambda x: to_enum(CatalogCandidateKind, x), x), from_none], self.kinds) + 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 CanvasHostContextCapabilities: - """Host capabilities""" +class CatalogContractViolationError: + """An upstream catalog response broke the wire contract. Most importantly, every result must + carry exactly one of a URL or embedded data: a result carrying both, or neither, is + refused here rather than being guessed at. + """ + kind: ClassVar[str] = "contract-violation" + """Discriminator: the upstream response broke the contract""" - canvases: bool | None = None - """Whether canvas rendering is supported""" + message: str + """Human-readable explanation, safe to surface. Never echoes response content, nor a query, + URL, handle, or secret. + """ + reason: CatalogContractViolationReason + """Which rule the response broke.""" @staticmethod - def from_dict(obj: Any) -> 'CanvasHostContextCapabilities': + def from_dict(obj: Any) -> 'CatalogContractViolationError': assert isinstance(obj, dict) - canvases = from_union([from_bool, from_none], obj.get("canvases")) - return CanvasHostContextCapabilities(canvases) + message = from_str(obj.get("message")) + reason = CatalogContractViolationReason(obj.get("reason")) + return CatalogContractViolationError(message, reason) def to_dict(self) -> dict: result: dict = {} - if self.canvases is not None: - result["canvases"] = from_union([from_bool, from_none], self.canvases) + result["kind"] = self.kind + result["message"] = from_str(self.message) + result["reason"] = to_enum(CatalogContractViolationReason, self.reason) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class DiscoveredCanvas: - """Canvas available in the current session.""" +class CatalogHandleRejectedError: + """A presented handle was not accepted. Handles are runtime-instance scoped, TTL-bound, and + single-use, so each way of failing is reported distinctly. + """ + handle_type: CatalogHandleType + """Which kind of handle was presented.""" - canvas_id: str - """Provider-local canvas identifier""" + kind: ClassVar[str] = "handle-rejected" + """Discriminator: a handle was rejected""" - description: str - """Short, single-sentence description shown to the agent in canvas catalogs.""" + message: str + """Human-readable explanation, safe to surface. Never contains the handle itself, nor a + query, URL, or secret. + """ + reason: CatalogHandleRejectionReason + """Why the handle was rejected.""" + + @staticmethod + def from_dict(obj: Any) -> 'CatalogHandleRejectedError': + assert isinstance(obj, dict) + handle_type = CatalogHandleType(obj.get("handleType")) + message = from_str(obj.get("message")) + reason = CatalogHandleRejectionReason(obj.get("reason")) + return CatalogHandleRejectedError(handle_type, message, reason) + + def to_dict(self) -> dict: + result: dict = {} + result["handleType"] = to_enum(CatalogHandleType, self.handle_type) + result["kind"] = self.kind + result["message"] = from_str(self.message) + result["reason"] = to_enum(CatalogHandleRejectionReason, self.reason) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class CatalogInvalidRequestError: + """The request was rejected before any work was done, because a bounded field fell outside + its permitted range or a required field was unusable. + """ + field: CatalogInvalidRequestField + """Which request field was rejected.""" + + kind: ClassVar[str] = "invalid-request" + """Discriminator: the request itself was invalid""" + + message: str + """Human-readable explanation, safe to surface. Never echoes the offending value, nor a + query, URL, handle, or secret. + """ + + @staticmethod + def from_dict(obj: Any) -> 'CatalogInvalidRequestError': + assert isinstance(obj, dict) + field = CatalogInvalidRequestField(obj.get("field")) + message = from_str(obj.get("message")) + return CatalogInvalidRequestError(field, message) + + def to_dict(self) -> dict: + result: dict = {} + result["field"] = to_enum(CatalogInvalidRequestField, self.field) + result["kind"] = self.kind + 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 CatalogMalformedCardError: + """A card could not be parsed or did not satisfy its declared media type's schema.""" + + kind: ClassVar[str] = "malformed-card" + """Discriminator: the card was malformed""" + + message: str + """Human-readable explanation, safe to surface. Never echoes card content, nor a query, URL, + handle, or secret. + """ + reason: CatalogMalformedCardReason + """How the card failed validation.""" + + media_type: CatalogMediaType | None = None + """Media type the card was interpreted as, when it declared one this runtime recognises.""" + + @staticmethod + def from_dict(obj: Any) -> 'CatalogMalformedCardError': + assert isinstance(obj, dict) + message = from_str(obj.get("message")) + reason = CatalogMalformedCardReason(obj.get("reason")) + media_type = from_union([CatalogMediaType, from_none], obj.get("mediaType")) + return CatalogMalformedCardError(message, reason, media_type) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = self.kind + result["message"] = from_str(self.message) + result["reason"] = to_enum(CatalogMalformedCardReason, self.reason) + if self.media_type is not None: + result["mediaType"] = from_union([lambda x: to_enum(CatalogMediaType, x), from_none], self.media_type) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MCPServerCardEmbedded: + """An MCP server card supplied inline as an inert document.""" + + data: str + """The card document verbatim, treated as inert untrusted bytes. The runtime parses and + validates it; the host is not expected to interpret it. Never logged. + """ + kind: ClassVar[str] = "embedded" + """Discriminator: the card is embedded, and carries no URL""" + + media_type: MCPServerCardMediaType + """Media type the card is expected to conform to.""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPServerCardEmbedded': + assert isinstance(obj, dict) + data = from_str(obj.get("data")) + media_type = MCPServerCardMediaType(obj.get("mediaType")) + return MCPServerCardEmbedded(data, media_type) + + def to_dict(self) -> dict: + result: dict = {} + result["data"] = from_str(self.data) + result["kind"] = self.kind + result["mediaType"] = to_enum(MCPServerCardMediaType, self.media_type) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MCPServerCardURL: + """An MCP server card to be retrieved from a URL through the runtime's hardened fetch + boundary. + """ + kind: ClassVar[str] = "url" + """Discriminator: the card is URL-backed, and carries no embedded data""" + + media_type: MCPServerCardMediaType + """Media type the card is expected to conform to.""" + + url: str + """Card URL. Retrieved only through the runtime's hardened boundary, with scheme, + credential, address-range, redirect, timeout, and response-size controls applied. Never + logged. + """ + + @staticmethod + def from_dict(obj: Any) -> 'MCPServerCardURL': + assert isinstance(obj, dict) + media_type = MCPServerCardMediaType(obj.get("mediaType")) + url = from_str(obj.get("url")) + return MCPServerCardURL(media_type, url) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = self.kind + result["mediaType"] = to_enum(MCPServerCardMediaType, self.media_type) + 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 CatalogNegotiationRefusedError: + """The caller's protocol version or required capabilities cannot be honoured. Returned + instead of a partial or ambiguous success. + """ + kind: ClassVar[str] = "negotiation-refused" + """Discriminator: capability or protocol-version negotiation failed""" + + message: str + """Human-readable explanation, safe to surface. Never contains a query, URL, handle, or + secret. + """ + minimum_supported_protocol_version: int + """Lowest caller protocol version this runtime will serve.""" + + reason: CatalogNegotiationRefusedReason + """Whether the version or the capability set was the problem.""" + + runtime_protocol_version: int + """Protocol version of the runtime that refused the request.""" + + supported_capabilities: list[CatalogCapability] + """Every wire feature this runtime understands, so the caller can retry within that + contract. This list does not imply that every deployment has enabled every operation. + """ + unsupported_capabilities: list[str] + """The subset of the caller's bounded extensible capability identifiers this runtime cannot + honour. + """ + + @staticmethod + def from_dict(obj: Any) -> 'CatalogNegotiationRefusedError': + assert isinstance(obj, dict) + message = from_str(obj.get("message")) + minimum_supported_protocol_version = from_int(obj.get("minimumSupportedProtocolVersion")) + reason = CatalogNegotiationRefusedReason(obj.get("reason")) + runtime_protocol_version = from_int(obj.get("runtimeProtocolVersion")) + supported_capabilities = from_list(CatalogCapability, obj.get("supportedCapabilities")) + unsupported_capabilities = from_list(from_str, obj.get("unsupportedCapabilities")) + return CatalogNegotiationRefusedError(message, minimum_supported_protocol_version, reason, runtime_protocol_version, supported_capabilities, unsupported_capabilities) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = self.kind + result["message"] = from_str(self.message) + result["minimumSupportedProtocolVersion"] = from_int(self.minimum_supported_protocol_version) + result["reason"] = to_enum(CatalogNegotiationRefusedReason, self.reason) + result["runtimeProtocolVersion"] = from_int(self.runtime_protocol_version) + result["supportedCapabilities"] = from_list(lambda x: to_enum(CatalogCapability, x), self.supported_capabilities) + result["unsupportedCapabilities"] = from_list(from_str, self.unsupported_capabilities) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class CatalogNetworkFailureError: + """The runtime could not reach the catalog authority or retrieve a card. Covers being + offline as well as transport-level failure. + """ + kind: ClassVar[str] = "network-failure" + """Discriminator: the network operation failed""" + + message: str + """Human-readable explanation, safe to surface. Never contains a query, URL, handle, or + secret. + """ + reason: CatalogNetworkFailureReason + """Categorised failure, low cardinality so it can be aggregated without carrying a URL.""" + + retry_after_seconds: int | None = None + """Bounded cooldown in seconds before another catalog request should be attempted, when the + authority supplied a numeric Retry-After value or the runtime applied its documented + fallback. + """ + status_code: int | None = None + """HTTP status code, when the failure was a rejected response.""" + + @staticmethod + def from_dict(obj: Any) -> 'CatalogNetworkFailureError': + assert isinstance(obj, dict) + message = from_str(obj.get("message")) + reason = CatalogNetworkFailureReason(obj.get("reason")) + retry_after_seconds = from_union([from_int, from_none], obj.get("retryAfterSeconds")) + status_code = from_union([from_int, from_none], obj.get("statusCode")) + return CatalogNetworkFailureError(message, reason, retry_after_seconds, status_code) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = self.kind + result["message"] = from_str(self.message) + result["reason"] = to_enum(CatalogNetworkFailureReason, self.reason) + if self.retry_after_seconds is not None: + result["retryAfterSeconds"] = from_union([from_int, from_none], self.retry_after_seconds) + if self.status_code is not None: + result["statusCode"] = from_union([from_int, from_none], self.status_code) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class CatalogNotInstallableError: + """The candidate is discoverable but cannot be installed. `application/ai-skill` resolves + here, because it stays searchable while remaining typed non-installable. + """ + kind: ClassVar[str] = "not-installable" + """Discriminator: the candidate cannot be installed""" + + message: str + """Human-readable explanation, safe to surface. Never contains a query, URL, handle, or + secret. + """ + reason: CatalogNotInstallableReason + """Why the candidate cannot be installed.""" + + @staticmethod + def from_dict(obj: Any) -> 'CatalogNotInstallableError': + assert isinstance(obj, dict) + message = from_str(obj.get("message")) + reason = CatalogNotInstallableReason(obj.get("reason")) + return CatalogNotInstallableError(message, reason) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = self.kind + result["message"] = from_str(self.message) + result["reason"] = to_enum(CatalogNotInstallableReason, self.reason) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class CatalogPolicyRejectedError: + """Registry or enterprise policy refused the operation.""" + + kind: ClassVar[str] = "policy-rejected" + """Discriminator: policy refused the operation""" + + message: str + """Human-readable explanation, safe to surface. Never contains a query, URL, handle, or + secret. + """ + source: MCPPlanPolicySource + """Which authority produced the decision.""" + + @staticmethod + def from_dict(obj: Any) -> 'CatalogPolicyRejectedError': + assert isinstance(obj, dict) + message = from_str(obj.get("message")) + source = MCPPlanPolicySource(obj.get("source")) + return CatalogPolicyRejectedError(message, source) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = self.kind + result["message"] = from_str(self.message) + result["source"] = to_enum(MCPPlanPolicySource, self.source) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class CatalogUnavailableError: + """The operation is not available on this runtime. Distinct from a network failure: nothing + was attempted. + """ + kind: ClassVar[str] = "unavailable" + """Discriminator: the operation is not available""" + + message: str + """Human-readable explanation, safe to surface. Never contains a query, URL, handle, or + secret. + """ + reason: CatalogUnavailableReason + """Why the operation is unavailable.""" + + @staticmethod + def from_dict(obj: Any) -> 'CatalogUnavailableError': + assert isinstance(obj, dict) + message = from_str(obj.get("message")) + reason = CatalogUnavailableReason(obj.get("reason")) + return CatalogUnavailableError(message, reason) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = self.kind + result["message"] = from_str(self.message) + result["reason"] = to_enum(CatalogUnavailableReason, self.reason) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class CatalogUnavailableTransportError: + """No transport this runtime can use is available for the requested server.""" + + kind: ClassVar[str] = "unavailable-transport" + """Discriminator: no usable transport is available""" + + message: str + """Human-readable explanation, safe to surface. Never contains a query, URL, handle, or + secret. + """ + reason: CatalogUnavailableTransportReason + """Why no transport could be offered.""" + + @staticmethod + def from_dict(obj: Any) -> 'CatalogUnavailableTransportError': + assert isinstance(obj, dict) + message = from_str(obj.get("message")) + reason = CatalogUnavailableTransportReason(obj.get("reason")) + return CatalogUnavailableTransportError(message, reason) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = self.kind + result["message"] = from_str(self.message) + result["reason"] = to_enum(CatalogUnavailableTransportReason, self.reason) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class CatalogUnsafeRetrievalError: + """Retrieval was refused by the runtime's hardened fetch boundary before any request left + the process, or before a redirect was followed. + """ + kind: ClassVar[str] = "unsafe-retrieval" + """Discriminator: retrieval was refused as unsafe""" + + message: str + """Human-readable explanation, safe to surface. Never contains the refused URL, nor a query, + handle, or secret. + """ + reason: CatalogUnsafeRetrievalReason + """Which control refused the retrieval, low cardinality so it can be aggregated without + carrying a URL. + """ + + @staticmethod + def from_dict(obj: Any) -> 'CatalogUnsafeRetrievalError': + assert isinstance(obj, dict) + message = from_str(obj.get("message")) + reason = CatalogUnsafeRetrievalReason(obj.get("reason")) + return CatalogUnsafeRetrievalError(message, reason) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = self.kind + result["message"] = from_str(self.message) + result["reason"] = to_enum(CatalogUnsafeRetrievalReason, self.reason) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class CatalogUnsupportedKindError: + """The request asked for a candidate kind this runtime does not serve.""" + + kind: ClassVar[str] = "unsupported-kind" + """Discriminator: an unsupported candidate kind was requested""" + + message: str + """Human-readable explanation, safe to surface. Never contains a query, URL, handle, or + secret. + """ + requested_kinds: list[CatalogCandidateKind] + """The kinds from the request that are not supported.""" + + supported_kinds: list[CatalogCandidateKind] + """Every candidate kind this runtime can serve.""" + + @staticmethod + def from_dict(obj: Any) -> 'CatalogUnsupportedKindError': + assert isinstance(obj, dict) + message = from_str(obj.get("message")) + requested_kinds = from_list(CatalogCandidateKind, obj.get("requestedKinds")) + supported_kinds = from_list(CatalogCandidateKind, obj.get("supportedKinds")) + return CatalogUnsupportedKindError(message, requested_kinds, supported_kinds) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = self.kind + result["message"] = from_str(self.message) + result["requestedKinds"] = from_list(lambda x: to_enum(CatalogCandidateKind, x), self.requested_kinds) + result["supportedKinds"] = from_list(lambda x: to_enum(CatalogCandidateKind, x), self.supported_kinds) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SlashCommandInput: + """Optional unstructured input hint""" + + hint: str + """Hint to display when command input has not been provided""" + + choices: list[SlashCommandInputChoice] | None = None + """Optional literal choices the input accepts, each with a human-facing description; clients + may render these as selectable options + """ + completion: SlashCommandInputCompletion | None = None + """Optional completion hint for the input (e.g. 'directory' for filesystem path completion)""" + + preserve_multiline_input: bool | None = None + """When true, clients should pass the full text after the command name as a single argument + rather than splitting on whitespace + """ + required: bool | None = None + """When true, the command requires non-empty input; clients should render the input hint as + required + """ + + @staticmethod + def from_dict(obj: Any) -> 'SlashCommandInput': + assert isinstance(obj, dict) + hint = from_str(obj.get("hint")) + choices = from_union([lambda x: from_list(SlashCommandInputChoice.from_dict, x), from_none], obj.get("choices")) + completion = from_union([SlashCommandInputCompletion, from_none], obj.get("completion")) + preserve_multiline_input = from_union([from_bool, from_none], obj.get("preserveMultilineInput")) + required = from_union([from_bool, from_none], obj.get("required")) + return SlashCommandInput(hint, choices, completion, preserve_multiline_input, required) + + def to_dict(self) -> dict: + result: dict = {} + result["hint"] = from_str(self.hint) + if self.choices is not None: + result["choices"] = from_union([lambda x: from_list(lambda x: to_class(SlashCommandInputChoice, x), x), from_none], self.choices) + if self.completion is not None: + result["completion"] = from_union([lambda x: to_enum(SlashCommandInputCompletion, x), from_none], self.completion) + if self.preserve_multiline_input is not None: + result["preserveMultilineInput"] = from_union([from_bool, from_none], self.preserve_multiline_input) + if self.required is not None: + result["required"] = from_union([from_bool, from_none], self.required) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class PushAttachmentDirectory: + """Directory attachment""" + + display_name: str + """User-facing display name for the attachment""" + + path: str + """Absolute directory path""" + + type: ClassVar[str] = "directory" + """Attachment type discriminator""" + + @staticmethod + def from_dict(obj: Any) -> 'PushAttachmentDirectory': + assert isinstance(obj, dict) + display_name = from_str(obj.get("displayName")) + path = from_str(obj.get("path")) + return PushAttachmentDirectory(display_name, path) + + def to_dict(self) -> dict: + result: dict = {} + result["displayName"] = from_str(self.display_name) + result["path"] = from_str(self.path) + result["type"] = self.type + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class CommandsFinalizeInvocationEffectRequest: + """The pending slash-command invocation effect to finalize, plus whether the host applied or + cancelled it. + """ + effect: dict[str, Any] + """The slash-command result object that produced the pending effect, echoed back unchanged.""" + + outcome: CommandsInvocationEffectOutcome + """Whether the host applied or cancelled the pending invocation effect.""" + + @staticmethod + def from_dict(obj: Any) -> 'CommandsFinalizeInvocationEffectRequest': + assert isinstance(obj, dict) + effect = from_dict(lambda x: x, obj.get("effect")) + outcome = CommandsInvocationEffectOutcome(obj.get("outcome")) + return CommandsFinalizeInvocationEffectRequest(effect, outcome) + + def to_dict(self) -> dict: + result: dict = {} + result["effect"] = from_dict(lambda x: x, self.effect) + result["outcome"] = to_enum(CommandsInvocationEffectOutcome, self.outcome) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class CommandsInvokeRequest: + """Slash command name and optional raw input string to invoke.""" + + name: str + """Command name. Leading slashes are stripped and the name is matched case-insensitively.""" + + input: str | None = None + """Raw input after the command name""" + + origin: CommandsInvocationOrigin | None = None + """Optional client surface that initiated the invocation""" + + @staticmethod + def from_dict(obj: Any) -> 'CommandsInvokeRequest': + assert isinstance(obj, dict) + name = from_str(obj.get("name")) + input = from_union([from_str, from_none], obj.get("input")) + origin = from_union([CommandsInvocationOrigin, from_none], obj.get("origin")) + return CommandsInvokeRequest(name, input, origin) + + def to_dict(self) -> dict: + result: dict = {} + result["name"] = from_str(self.name) + if self.input is not None: + result["input"] = from_union([from_str, from_none], self.input) + if self.origin is not None: + result["origin"] = from_union([lambda x: to_enum(CommandsInvocationOrigin, x), from_none], self.origin) + 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 _ConnectRequest: + """Connection-level opt-ins for the `server.connect` handshake. Transport authentication is + consumed by the native protocol boundary before dispatch. + """ + client_info: _ConnectClientInfo | None = None + """Identity of the integrating host. Optional; omit it to keep the default attribution.""" + + 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. + 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""" + + @staticmethod + def from_dict(obj: Any) -> '_ConnectRequest': + assert isinstance(obj, dict) + client_info = from_union([_ConnectClientInfo.from_dict, from_none], obj.get("clientInfo")) + enable_git_hub_telemetry_forwarding = from_union([from_bool, from_none], obj.get("enableGitHubTelemetryForwarding")) + token = from_union([from_str, from_none], obj.get("token")) + return _ConnectRequest(client_info, enable_git_hub_telemetry_forwarding, token) + + def to_dict(self) -> dict: + result: dict = {} + if self.client_info is not None: + result["clientInfo"] = from_union([lambda x: to_class(_ConnectClientInfo, x), from_none], self.client_info) + if self.enable_git_hub_telemetry_forwarding is not None: + result["enableGitHubTelemetryForwarding"] = from_union([from_bool, from_none], self.enable_git_hub_telemetry_forwarding) + if self.token is not None: + result["token"] = from_union([from_str, from_none], self.token) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ConnectedRemoteSessionMetadata: + """Metadata for a connected remote session.""" + + kind: ConnectedRemoteSessionMetadataKind + """Neutral SDK discriminator for the connected remote session kind.""" + + modified_time: datetime + """Last session update time as an ISO 8601 string.""" + + repository: ConnectedRemoteSessionMetadataRepository + """Repository associated with the connected remote session.""" + + session_id: str + """SDK session ID for the connected remote session.""" + + start_time: datetime + """Session start time as an ISO 8601 string.""" + + name: str | None = None + """Optional friendly session name.""" + + pull_request_number: int | None = None + """Pull request number associated with the session.""" + + resource_id: str | None = None + """Original remote resource identifier.""" + + stale_at: datetime | None = None + """Remote session staleness deadline as an ISO 8601 string.""" + + state: str | None = None + """Remote session state returned by the backing service.""" + + summary: str | None = None + """Optional session summary.""" + + @staticmethod + def from_dict(obj: Any) -> 'ConnectedRemoteSessionMetadata': + assert isinstance(obj, dict) + kind = ConnectedRemoteSessionMetadataKind(obj.get("kind")) + modified_time = from_datetime(obj.get("modifiedTime")) + repository = ConnectedRemoteSessionMetadataRepository.from_dict(obj.get("repository")) + session_id = from_str(obj.get("sessionId")) + start_time = from_datetime(obj.get("startTime")) + name = from_union([from_str, from_none], obj.get("name")) + pull_request_number = from_union([from_int, from_none], obj.get("pullRequestNumber")) + resource_id = from_union([from_str, from_none], obj.get("resourceId")) + stale_at = from_union([from_datetime, from_none], obj.get("staleAt")) + state = from_union([from_str, from_none], obj.get("state")) + summary = from_union([from_str, from_none], obj.get("summary")) + return ConnectedRemoteSessionMetadata(kind, modified_time, repository, session_id, start_time, name, pull_request_number, resource_id, stale_at, state, summary) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = to_enum(ConnectedRemoteSessionMetadataKind, self.kind) + result["modifiedTime"] = self.modified_time.isoformat() + result["repository"] = to_class(ConnectedRemoteSessionMetadataRepository, self.repository) + result["sessionId"] = from_str(self.session_id) + result["startTime"] = self.start_time.isoformat() + if self.name is not None: + result["name"] = from_union([from_str, from_none], self.name) + if self.pull_request_number is not None: + result["pullRequestNumber"] = from_union([from_int, from_none], self.pull_request_number) + if self.resource_id is not None: + result["resourceId"] = from_union([from_str, from_none], self.resource_id) + if self.stale_at is not None: + result["staleAt"] = from_union([lambda x: x.isoformat(), from_none], self.stale_at) + if self.state is not None: + result["state"] = from_union([from_str, from_none], self.state) + if self.summary is not None: + 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: + """The heaviest individual messages in the session's context window, most-expensive first.""" + + messages: list[ContextHeaviestMessage] + """Heaviest messages, most-expensive first.""" + + total_tokens: int + """Total token count of the current context window, so callers can compute each message's + share without a second call. + """ + + @staticmethod + def from_dict(obj: Any) -> 'MetadataContextHeaviestMessagesResult': + assert isinstance(obj, dict) + messages = from_list(ContextHeaviestMessage.from_dict, obj.get("messages")) + total_tokens = from_int(obj.get("totalTokens")) + return MetadataContextHeaviestMessagesResult(messages, total_tokens) + + def to_dict(self) -> dict: + result: dict = {} + result["messages"] = from_list(lambda x: to_class(ContextHeaviestMessage, x), self.messages) + result["totalTokens"] = from_int(self.total_tokens) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class CanvasHostContextCapabilities: + """Host capabilities""" + + canvases: bool | None = None + """Whether canvas rendering is supported""" + + @staticmethod + def from_dict(obj: Any) -> 'CanvasHostContextCapabilities': + assert isinstance(obj, dict) + canvases = from_union([from_bool, from_none], obj.get("canvases")) + return CanvasHostContextCapabilities(canvases) + + def to_dict(self) -> dict: + result: dict = {} + if self.canvases is not None: + result["canvases"] = from_union([from_bool, from_none], self.canvases) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class DiscoveredCanvas: + """Canvas available in the current session.""" + + canvas_id: str + """Provider-local canvas identifier""" + + description: str + """Short, single-sentence description shown to the agent in canvas catalogs.""" display_name: str """Human-readable canvas name""" @@ -13104,6 +16239,119 @@ def to_dict(self) -> dict: result["url"] = from_union([from_str, from_none], self.url) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class CatalogAISkillCandidateProvenance: + """Where the catalog reference was observed, without the card itself or any content digest. + + Where and when an AI skill catalog reference was observed. Discovery provenance + deliberately carries no content digest because search does not establish the exact + validated content a later plan will bind. + """ + authority: str + """Host of the catalog authority that advertised the reference, without path, query, or + credentials. Inert untrusted data. + """ + media_type: MediaType + """Media type advertised for the referenced AI skill card""" + + observed_at: str + """ISO 8601 timestamp at which the runtime observed the catalog reference. This is not a + retrieval or validation timestamp. + """ + + @staticmethod + def from_dict(obj: Any) -> 'CatalogAISkillCandidateProvenance': + assert isinstance(obj, dict) + authority = from_str(obj.get("authority")) + media_type = MediaType(obj.get("mediaType")) + observed_at = from_str(obj.get("observedAt")) + return CatalogAISkillCandidateProvenance(authority, media_type, observed_at) + + def to_dict(self) -> dict: + result: dict = {} + result["authority"] = from_str(self.authority) + result["mediaType"] = to_enum(MediaType, self.media_type) + result["observedAt"] = from_str(self.observed_at) + return result + +@dataclass +class CatalogCandidateProvenance: + """Where the catalog reference was observed, without the card itself or any content digest. + + Where and when an MCP server catalog reference was observed. Discovery provenance + deliberately carries no content digest because search does not establish the exact + validated content a later plan will bind. + + Where and when an AI skill catalog reference was observed. Discovery provenance + deliberately carries no content digest because search does not establish the exact + validated content a later plan will bind. + """ + authority: str + """Host of the catalog authority that advertised the reference, without path, query, or + credentials. Inert untrusted data. + """ + media_type: CatalogMediaType + """JSON MCP media type advertised for the referenced card. + + Media type advertised for the referenced AI skill card + """ + observed_at: str + """ISO 8601 timestamp at which the runtime observed the catalog reference. This is not a + retrieval or validation timestamp. + """ + + @staticmethod + def from_dict(obj: Any) -> 'CatalogCandidateProvenance': + assert isinstance(obj, dict) + authority = from_str(obj.get("authority")) + media_type = CatalogMediaType(obj.get("mediaType")) + observed_at = from_str(obj.get("observedAt")) + return CatalogCandidateProvenance(authority, media_type, observed_at) + + def to_dict(self) -> dict: + result: dict = {} + result["authority"] = from_str(self.authority) + result["mediaType"] = to_enum(CatalogMediaType, self.media_type) + result["observedAt"] = from_str(self.observed_at) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class CatalogMCPServerCandidateProvenance: + """Where the catalog reference was observed, without the card itself or any content digest. + + Where and when an MCP server catalog reference was observed. Discovery provenance + deliberately carries no content digest because search does not establish the exact + validated content a later plan will bind. + """ + authority: str + """Host of the catalog authority that advertised the reference, without path, query, or + credentials. Inert untrusted data. + """ + media_type: MCPServerCardMediaType + """JSON MCP media type advertised for the referenced card.""" + + observed_at: str + """ISO 8601 timestamp at which the runtime observed the catalog reference. This is not a + retrieval or validation timestamp. + """ + + @staticmethod + def from_dict(obj: Any) -> 'CatalogMCPServerCandidateProvenance': + assert isinstance(obj, dict) + authority = from_str(obj.get("authority")) + media_type = MCPServerCardMediaType(obj.get("mediaType")) + observed_at = from_str(obj.get("observedAt")) + return CatalogMCPServerCandidateProvenance(authority, media_type, observed_at) + + def to_dict(self) -> dict: + result: dict = {} + result["authority"] = from_str(self.authority) + result["mediaType"] = to_enum(MCPServerCardMediaType, self.media_type) + result["observedAt"] = from_str(self.observed_at) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class CompletionsRequestResult: @@ -13162,6 +16410,8 @@ class DebugCollectLogsDestination: `directory` to stage redacted files for caller-managed upload/post-processing. """ kind: DebugCollectLogsResultKind + """Destination variant discriminator.""" + no_overwrite: bool | None = None """When true, create the archive atomically without overwriting an existing file by appending ` (N)` before the extension as needed. Defaults to false. @@ -13237,6 +16487,132 @@ def to_dict(self) -> dict: result["plugin"] = from_union([lambda x: to_class(DiscoveredExtensionPlugin, x), from_none], self.plugin) return result +# Internal: this type is an internal SDK API and is not part of the public surface. +@dataclass +class _HookInvokeRequest: + """Runtime-owned wire payload for a server-to-client hook callback invocation.""" + + hook_type: HookType + input: Any + session_id: str + + @staticmethod + def from_dict(obj: Any) -> '_HookInvokeRequest': + assert isinstance(obj, dict) + hook_type = HookType(obj.get("hookType")) + input = obj.get("input") + session_id = from_str(obj.get("sessionId")) + return _HookInvokeRequest(hook_type, input, session_id) + + def to_dict(self) -> dict: + result: dict = {} + result["hookType"] = to_enum(HookType, self.hook_type) + result["input"] = self.input + 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 DiscoveredHook: + """One server-discovered hook action from user, repository, plugin, or managed-policy + configuration. + """ + enabled: bool + """Whether this action is enabled under the server-side discovery settings. Concrete + sessions may differ because they can add session-specific directories, plugins, or trust. + False when its disable key is present in the user's disabled-hooks setting or disable-all + settings suppress the action. + """ + hook_type: HookType + """Hook event that invokes this action.""" + + id: str + """Deterministic identifier for this server-discovered action row. It remains stable while + the project, origin, source, event, action content, and duplicate ordinal are unchanged. + This is row identity, not the key persisted in disabledHooks. + """ + origin: HookOrigin + """Configuration tier that contributed this hook action.""" + + disable_key: str | None = None + """Durable content hash used by hook enablement. Identical actions may intentionally share + this key. Omitted when changing the user's disabled-hooks setting cannot change the + action's current server-discovered state, including managed-policy hooks, session-start + prompt actions, actions suppressed by disable-all settings, and projectless plugin + actions that require project-directory expansion. + """ + project_path: str | None = None + """Input project path for which this server-side action was resolved. Set on every row + returned for project-scoped discovery, including repeated user and policy actions. + """ + source: str | None = None + """Human-readable source label, such as a hook file path, settings source, or plugin name.""" + + @staticmethod + def from_dict(obj: Any) -> 'DiscoveredHook': + assert isinstance(obj, dict) + enabled = from_bool(obj.get("enabled")) + hook_type = HookType(obj.get("hookType")) + id = from_str(obj.get("id")) + origin = HookOrigin(obj.get("origin")) + disable_key = from_union([from_str, from_none], obj.get("disableKey")) + project_path = from_union([from_str, from_none], obj.get("projectPath")) + source = from_union([from_str, from_none], obj.get("source")) + return DiscoveredHook(enabled, hook_type, id, origin, disable_key, project_path, source) + + def to_dict(self) -> dict: + result: dict = {} + result["enabled"] = from_bool(self.enabled) + result["hookType"] = to_enum(HookType, self.hook_type) + result["id"] = from_str(self.id) + result["origin"] = to_enum(HookOrigin, self.origin) + if self.disable_key is not None: + result["disableKey"] = from_union([from_str, from_none], self.disable_key) + if self.project_path is not None: + result["projectPath"] = from_union([from_str, from_none], self.project_path) + if self.source is not None: + 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. +@dataclass +class SessionsReadPersistedEventsRequest: + """Pagination options for reading an inactive or active local session's persisted event + journal. + """ + session_id: str + """Session ID whose persisted event journal should be read.""" + + cursor: str | None = None + """Opaque cursor returned by a previous persisted-event read. Omit on the first call.""" + + direction: EventsReadDirection | None = None + """Direction to page through persisted history. Forward starts at the beginning; backward + starts with the newest events. Events in each page remain chronological. + """ + max: int | None = None + """Maximum number of events to return in this batch (1–1000, default 200).""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionsReadPersistedEventsRequest': + assert isinstance(obj, dict) + session_id = from_str(obj.get("sessionId")) + cursor = from_union([from_str, from_none], obj.get("cursor")) + direction = from_union([EventsReadDirection, from_none], obj.get("direction")) + max = from_union([from_int, from_none], obj.get("max")) + return SessionsReadPersistedEventsRequest(session_id, cursor, direction, max) + + def to_dict(self) -> dict: + result: dict = {} + result["sessionId"] = from_str(self.session_id) + 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.max is not None: + result["max"] = from_union([from_int, from_none], self.max) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class EventLogReadRequest: @@ -13382,6 +16758,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: @@ -13453,6 +16864,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: @@ -13673,6 +17105,10 @@ class ExternalToolTextResultForLlmContentShellExit: cwd: str | None = None """Working directory where the shell command was executed""" + output_file_path: str | None = None + """Path reported in the shell session's filesystem namespace when shell output exceeded the + configured large-output threshold. + """ output_preview: str | None = None """Output associated with this shell command, if available. May be partial, truncated, or a preview; not guaranteed to be full output. @@ -13686,9 +17122,10 @@ def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmContentShellExit': exit_code = from_int(obj.get("exitCode")) shell_id = from_str(obj.get("shellId")) cwd = from_union([from_str, from_none], obj.get("cwd")) + output_file_path = from_union([from_str, from_none], obj.get("outputFilePath")) output_preview = from_union([from_str, from_none], obj.get("outputPreview")) output_truncated = from_union([from_bool, from_none], obj.get("outputTruncated")) - return ExternalToolTextResultForLlmContentShellExit(exit_code, shell_id, cwd, output_preview, output_truncated) + return ExternalToolTextResultForLlmContentShellExit(exit_code, shell_id, cwd, output_file_path, output_preview, output_truncated) def to_dict(self) -> dict: result: dict = {} @@ -13697,6 +17134,8 @@ def to_dict(self) -> dict: result["type"] = self.type if self.cwd is not None: result["cwd"] = from_union([from_str, from_none], self.cwd) + if self.output_file_path is not None: + result["outputFilePath"] = from_union([from_str, from_none], self.output_file_path) if self.output_preview is not None: result["outputPreview"] = from_union([from_str, from_none], self.output_preview) if self.output_truncated is not None: @@ -13841,9 +17280,16 @@ 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. + """Machine-readable terminal failure. + + Machine-readable factory run failure. - Machine-readable failure details for an errored run. + Machine-readable failure details for a halted or errored run. + + The run stopped because its usage accounting could not be completed. + + The extension that owns the factory disconnected while the run was executing, so the host + halted it. The run's journaled subagent results are preserved so a resume can reuse them. """ run_id: str """Factory run identifier. @@ -13851,6 +17297,8 @@ class FactoryRunFailure: Factory run identifier whose changed limits were declined. """ type: FactoryRunFailureType + """Factory failure variant discriminator.""" + kind: FactoryRunFailureKind | None = None """Resource ceiling that stopped the run.""" @@ -13866,6 +17314,9 @@ class FactoryRunFailure: 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) @@ -13876,7 +17327,8 @@ def from_dict(obj: Any) -> 'FactoryRunFailure': 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")) - return FactoryRunFailure(run_id, type, kind, value, reason, code, 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 = {} @@ -13892,6 +17344,8 @@ def to_dict(self) -> dict: 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. @@ -13973,18 +17427,47 @@ class FactoryPhaseObservation: """Durable lifecycle and timing for one factory phase.""" accumulated_active_ms: int + """Completed active time accumulated by this phase in milliseconds.""" + current_active_ms: int + """Current live active time for this phase in milliseconds.""" + entry_count: int + """Number of times execution entered this phase.""" + id: str + """Phase identifier.""" + last_entered_run_attempt: int + """Most recent run attempt that entered this phase, or `0` if the phase has never been + entered. + """ live_agent_count: int + """Direct agents in this phase that are currently live.""" + status: FactoryPhaseStatus + """Derived lifecycle state of the phase.""" + title: str + """Human-readable phase title.""" + total_agent_count: int + """Total direct agents associated with this phase.""" + completed_at: int | None = None + """Epoch milliseconds when this phase completed; for a skipped phase, the synthetic skip + timestamp (equal to `startedAt`). + """ detail: str | None = None + """Optional human-readable phase detail.""" + ordinal: int | None = None + """Zero-based declared phase ordinal, or null for an undeclared phase.""" + started_at: int | None = None + """Epoch milliseconds when this phase first started; for a skipped phase, the synthetic skip + timestamp (equal to `completedAt`). + """ @staticmethod def from_dict(obj: Any) -> 'FactoryPhaseObservation': @@ -14035,18 +17518,30 @@ class FactoryResumeRequest: limits: FactoryRunLimits | None = None """Optional per-invocation resource ceiling overrides.""" + log_phase_names: bool | None = None + """Whether to emit factory phase names to the session transcript.""" + + notify_on_complete: bool | None = None + """Whether to notify the originating session when the factory completes.""" + @staticmethod def from_dict(obj: Any) -> 'FactoryResumeRequest': assert isinstance(obj, dict) run_id = from_str(obj.get("runId")) limits = from_union([FactoryRunLimits.from_dict, from_none], obj.get("limits")) - return FactoryResumeRequest(run_id, limits) + log_phase_names = from_union([from_bool, from_none], obj.get("logPhaseNames")) + notify_on_complete = from_union([from_bool, from_none], obj.get("notifyOnComplete")) + return FactoryResumeRequest(run_id, limits, log_phase_names, notify_on_complete) def to_dict(self) -> dict: result: dict = {} result["runId"] = from_str(self.run_id) if self.limits is not None: result["limits"] = from_union([lambda x: to_class(FactoryRunLimits, x), from_none], self.limits) + if self.log_phase_names is not None: + result["logPhaseNames"] = from_union([from_bool, from_none], self.log_phase_names) + if self.notify_on_complete is not None: + result["notifyOnComplete"] = from_union([from_bool, from_none], self.notify_on_complete) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -14059,6 +17554,12 @@ class RunOptions: limits: FactoryRunLimits | None = None """Per-invocation resource ceiling overrides.""" + log_phase_names: bool | None = None + """Whether to emit factory phase names to the session transcript.""" + + notify_on_complete: bool | None = None + """Whether to notify the originating session when the factory completes.""" + resume_from_run_id: str | None = None """Run identifier whose journal and progress should seed this resumed run.""" @@ -14066,17 +17567,165 @@ class RunOptions: def from_dict(obj: Any) -> 'RunOptions': assert isinstance(obj, dict) limits = from_union([FactoryRunLimits.from_dict, from_none], obj.get("limits")) + log_phase_names = from_union([from_bool, from_none], obj.get("logPhaseNames")) + notify_on_complete = from_union([from_bool, from_none], obj.get("notifyOnComplete")) resume_from_run_id = from_union([from_str, from_none], obj.get("resumeFromRunId")) - return RunOptions(limits, resume_from_run_id) + return RunOptions(limits, log_phase_names, notify_on_complete, resume_from_run_id) def to_dict(self) -> dict: result: dict = {} if self.limits is not None: result["limits"] = from_union([lambda x: to_class(FactoryRunLimits, x), from_none], self.limits) + if self.log_phase_names is not None: + result["logPhaseNames"] = from_union([from_bool, from_none], self.log_phase_names) + if self.notify_on_complete is not None: + result["notifyOnComplete"] = from_union([from_bool, from_none], self.notify_on_complete) if self.resume_from_run_id is not None: result["resumeFromRunId"] = from_union([from_str, from_none], self.resume_from_run_id) 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 _FactoryToolResumeRequest: + """Internal parameters for resuming a factory run from a tool.""" + + run_id: str + """Factory run identifier.""" + + limits: FactoryRunLimits | None = None + """Optional per-invocation resource ceiling overrides.""" + + tool_call_id: str | None = None + """Opaque identifier of the originating tool call.""" + + @staticmethod + def from_dict(obj: Any) -> '_FactoryToolResumeRequest': + assert isinstance(obj, dict) + run_id = from_str(obj.get("runId")) + limits = from_union([FactoryRunLimits.from_dict, from_none], obj.get("limits")) + tool_call_id = from_union([from_str, from_none], obj.get("toolCallId")) + return _FactoryToolResumeRequest(run_id, limits, tool_call_id) + + def to_dict(self) -> dict: + result: dict = {} + result["runId"] = from_str(self.run_id) + if self.limits is not None: + result["limits"] = from_union([lambda x: to_class(FactoryRunLimits, x), from_none], self.limits) + if self.tool_call_id is not None: + result["toolCallId"] = from_union([from_str, from_none], self.tool_call_id) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class GitHubTokenAcquireRequest: + """Asks the SDK client to acquire a GitHub access token from an opaque callback registration.""" + + host: str + """Effective GitHub host for which the callback must return a token.""" + + reason: GitHubTokenAcquireReason + """Why the runtime is requesting a GitHub credential.""" + + registration_id: str + """Opaque identifier generated by the SDK for this callback registration.""" + + session_id: str | None = None + """Session receiving the token. Absent only before a cloud session has been assigned its id.""" + + @staticmethod + def from_dict(obj: Any) -> 'GitHubTokenAcquireRequest': + assert isinstance(obj, dict) + host = from_str(obj.get("host")) + reason = GitHubTokenAcquireReason(obj.get("reason")) + registration_id = from_str(obj.get("registrationId")) + session_id = from_union([from_str, from_none], obj.get("sessionId")) + return GitHubTokenAcquireRequest(host, reason, registration_id, session_id) + + def to_dict(self) -> dict: + result: dict = {} + result["host"] = from_str(self.host) + result["reason"] = to_enum(GitHubTokenAcquireReason, self.reason) + result["registrationId"] = from_str(self.registration_id) + if self.session_id is not None: + 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 GitHubTokenAcquireResult: + """SDK host response to a GitHub credential request.""" + + kind: GitHubTokenAcquireResultKind + """GitHub credential response variant discriminator.""" + + access_token: str | None = None + """GitHub access token acquired by the SDK host.""" + + expires_in: int | None = None + """Remaining token lifetime in seconds when callback execution completes. It must exceed the + one-hour preflight refresh threshold. + """ + token_type: str | None = None + """OAuth token type. Defaults to bearer when omitted.""" + + @staticmethod + def from_dict(obj: Any) -> 'GitHubTokenAcquireResult': + assert isinstance(obj, dict) + kind = GitHubTokenAcquireResultKind(obj.get("kind")) + access_token = from_union([from_str, from_none], obj.get("accessToken")) + expires_in = from_union([from_int, from_none], obj.get("expiresIn")) + token_type = from_union([from_str, from_none], obj.get("tokenType")) + return GitHubTokenAcquireResult(kind, access_token, expires_in, token_type) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = to_enum(GitHubTokenAcquireResultKind, self.kind) + if self.access_token is not None: + result["accessToken"] = from_union([from_str, from_none], self.access_token) + if self.expires_in is not None: + result["expiresIn"] = from_union([from_int, from_none], self.expires_in) + if self.token_type is not None: + result["tokenType"] = from_union([from_str, from_none], self.token_type) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MCPOauthPendingRequestResponse: + """Host response to the pending OAuth request.""" + + kind: GitHubTokenAcquireResultKind + """OAuth response variant discriminator.""" + + access_token: str | None = None + """Access token acquired by the SDK host""" + + expires_in: int | None = None + """Token lifetime in seconds, if known.""" + + token_type: str | None = None + """OAuth token type. Defaults to bearer when omitted.""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPOauthPendingRequestResponse': + assert isinstance(obj, dict) + kind = GitHubTokenAcquireResultKind(obj.get("kind")) + access_token = from_union([from_str, from_none], obj.get("accessToken")) + expires_in = from_union([from_int, from_none], obj.get("expiresIn")) + token_type = from_union([from_str, from_none], obj.get("tokenType")) + return MCPOauthPendingRequestResponse(kind, access_token, expires_in, token_type) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = to_enum(GitHubTokenAcquireResultKind, self.kind) + if self.access_token is not None: + result["accessToken"] = from_union([from_str, from_none], self.access_token) + if self.expires_in is not None: + result["expiresIn"] = from_union([from_int, from_none], self.expires_in) + if self.token_type is not None: + result["tokenType"] = from_union([from_str, from_none], self.token_type) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class HistoryCompactResult: @@ -14242,30 +17891,6 @@ def to_dict(self) -> dict: 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: - """Runtime-owned wire payload for a server-to-client hook callback invocation.""" - - hook_type: _HookType - input: Any - session_id: str - - @staticmethod - def from_dict(obj: Any) -> '_HookInvokeRequest': - assert isinstance(obj, dict) - hook_type = _HookType(obj.get("hookType")) - input = obj.get("input") - session_id = from_str(obj.get("sessionId")) - return _HookInvokeRequest(hook_type, input, session_id) - - def to_dict(self) -> dict: - result: dict = {} - result["hookType"] = to_enum(_HookType, self.hook_type) - result["input"] = self.input - 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 InstalledPluginSource: @@ -14277,7 +17902,7 @@ class InstalledPluginSource: Source descriptor for a direct local plugin install, with a local filesystem path. """ - source: PurpleSource + source: InstalledPluginSourceURLSource """Constant value. Always "github". Constant value. Always "url". @@ -14285,17 +17910,28 @@ class InstalledPluginSource: Constant value. Always "local". """ path: str | None = None + """Optional repository-relative path to the plugin. + + Optional source-relative path to the plugin. + + Local filesystem path to the plugin. + """ ref: str | None = None + """Optional Git ref to resolve.""" + repo: str | None = None + """GitHub repository in `owner/repo` form.""" + sha: str | None = None """Optional full 40-character hexadecimal commit SHA.""" url: str | None = None + """URL of the plugin source.""" @staticmethod def from_dict(obj: Any) -> 'InstalledPluginSource': assert isinstance(obj, dict) - source = PurpleSource(obj.get("source")) + source = InstalledPluginSourceURLSource(obj.get("source")) 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")) @@ -14305,7 +17941,7 @@ def from_dict(obj: Any) -> 'InstalledPluginSource': def to_dict(self) -> dict: result: dict = {} - result["source"] = to_enum(PurpleSource, self.source) + result["source"] = to_enum(InstalledPluginSourceURLSource, self.source) if self.path is not None: result["path"] = from_union([from_str, from_none], self.path) if self.ref is not None: @@ -14329,7 +17965,7 @@ class SessionInstalledPluginSource: Source descriptor for a direct local plugin install, with a local filesystem path. """ - source: PurpleSource + source: InstalledPluginSourceURLSource """Constant value. Always "github". Constant value. Always "url". @@ -14337,17 +17973,28 @@ class SessionInstalledPluginSource: Constant value. Always "local". """ path: str | None = None + """Optional repository-relative path to the plugin. + + Optional source-relative path to the plugin. + + Local filesystem path to the plugin. + """ ref: str | None = None + """Optional Git ref to resolve.""" + repo: str | None = None + """GitHub repository in `owner/repo` form.""" + sha: str | None = None """Optional full 40-character hexadecimal commit SHA.""" url: str | None = None + """URL of the plugin source.""" @staticmethod def from_dict(obj: Any) -> 'SessionInstalledPluginSource': assert isinstance(obj, dict) - source = PurpleSource(obj.get("source")) + source = InstalledPluginSourceURLSource(obj.get("source")) 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")) @@ -14357,7 +18004,7 @@ def from_dict(obj: Any) -> 'SessionInstalledPluginSource': def to_dict(self) -> dict: result: dict = {} - result["source"] = to_enum(PurpleSource, self.source) + result["source"] = to_enum(InstalledPluginSourceURLSource, self.source) if self.path is not None: result["path"] = from_union([from_str, from_none], self.path) if self.ref is not None: @@ -14377,11 +18024,17 @@ class InstalledPluginSourceGitHub: full commit SHA, and optional subpath. """ repo: str - source: FluffySource + """GitHub repository in `owner/repo` form.""" + + source: PurpleSource """Constant value. Always "github".""" path: str | None = None + """Optional repository-relative path to the plugin.""" + ref: str | None = None + """Optional Git ref to resolve.""" + sha: str | None = None """Optional full 40-character hexadecimal commit SHA.""" @@ -14389,7 +18042,7 @@ class InstalledPluginSourceGitHub: def from_dict(obj: Any) -> 'InstalledPluginSourceGitHub': assert isinstance(obj, dict) repo = from_str(obj.get("repo")) - source = FluffySource(obj.get("source")) + source = PurpleSource(obj.get("source")) path = from_union([from_str, from_none], obj.get("path")) ref = from_union([from_str, from_none], obj.get("ref")) sha = from_union([from_str, from_none], obj.get("sha")) @@ -14398,7 +18051,7 @@ def from_dict(obj: Any) -> 'InstalledPluginSourceGitHub': def to_dict(self) -> dict: result: dict = {} result["repo"] = from_str(self.repo) - result["source"] = to_enum(FluffySource, self.source) + result["source"] = to_enum(PurpleSource, self.source) if self.path is not None: result["path"] = from_union([from_str, from_none], self.path) if self.ref is not None: @@ -14414,11 +18067,17 @@ class SessionInstalledPluginSourceGitHub: full commit SHA, and optional subpath. """ repo: str - source: FluffySource + """GitHub repository in `owner/repo` form.""" + + source: PurpleSource """Constant value. Always "github".""" path: str | None = None + """Optional repository-relative path to the plugin.""" + ref: str | None = None + """Optional Git ref to resolve.""" + sha: str | None = None """Optional full 40-character hexadecimal commit SHA.""" @@ -14426,7 +18085,7 @@ class SessionInstalledPluginSourceGitHub: def from_dict(obj: Any) -> 'SessionInstalledPluginSourceGitHub': assert isinstance(obj, dict) repo = from_str(obj.get("repo")) - source = FluffySource(obj.get("source")) + source = PurpleSource(obj.get("source")) path = from_union([from_str, from_none], obj.get("path")) ref = from_union([from_str, from_none], obj.get("ref")) sha = from_union([from_str, from_none], obj.get("sha")) @@ -14435,7 +18094,7 @@ def from_dict(obj: Any) -> 'SessionInstalledPluginSourceGitHub': def to_dict(self) -> dict: result: dict = {} result["repo"] = from_str(self.repo) - result["source"] = to_enum(FluffySource, self.source) + result["source"] = to_enum(PurpleSource, self.source) if self.path is not None: result["path"] = from_union([from_str, from_none], self.path) if self.ref is not None: @@ -14450,20 +18109,22 @@ class InstalledPluginSourceLocal: """Source descriptor for a direct local plugin install, with a local filesystem path.""" path: str - source: TentacledSource + """Local filesystem path to the plugin.""" + + source: FluffySource """Constant value. Always "local".""" @staticmethod def from_dict(obj: Any) -> 'InstalledPluginSourceLocal': assert isinstance(obj, dict) path = from_str(obj.get("path")) - source = TentacledSource(obj.get("source")) + source = FluffySource(obj.get("source")) return InstalledPluginSourceLocal(path, source) def to_dict(self) -> dict: result: dict = {} result["path"] = from_str(self.path) - result["source"] = to_enum(TentacledSource, self.source) + result["source"] = to_enum(FluffySource, self.source) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -14472,94 +18133,22 @@ class SessionInstalledPluginSourceLocal: """Source descriptor for a direct local plugin install, with a local filesystem path.""" path: str - source: TentacledSource + """Local filesystem path to the plugin.""" + + source: FluffySource """Constant value. Always "local".""" @staticmethod def from_dict(obj: Any) -> 'SessionInstalledPluginSourceLocal': assert isinstance(obj, dict) path = from_str(obj.get("path")) - source = TentacledSource(obj.get("source")) + source = FluffySource(obj.get("source")) return SessionInstalledPluginSourceLocal(path, source) def to_dict(self) -> dict: result: dict = {} result["path"] = from_str(self.path) - result["source"] = to_enum(TentacledSource, self.source) - return result - -# 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 or full commit - SHA, and optional subpath. - """ - source: StickySource - """Constant value. Always "url".""" - - 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': - assert isinstance(obj, dict) - source = StickySource(obj.get("source")) - 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")) - sha = from_union([from_str, from_none], obj.get("sha")) - return InstalledPluginSourceURL(source, url, path, ref, sha) - - def to_dict(self) -> dict: - result: dict = {} - result["source"] = to_enum(StickySource, self.source) - result["url"] = from_str(self.url) - 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.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 or full commit - SHA, and optional subpath. - """ - source: StickySource - """Constant value. Always "url".""" - - 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': - assert isinstance(obj, dict) - source = StickySource(obj.get("source")) - 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")) - sha = from_union([from_str, from_none], obj.get("sha")) - return SessionInstalledPluginSourceURL(source, url, path, ref, sha) - - def to_dict(self) -> dict: - result: dict = {} - result["source"] = to_enum(StickySource, self.source) - result["url"] = from_str(self.url) - 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.sha is not None: - result["sha"] = from_union([from_str, from_none], self.sha) + result["source"] = to_enum(FluffySource, self.source) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -14640,6 +18229,8 @@ class LlmInferenceHTTPRequestStartRequest: """The head of an outbound model-layer HTTP request.""" headers: dict[str, list[str]] + """HTTP request headers, preserving multiple values per name.""" + method: str """HTTP method, e.g. GET, POST.""" @@ -14892,24 +18483,55 @@ def to_dict(self) -> dict: @dataclass class Workspace: id: str + """Stable workspace identifier.""" + branch: str | None = None + """Current Git branch.""" + chronicle_sync_dismissed: bool | None = None + """Whether the per-session Chronicle upgrade prompt was dismissed for the workspace.""" + client_name: str | None = None + """Name of the client that created the workspace.""" + created_at: datetime | None = None + """Timestamp when the workspace was created.""" + cwd: str | None = None + """Current working directory associated with the workspace.""" + git_root: str | None = None + """Git repository root associated with the workspace.""" + host_type: HostType | None = None """Allowed values for the `WorkspacesWorkspaceDetailsHostType` enumeration.""" mc_last_event_id: str | None = None + """Most recent Mission Control event identifier observed for the workspace.""" + mc_session_id: str | None = None + """Mission Control session identifier associated with the workspace.""" + mc_task_id: str | None = None + """Mission Control task identifier associated with the workspace.""" + name: str | None = None + """Workspace display name.""" + remote_steerable: bool | None = None + """Whether the workspace session can be steered remotely.""" + repository: str | None = None + """Repository identifier associated with the workspace.""" + summary_count: int | None = None + """Number of persisted summaries in the workspace.""" + updated_at: datetime | None = None + """Timestamp when the workspace was last updated.""" + user_named: bool | None = None + """Whether the workspace name was explicitly chosen by the user.""" @staticmethod def from_dict(obj: Any) -> 'Workspace': @@ -15215,95 +18837,6 @@ def to_dict(self) -> dict: result["contents"] = from_list(lambda x: to_class(MCPAppsResourceContent, x), self.contents) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class MCPServerConfigStdio: - """Stdio MCP server configuration launched as a child process.""" - - command: str - """Executable command used to start the Stdio MCP server process.""" - - args: list[str] | None = None - """Command-line arguments passed to the Stdio MCP server process.""" - - auth: bool | MCPServerAuthConfigRedirectPort | None = None - """Set to `true` to use defaults, or provide an object with additional auth or OIDC settings.""" - - cwd: str | None = None - """Working directory for the Stdio MCP server process.""" - - defer_tools: MCPServerConfigDeferTools | None = None - """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.""" - - 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. - """ - is_default_server: bool | None = None - """Whether this server is a built-in fallback used when the user has not configured their - own server. - """ - oidc: bool | MCPServerAuthConfigRedirectPort | None = None - """Set to `true` to use defaults, or provide an object with additional auth or OIDC settings.""" - - timeout: int | None = None - """Timeout in milliseconds for tool calls to this server.""" - - tools: list[str] | None = None - """Tools to include. Defaults to all tools if not specified.""" - - @staticmethod - def from_dict(obj: Any) -> 'MCPServerConfigStdio': - assert isinstance(obj, dict) - command = from_str(obj.get("command")) - args = from_union([lambda x: from_list(from_str, x), from_none], obj.get("args")) - 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, disable_tool_cache, env, filter_mapping, is_default_server, oidc, timeout, tools) - - def to_dict(self) -> dict: - result: dict = {} - result["command"] = from_str(self.command) - if self.args is not None: - result["args"] = from_union([lambda x: from_list(from_str, x), from_none], self.args) - if self.auth is not None: - result["auth"] = from_union([from_bool, lambda x: to_class(MCPServerAuthConfigRedirectPort, x), from_none], self.auth) - if self.cwd is not None: - 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: - 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.is_default_server is not None: - result["isDefaultServer"] = from_union([from_bool, from_none], self.is_default_server) - if self.oidc is not None: - result["oidc"] = from_union([from_bool, lambda x: to_class(MCPServerAuthConfigRedirectPort, x), from_none], self.oidc) - if self.timeout is not None: - result["timeout"] = from_union([from_int, from_none], self.timeout) - if self.tools is not None: - result["tools"] = from_union([lambda x: from_list(from_str, x), from_none], self.tools) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MCPOauthLoginRequest: @@ -15319,303 +18852,66 @@ class MCPOauthLoginRequest: should pass surface-specific copy telling the user where to return. """ client_id: str | None = None - """Optional OAuth client ID override for this login. When set, the runtime uses this - pre-registered static client instead of dynamic client registration. - """ - client_name: str | None = None - """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. - """ - client_secret: str | None = None - """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. - """ - force_reauth: bool | None = None - """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. - """ - grant_type: MCPGrantType | None = None - """Optional OAuth grant type override for this login. Defaults to the server configuration, - or authorization_code when no grant type is specified. - """ - public_client: bool | None = None - """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. - """ - - @staticmethod - def from_dict(obj: Any) -> 'MCPOauthLoginRequest': - assert isinstance(obj, dict) - server_name = from_str(obj.get("serverName")) - callback_success_message = from_union([from_str, from_none], obj.get("callbackSuccessMessage")) - client_id = from_union([from_str, from_none], obj.get("clientId")) - client_name = from_union([from_str, from_none], obj.get("clientName")) - client_secret = from_union([from_str, from_none], obj.get("clientSecret")) - force_reauth = from_union([from_bool, from_none], obj.get("forceReauth")) - grant_type = from_union([MCPGrantType, from_none], obj.get("grantType")) - public_client = from_union([from_bool, from_none], obj.get("publicClient")) - return MCPOauthLoginRequest(server_name, callback_success_message, client_id, client_name, client_secret, force_reauth, grant_type, public_client) - - def to_dict(self) -> dict: - result: dict = {} - result["serverName"] = from_str(self.server_name) - if self.callback_success_message is not None: - result["callbackSuccessMessage"] = from_union([from_str, from_none], self.callback_success_message) - if self.client_id is not None: - result["clientId"] = from_union([from_str, from_none], self.client_id) - if self.client_name is not None: - result["clientName"] = from_union([from_str, from_none], self.client_name) - if self.client_secret is not None: - result["clientSecret"] = from_union([from_str, from_none], self.client_secret) - if self.force_reauth is not None: - result["forceReauth"] = from_union([from_bool, from_none], self.force_reauth) - if self.grant_type is not None: - result["grantType"] = from_union([lambda x: to_enum(MCPGrantType, x), from_none], self.grant_type) - if self.public_client is not None: - result["publicClient"] = from_union([from_bool, from_none], self.public_client) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class MCPServerConfig: - """MCP server configuration (stdio process or remote HTTP/SSE) - - 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. - """ - args: list[str] | None = None - """Command-line arguments passed to the Stdio MCP server process.""" - - auth: bool | MCPServerAuthConfigRedirectPort | None = None - """Set to `true` to use defaults, or provide an object with additional auth or OIDC settings.""" - - command: str | None = None - """Executable command used to start the Stdio MCP server process.""" - - cwd: str | None = None - """Working directory for the Stdio MCP server process.""" - - defer_tools: MCPServerConfigDeferTools | None = None - """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.""" - - 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. - """ - is_default_server: bool | None = None - """Whether this server is a built-in fallback used when the user has not configured their - own server. - """ - oidc: bool | MCPServerAuthConfigRedirectPort | None = None - """Set to `true` to use defaults, or provide an object with additional auth or OIDC settings.""" - - timeout: int | None = None - """Timeout in milliseconds for tool calls to this server.""" - - tools: list[str] | None = None - """Tools to include. Defaults to all tools if not specified.""" - - headers: dict[str, str] | None = None - """HTTP headers to include in requests to the remote MCP server.""" - - oauth_client_id: str | None = None - """OAuth client ID for a pre-registered remote MCP OAuth client.""" - - oauth_grant_type: MCPGrantType | None = None - """OAuth grant type to use when authenticating to the remote MCP server.""" - - oauth_public_client: bool | None = None - """Whether the configured OAuth client is public and does not require a client secret.""" - - type: MCPServerConfigHTTPType | None = None - """Remote transport type. Defaults to "http" when omitted.""" - - url: str | None = None - """URL of the remote MCP server endpoint.""" - - @staticmethod - def from_dict(obj: Any) -> 'MCPServerConfig': - assert isinstance(obj, dict) - args = from_union([lambda x: from_list(from_str, x), from_none], obj.get("args")) - auth = from_union([from_bool, MCPServerAuthConfigRedirectPort.from_dict, from_none], obj.get("auth")) - 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")) - 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")) - headers = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("headers")) - oauth_client_id = from_union([from_str, from_none], obj.get("oauthClientId")) - oauth_grant_type = from_union([MCPGrantType, from_none], obj.get("oauthGrantType")) - 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, 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 = {} - if self.args is not None: - result["args"] = from_union([lambda x: from_list(from_str, x), from_none], self.args) - if self.auth is not None: - result["auth"] = from_union([from_bool, lambda x: to_class(MCPServerAuthConfigRedirectPort, x), from_none], self.auth) - if self.command is not None: - result["command"] = from_union([from_str, from_none], self.command) - if self.cwd is not None: - 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: - 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.is_default_server is not None: - result["isDefaultServer"] = from_union([from_bool, from_none], self.is_default_server) - if self.oidc is not None: - result["oidc"] = from_union([from_bool, lambda x: to_class(MCPServerAuthConfigRedirectPort, x), from_none], self.oidc) - if self.timeout is not None: - result["timeout"] = from_union([from_int, from_none], self.timeout) - if self.tools is not None: - result["tools"] = from_union([lambda x: from_list(from_str, x), from_none], self.tools) - if self.headers is not None: - result["headers"] = from_union([lambda x: from_dict(from_str, x), from_none], self.headers) - if self.oauth_client_id is not None: - result["oauthClientId"] = from_union([from_str, from_none], self.oauth_client_id) - if self.oauth_grant_type is not None: - result["oauthGrantType"] = from_union([lambda x: to_enum(MCPGrantType, x), from_none], self.oauth_grant_type) - if self.oauth_public_client is not None: - result["oauthPublicClient"] = from_union([from_bool, from_none], self.oauth_public_client) - if self.type is not None: - result["type"] = from_union([lambda x: to_enum(MCPServerConfigHTTPType, x), from_none], self.type) - if self.url is not None: - result["url"] = from_union([from_str, from_none], self.url) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class MCPServerConfigHTTP: - """Remote MCP server configuration accessed over HTTP or SSE.""" - - url: str - """URL of the remote MCP server endpoint.""" - - auth: bool | MCPServerAuthConfigRedirectPort | None = None - """Set to `true` to use defaults, or provide an object with additional auth or OIDC settings.""" - - defer_tools: MCPServerConfigDeferTools | None = None - """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) + """Optional OAuth client ID override for this login. When set, the runtime uses this + pre-registered static client instead of dynamic client registration. """ - disable_tool_cache: bool | None = None - """Set to true to disable persisted MCP tool snapshots for this server. Live tool discovery - is unaffected. + client_name: str | None = None + """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. """ - 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. + client_secret: str | None = None + """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. """ - headers: dict[str, str] | None = None - """HTTP headers to include in requests to the remote MCP server.""" - - is_default_server: bool | None = None - """Whether this server is a built-in fallback used when the user has not configured their - own server. + force_reauth: bool | None = None + """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. + """ + grant_type: MCPGrantType | None = None + """Optional OAuth grant type override for this login. Defaults to the server configuration, + or authorization_code when no grant type is specified. + """ + public_client: bool | None = None + """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. """ - oauth_client_id: str | None = None - """OAuth client ID for a pre-registered remote MCP OAuth client.""" - - oauth_grant_type: MCPGrantType | None = None - """OAuth grant type to use when authenticating to the remote MCP server.""" - - oauth_public_client: bool | None = None - """Whether the configured OAuth client is public and does not require a client secret.""" - - oidc: bool | MCPServerAuthConfigRedirectPort | None = None - """Set to `true` to use defaults, or provide an object with additional auth or OIDC settings.""" - - timeout: int | None = None - """Timeout in milliseconds for tool calls to this server.""" - - tools: list[str] | None = None - """Tools to include. Defaults to all tools if not specified.""" - - type: MCPServerConfigHTTPType | None = None - """Remote transport type. Defaults to "http" when omitted.""" @staticmethod - def from_dict(obj: Any) -> 'MCPServerConfigHTTP': + def from_dict(obj: Any) -> 'MCPOauthLoginRequest': assert isinstance(obj, dict) - 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")) - oauth_client_id = from_union([from_str, from_none], obj.get("oauthClientId")) - oauth_grant_type = from_union([MCPGrantType, from_none], obj.get("oauthGrantType")) - oauth_public_client = from_union([from_bool, from_none], obj.get("oauthPublicClient")) - 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")) - type = from_union([MCPServerConfigHTTPType, from_none], obj.get("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) + server_name = from_str(obj.get("serverName")) + callback_success_message = from_union([from_str, from_none], obj.get("callbackSuccessMessage")) + client_id = from_union([from_str, from_none], obj.get("clientId")) + client_name = from_union([from_str, from_none], obj.get("clientName")) + client_secret = from_union([from_str, from_none], obj.get("clientSecret")) + force_reauth = from_union([from_bool, from_none], obj.get("forceReauth")) + grant_type = from_union([MCPGrantType, from_none], obj.get("grantType")) + public_client = from_union([from_bool, from_none], obj.get("publicClient")) + return MCPOauthLoginRequest(server_name, callback_success_message, client_id, client_name, client_secret, force_reauth, grant_type, public_client) def to_dict(self) -> dict: result: dict = {} - result["url"] = from_str(self.url) - if self.auth is not None: - 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: - result["headers"] = from_union([lambda x: from_dict(from_str, x), from_none], self.headers) - if self.is_default_server is not None: - result["isDefaultServer"] = from_union([from_bool, from_none], self.is_default_server) - if self.oauth_client_id is not None: - result["oauthClientId"] = from_union([from_str, from_none], self.oauth_client_id) - if self.oauth_grant_type is not None: - result["oauthGrantType"] = from_union([lambda x: to_enum(MCPGrantType, x), from_none], self.oauth_grant_type) - if self.oauth_public_client is not None: - result["oauthPublicClient"] = from_union([from_bool, from_none], self.oauth_public_client) - if self.oidc is not None: - result["oidc"] = from_union([from_bool, lambda x: to_class(MCPServerAuthConfigRedirectPort, x), from_none], self.oidc) - if self.timeout is not None: - result["timeout"] = from_union([from_int, from_none], self.timeout) - if self.tools is not None: - result["tools"] = from_union([lambda x: from_list(from_str, x), from_none], self.tools) - if self.type is not None: - result["type"] = from_union([lambda x: to_enum(MCPServerConfigHTTPType, x), from_none], self.type) + result["serverName"] = from_str(self.server_name) + if self.callback_success_message is not None: + result["callbackSuccessMessage"] = from_union([from_str, from_none], self.callback_success_message) + if self.client_id is not None: + result["clientId"] = from_union([from_str, from_none], self.client_id) + if self.client_name is not None: + result["clientName"] = from_union([from_str, from_none], self.client_name) + if self.client_secret is not None: + result["clientSecret"] = from_union([from_str, from_none], self.client_secret) + if self.force_reauth is not None: + result["forceReauth"] = from_union([from_bool, from_none], self.force_reauth) + if self.grant_type is not None: + result["grantType"] = from_union([lambda x: to_enum(MCPGrantType, x), from_none], self.grant_type) + if self.public_client is not None: + result["publicClient"] = from_union([from_bool, from_none], self.public_client) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -15629,18 +18925,24 @@ class MCPStartServersResult: allowed_servers: list[MCPAllowedServer] | None = None """Non-default servers allowed by policy""" + failed_servers: list[MCPFailedServer] | None = None + """Servers whose connection attempt failed.""" + @staticmethod def from_dict(obj: Any) -> 'MCPStartServersResult': assert isinstance(obj, dict) filtered_servers = from_list(MCPFilteredServer.from_dict, obj.get("filteredServers")) allowed_servers = from_union([lambda x: from_list(MCPAllowedServer.from_dict, x), from_none], obj.get("allowedServers")) - return MCPStartServersResult(filtered_servers, allowed_servers) + failed_servers = from_union([lambda x: from_list(MCPFailedServer.from_dict, x), from_none], obj.get("failedServers")) + return MCPStartServersResult(filtered_servers, allowed_servers, failed_servers) def to_dict(self) -> dict: result: dict = {} result["filteredServers"] = from_list(lambda x: to_class(MCPFilteredServer, x), self.filtered_servers) if self.allowed_servers is not None: result["allowedServers"] = from_union([lambda x: from_list(lambda x: to_class(MCPAllowedServer, x), x), from_none], self.allowed_servers) + if self.failed_servers is not None: + result["failedServers"] = from_union([lambda x: from_list(lambda x: to_class(MCPFailedServer, x), x), from_none], self.failed_servers) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -15649,6 +18951,8 @@ class MCPHeadersHandlePendingHeadersRefreshRequest: """Host response: supply dynamic headers or decline this refresh.""" kind: MCPHeadersHandlePendingHeadersRefreshRequestKind + """Headers-refresh response variant discriminator.""" + headers: dict[str, str] | None = None """Headers to overlay onto the MCP request. Dynamic headers override static config headers but do not replace SDK-managed request headers. @@ -15719,37 +19023,422 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class MCPOauthPendingRequestResponse: - """Host response to the pending OAuth request.""" +class MCPPlanConfigurationChange: + """One change applying the plan would make, described rather than serialised so the + configuration payload stays behind the runtime boundary. + """ + changed_fields: list[str] + """Names of the configuration fields the change would set, without their values.""" - kind: MCPOauthPendingRequestResponseKind - access_token: str | None = None - """Access token acquired by the SDK host""" + config_key: str + """Configuration key the change applies to.""" - expires_in: int | None = None - """Token lifetime in seconds, if known.""" + operation: MCPPlanConfigurationOperation + """Whether the change would create a new entry or modify an existing one.""" - token_type: str | None = None - """OAuth token type. Defaults to Bearer when omitted.""" + scope: MCPPlanScope + """Scope the change would be written to.""" + + secret_references: list[str] + """Secret placeholders the written configuration would reference. The constrained + placeholder type cannot carry a literal secret value. + """ @staticmethod - def from_dict(obj: Any) -> 'MCPOauthPendingRequestResponse': + def from_dict(obj: Any) -> 'MCPPlanConfigurationChange': assert isinstance(obj, dict) - kind = MCPOauthPendingRequestResponseKind(obj.get("kind")) - access_token = from_union([from_str, from_none], obj.get("accessToken")) - expires_in = from_union([from_int, from_none], obj.get("expiresIn")) - token_type = from_union([from_str, from_none], obj.get("tokenType")) - return MCPOauthPendingRequestResponse(kind, access_token, expires_in, token_type) + changed_fields = from_list(from_str, obj.get("changedFields")) + config_key = from_str(obj.get("configKey")) + operation = MCPPlanConfigurationOperation(obj.get("operation")) + scope = MCPPlanScope(obj.get("scope")) + secret_references = from_list(from_str, obj.get("secretReferences")) + return MCPPlanConfigurationChange(changed_fields, config_key, operation, scope, secret_references) def to_dict(self) -> dict: result: dict = {} - result["kind"] = to_enum(MCPOauthPendingRequestResponseKind, self.kind) - if self.access_token is not None: - result["accessToken"] = from_union([from_str, from_none], self.access_token) - if self.expires_in is not None: - result["expiresIn"] = from_union([from_int, from_none], self.expires_in) - if self.token_type is not None: - result["tokenType"] = from_union([from_str, from_none], self.token_type) + result["changedFields"] = from_list(from_str, self.changed_fields) + result["configKey"] = from_str(self.config_key) + result["operation"] = to_enum(MCPPlanConfigurationOperation, self.operation) + result["scope"] = to_enum(MCPPlanScope, self.scope) + result["secretReferences"] = from_list(from_str, self.secret_references) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MCPPlanTarget: + """Configuration scope and key the plan would write to. + + Where a plan would be written. + """ + config_key: str + """Configuration key the server would be recorded under within that scope.""" + + scope: MCPPlanScope + """Configuration scope the plan targets.""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPPlanTarget': + assert isinstance(obj, dict) + config_key = from_str(obj.get("configKey")) + scope = MCPPlanScope(obj.get("scope")) + return MCPPlanTarget(config_key, scope) + + def to_dict(self) -> dict: + result: dict = {} + result["configKey"] = from_str(self.config_key) + result["scope"] = to_enum(MCPPlanScope, self.scope) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MCPPlanInstallRequest: + """A side-effect-free request for an MCP install plan. Computing a plan never writes + configuration, stores a secret, or reloads MCP servers. + """ + contract: CatalogClientContract + """Protocol version and capabilities the caller requires.""" + + source: MCPPlanInstallSource + """What to plan: either a candidate handle from a previous search, or a card supplied + directly. + """ + scope: MCPPlanScope | None = None + """Configuration scope the plan targets. Defaults to user scope when omitted.""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPPlanInstallRequest': + assert isinstance(obj, dict) + contract = CatalogClientContract.from_dict(obj.get("contract")) + source = _load_MCPPlanInstallSource(obj.get("source")) + scope = from_union([MCPPlanScope, from_none], obj.get("scope")) + return MCPPlanInstallRequest(contract, source, scope) + + def to_dict(self) -> dict: + result: dict = {} + result["contract"] = to_class(CatalogClientContract, self.contract) + result["source"] = (self.source).to_dict() + if self.scope is not None: + result["scope"] = from_union([lambda x: to_enum(MCPPlanScope, x), from_none], self.scope) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MCPPlanPolicyResult: + """Outcome of evaluating the server against registry and enterprise policy. + + Outcome of evaluating the planned server against registry and enterprise policy. + Evaluation is read-only. + """ + decision: MCPPlanPolicyDecision + """What policy decided for this server.""" + + source: MCPPlanPolicySource + """Which authority produced the decision.""" + + reason: str | None = None + """Human-readable explanation, safe to surface. Never contains a query, URL, handle, or + secret. + """ + + @staticmethod + def from_dict(obj: Any) -> 'MCPPlanPolicyResult': + assert isinstance(obj, dict) + decision = MCPPlanPolicyDecision(obj.get("decision")) + source = MCPPlanPolicySource(obj.get("source")) + reason = from_union([from_str, from_none], obj.get("reason")) + return MCPPlanPolicyResult(decision, source, reason) + + def to_dict(self) -> dict: + result: dict = {} + result["decision"] = to_enum(MCPPlanPolicyDecision, self.decision) + result["source"] = to_enum(MCPPlanPolicySource, self.source) + if self.reason is not None: + 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 MCPPlanRequiredValueEnum: + """One enumerated non-secret value a transport choice needs before it can be applied. The + permitted values are structurally required. + """ + category: MCPPlanValueCategory + """Where the value is applied when the server is launched.""" + + enum_values: list[str] + """Non-empty permitted value set. Inert untrusted data.""" + + is_repeated: bool + """Whether the value may be supplied more than once.""" + + key: str + """Key the value is supplied under. Inert untrusted data.""" + + kind: ClassVar[str] = "enum" + """Discriminator: this required value uses a fixed enumeration.""" + + required: bool + """Whether the value must be present for the plan to be applicable.""" + + value_type: MCPPlan + """Discriminator: the value must be one of `enumValues`.""" + + default_value: str | None = None + """Default supplied by the card, when the value can be resolved without input. Presence is + the authoritative indication that a default exists. Inert untrusted data. + """ + description: str | None = None + """Human-readable explanation from the card. Inert untrusted text.""" + + title: str | None = None + """Human-readable label from the card. Inert untrusted text.""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPPlanRequiredValueEnum': + assert isinstance(obj, dict) + category = MCPPlanValueCategory(obj.get("category")) + enum_values = from_list(from_str, obj.get("enumValues")) + is_repeated = from_bool(obj.get("isRepeated")) + key = from_str(obj.get("key")) + required = from_bool(obj.get("required")) + value_type = MCPPlan(obj.get("valueType")) + default_value = from_union([from_str, from_none], obj.get("defaultValue")) + description = from_union([from_str, from_none], obj.get("description")) + title = from_union([from_str, from_none], obj.get("title")) + return MCPPlanRequiredValueEnum(category, enum_values, is_repeated, key, required, value_type, default_value, description, title) + + def to_dict(self) -> dict: + result: dict = {} + result["category"] = to_enum(MCPPlanValueCategory, self.category) + result["enumValues"] = from_list(from_str, self.enum_values) + result["isRepeated"] = from_bool(self.is_repeated) + result["key"] = from_str(self.key) + result["kind"] = self.kind + result["required"] = from_bool(self.required) + result["valueType"] = to_enum(MCPPlan, self.value_type) + if self.default_value is not None: + result["defaultValue"] = from_union([from_str, from_none], self.default_value) + if self.description is not None: + result["description"] = from_union([from_str, from_none], self.description) + if self.title is not None: + result["title"] = from_union([from_str, from_none], self.title) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MCPPlanInstallSourceCandidate: + """Plan from a candidate returned by a previous catalog search.""" + + candidate_handle: str + """Single-use candidate handle. Consumed by this call, so a replay of the same handle is + rejected. + """ + kind: ClassVar[str] = "candidate" + """Discriminator: plan from a previously returned candidate""" + + search_id: str + """The runtime- or authority-minted `searchId` returned with the search that produced this + candidate. A search implementation binds it to private candidate-handle context; a + planning implementation must verify that context before returning a plan. The unavailable + planning implementation in this contract layer validates presence but does not claim the + verification has occurred. It identifies a search rather than a person and must never be + joined with user identity to re-identify anyone. + """ + + @staticmethod + def from_dict(obj: Any) -> 'MCPPlanInstallSourceCandidate': + assert isinstance(obj, dict) + candidate_handle = from_str(obj.get("candidateHandle")) + search_id = from_str(obj.get("searchId")) + return MCPPlanInstallSourceCandidate(candidate_handle, search_id) + + def to_dict(self) -> dict: + result: dict = {} + result["candidateHandle"] = from_str(self.candidate_handle) + result["kind"] = self.kind + result["searchId"] = from_str(self.search_id) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MCPPlanInstallSourceCard: + """Plan from a card supplied directly by the caller, without a preceding search.""" + + card: MCPServerCardReference + """The card to plan from: exactly one of a URL or embedded data.""" + + kind: ClassVar[str] = "card" + """Discriminator: plan from a caller-supplied card""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPPlanInstallSourceCard': + assert isinstance(obj, dict) + card = _load_MCPServerCardReference(obj.get("card")) + return MCPPlanInstallSourceCard(card) + + def to_dict(self) -> dict: + result: dict = {} + result["card"] = (self.card).to_dict() + result["kind"] = self.kind + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MCPPlanTransportChoicePackage: + """An eligible local-package transport choice. Package identity is required and a remote + endpoint cannot be represented. + """ + choice_id: str + """Stable identifier for this choice within the plan, used to select it when the plan is + applied. + """ + install_method: MCPPlanPackageInstallMethod + """Discriminator: this choice runs a local package""" + + package_identifier: str + """Package identifier. Inert untrusted data.""" + + package_type: str + """Packaging ecosystem, for example `oci` or `npm`.""" + + required_values: list[MCPPlanRequiredValue] + """Typed values this choice requires, excluding secrets.""" + + secret_placeholders: list[MCPPlanSecretPlaceholder] + """Secrets this choice requires, referenced by placeholder only.""" + + transport: MCPPlanPackageTransport + """Local process transport this package choice would use.""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPPlanTransportChoicePackage': + assert isinstance(obj, dict) + choice_id = from_str(obj.get("choiceId")) + install_method = MCPPlanPackageInstallMethod(obj.get("installMethod")) + package_identifier = from_str(obj.get("packageIdentifier")) + package_type = from_str(obj.get("packageType")) + required_values = from_list(_load_MCPPlanRequiredValue, obj.get("requiredValues")) + secret_placeholders = from_list(MCPPlanSecretPlaceholder.from_dict, obj.get("secretPlaceholders")) + transport = MCPPlanPackageTransport(obj.get("transport")) + return MCPPlanTransportChoicePackage(choice_id, install_method, package_identifier, package_type, required_values, secret_placeholders, transport) + + def to_dict(self) -> dict: + result: dict = {} + result["choiceId"] = from_str(self.choice_id) + result["installMethod"] = to_enum(MCPPlanPackageInstallMethod, self.install_method) + result["packageIdentifier"] = from_str(self.package_identifier) + result["packageType"] = from_str(self.package_type) + result["requiredValues"] = from_list(lambda x: (x).to_dict(), self.required_values) + result["secretPlaceholders"] = from_list(lambda x: to_class(MCPPlanSecretPlaceholder, x), self.secret_placeholders) + result["transport"] = to_enum(MCPPlanPackageTransport, self.transport) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MCPPlanTransportChoiceRemote: + """An eligible remote-endpoint transport choice. The endpoint is required and package + identity cannot be represented. + """ + choice_id: str + """Stable identifier for this choice within the plan, used to select it when the plan is + applied. + """ + endpoint: str + """Endpoint URL. Inert untrusted data.""" + + install_method: MCPPlanRemoteInstallMethod + """Discriminator: this choice connects to a remote endpoint""" + + required_values: list[MCPPlanRequiredValue] + """Typed values this choice requires, excluding secrets.""" + + secret_placeholders: list[MCPPlanSecretPlaceholder] + """Secrets this choice requires, referenced by placeholder only.""" + + transport: MCPPlanRemoteTransport + """Endpoint transport this remote choice would use.""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPPlanTransportChoiceRemote': + assert isinstance(obj, dict) + choice_id = from_str(obj.get("choiceId")) + endpoint = from_str(obj.get("endpoint")) + install_method = MCPPlanRemoteInstallMethod(obj.get("installMethod")) + required_values = from_list(_load_MCPPlanRequiredValue, obj.get("requiredValues")) + secret_placeholders = from_list(MCPPlanSecretPlaceholder.from_dict, obj.get("secretPlaceholders")) + transport = MCPPlanRemoteTransport(obj.get("transport")) + return MCPPlanTransportChoiceRemote(choice_id, endpoint, install_method, required_values, secret_placeholders, transport) + + def to_dict(self) -> dict: + result: dict = {} + result["choiceId"] = from_str(self.choice_id) + result["endpoint"] = from_str(self.endpoint) + result["installMethod"] = to_enum(MCPPlanRemoteInstallMethod, self.install_method) + result["requiredValues"] = from_list(lambda x: (x).to_dict(), self.required_values) + result["secretPlaceholders"] = from_list(lambda x: to_class(MCPPlanSecretPlaceholder, x), self.secret_placeholders) + result["transport"] = to_enum(MCPPlanRemoteTransport, self.transport) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MCPPlanRequiredValueScalar: + """One non-secret scalar value a transport choice needs before it can be applied.""" + + category: MCPPlanValueCategory + """Where the value is applied when the server is launched.""" + + is_repeated: bool + """Whether the value may be supplied more than once.""" + + key: str + """Key the value is supplied under. Inert untrusted data.""" + + kind: ClassVar[str] = "scalar" + """Discriminator: this required value uses a scalar type.""" + + required: bool + """Whether the value must be present for the plan to be applicable.""" + + value_type: MCPPlanScalarValueTypeEnum + """Scalar type the value must conform to.""" + + default_value: str | None = None + """Default supplied by the card, when the value can be resolved without input. Presence is + the authoritative indication that a default exists. Inert untrusted data. + """ + description: str | None = None + """Human-readable explanation from the card. Inert untrusted text.""" + + title: str | None = None + """Human-readable label from the card. Inert untrusted text.""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPPlanRequiredValueScalar': + assert isinstance(obj, dict) + category = MCPPlanValueCategory(obj.get("category")) + is_repeated = from_bool(obj.get("isRepeated")) + key = from_str(obj.get("key")) + required = from_bool(obj.get("required")) + value_type = MCPPlanScalarValueTypeEnum(obj.get("valueType")) + default_value = from_union([from_str, from_none], obj.get("defaultValue")) + description = from_union([from_str, from_none], obj.get("description")) + title = from_union([from_str, from_none], obj.get("title")) + return MCPPlanRequiredValueScalar(category, is_repeated, key, required, value_type, default_value, description, title) + + def to_dict(self) -> dict: + result: dict = {} + result["category"] = to_enum(MCPPlanValueCategory, self.category) + result["isRepeated"] = from_bool(self.is_repeated) + result["key"] = from_str(self.key) + result["kind"] = self.kind + result["required"] = from_bool(self.required) + result["valueType"] = to_enum(MCPPlanScalarValueTypeEnum, self.value_type) + if self.default_value is not None: + result["defaultValue"] = from_union([from_str, from_none], self.default_value) + if self.description is not None: + result["description"] = from_union([from_str, from_none], self.description) + if self.title is not None: + result["title"] = from_union([from_str, from_none], self.title) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -15807,6 +19496,60 @@ def to_dict(self) -> dict: result["result"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.result) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class PermissionDecisionApproveForLocationApprovalMemory: + """Location-scoped approval details for writes to long-term memory.""" + + kind: ClassVar[str] = "memory" + """Approval covering writes to long-term memory.""" + + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecisionApproveForLocationApprovalMemory': + assert isinstance(obj, dict) + return PermissionDecisionApproveForLocationApprovalMemory() + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = self.kind + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class PermissionDecisionApproveForSessionApprovalMemory: + """Session-scoped approval details for writes to long-term memory.""" + + kind: ClassVar[str] = "memory" + """Approval covering writes to long-term memory.""" + + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecisionApproveForSessionApprovalMemory': + assert isinstance(obj, dict) + return PermissionDecisionApproveForSessionApprovalMemory() + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = self.kind + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class PermissionsLocationsAddToolApprovalDetailsMemory: + """Location-persisted tool approval details for writes to long-term memory.""" + + kind: ClassVar[str] = "memory" + """Approval covering writes to long-term memory.""" + + @staticmethod + def from_dict(obj: Any) -> 'PermissionsLocationsAddToolApprovalDetailsMemory': + assert isinstance(obj, dict) + return PermissionsLocationsAddToolApprovalDetailsMemory() + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = self.kind + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MCPSetEnvValueModeParams: @@ -15869,13 +19612,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 @@ -15885,15 +19662,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 @@ -15960,6 +19751,242 @@ def to_dict(self) -> dict: result["taskType"] = from_union([lambda x: to_enum(TaskType, x), from_none], self.task_type) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ModeSetRequest: + """Agent interaction mode to apply to the session.""" + + mode: SessionMode + """The session mode the agent is operating in""" + + compaction_decision: str | None = None + """Explicit response to a model-switch compaction preflight.""" + + inherit_plan_base_from_session_id: str | None = None + """Session whose plan-mode base state should be inherited.""" + + persist_plan_selection: bool | None = None + """Whether the selected plan model should be persisted.""" + + picker_settings_context: ModelPickerSettingsContext | None = None + """Settings context used when persisting the selected plan model.""" + + plan_context_tier: str | None = None + """Context tier to use with the dedicated plan model.""" + + plan_exit_action: str | None = None + """Action to perform when leaving plan mode.""" + + plan_model: str | None = None + """Dedicated model to use in plan mode, when configured.""" + + plan_model_configured: bool | None = None + """Whether a dedicated plan model is configured.""" + + plan_reasoning_effort: str | None = None + """Reasoning effort to use with the dedicated plan model.""" + + restore_plan_model: bool | None = None + """Whether leaving plan mode should restore the session's previous model.""" + + @staticmethod + def from_dict(obj: Any) -> 'ModeSetRequest': + assert isinstance(obj, dict) + mode = SessionMode(obj.get("mode")) + compaction_decision = from_union([from_str, from_none], obj.get("compactionDecision")) + inherit_plan_base_from_session_id = from_union([from_str, from_none], obj.get("inheritPlanBaseFromSessionId")) + persist_plan_selection = from_union([from_bool, from_none], obj.get("persistPlanSelection")) + picker_settings_context = from_union([ModelPickerSettingsContext.from_dict, from_none], obj.get("pickerSettingsContext")) + plan_context_tier = from_union([from_str, from_none], obj.get("planContextTier")) + plan_exit_action = from_union([from_str, from_none], obj.get("planExitAction")) + plan_model = from_union([from_str, from_none], obj.get("planModel")) + plan_model_configured = from_union([from_bool, from_none], obj.get("planModelConfigured")) + plan_reasoning_effort = from_union([from_str, from_none], obj.get("planReasoningEffort")) + restore_plan_model = from_union([from_bool, from_none], obj.get("restorePlanModel")) + return ModeSetRequest(mode, compaction_decision, inherit_plan_base_from_session_id, persist_plan_selection, picker_settings_context, plan_context_tier, plan_exit_action, plan_model, plan_model_configured, plan_reasoning_effort, restore_plan_model) + + def to_dict(self) -> dict: + result: dict = {} + result["mode"] = to_enum(SessionMode, self.mode) + if self.compaction_decision is not None: + result["compactionDecision"] = from_union([from_str, from_none], self.compaction_decision) + if self.inherit_plan_base_from_session_id is not None: + result["inheritPlanBaseFromSessionId"] = from_union([from_str, from_none], self.inherit_plan_base_from_session_id) + if self.persist_plan_selection is not None: + result["persistPlanSelection"] = from_union([from_bool, from_none], self.persist_plan_selection) + if self.picker_settings_context is not None: + result["pickerSettingsContext"] = from_union([lambda x: to_class(ModelPickerSettingsContext, x), from_none], self.picker_settings_context) + if self.plan_context_tier is not None: + result["planContextTier"] = from_union([from_str, from_none], self.plan_context_tier) + if self.plan_exit_action is not None: + result["planExitAction"] = from_union([from_str, from_none], self.plan_exit_action) + if self.plan_model is not None: + result["planModel"] = from_union([from_str, from_none], self.plan_model) + if self.plan_model_configured is not None: + result["planModelConfigured"] = from_union([from_bool, from_none], self.plan_model_configured) + if self.plan_reasoning_effort is not None: + result["planReasoningEffort"] = from_union([from_str, from_none], self.plan_reasoning_effort) + if self.restore_plan_model is not None: + result["restorePlanModel"] = from_union([from_bool, from_none], self.restore_plan_model) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ModelPickerPersistenceRequest: + """Optional settings context and explicit-override flags used to persist a picker selection.""" + + settings_context: ModelPickerSettingsContext + """Filesystem and environment context used to resolve settings persistence.""" + + context_tier_explicit: bool | None = None + """Whether context tier was explicitly selected and should be persisted.""" + + reasoning_effort_explicit: bool | None = None + """Whether reasoning effort was explicitly selected and should be persisted.""" + + @staticmethod + def from_dict(obj: Any) -> 'ModelPickerPersistenceRequest': + assert isinstance(obj, dict) + settings_context = ModelPickerSettingsContext.from_dict(obj.get("settingsContext")) + context_tier_explicit = from_union([from_bool, from_none], obj.get("contextTierExplicit")) + reasoning_effort_explicit = from_union([from_bool, from_none], obj.get("reasoningEffortExplicit")) + return ModelPickerPersistenceRequest(settings_context, context_tier_explicit, reasoning_effort_explicit) + + def to_dict(self) -> dict: + result: dict = {} + result["settingsContext"] = to_class(ModelPickerSettingsContext, self.settings_context) + if self.context_tier_explicit is not None: + result["contextTierExplicit"] = from_union([from_bool, from_none], self.context_tier_explicit) + if self.reasoning_effort_explicit is not None: + result["reasoningEffortExplicit"] = from_union([from_bool, from_none], self.reasoning_effort_explicit) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ModeSetResult: + """Outcome of a session mode change, including any model switch it triggered and follow-up + the host must perform. + """ + model_changed: bool + """Whether applying the mode changed the active model.""" + + status: str + """Lifecycle status of the requested mode change.""" + + arm_interactive_continuation: bool | None = None + """Whether the host should arm an interactive continuation after the mode change.""" + + confirmation: ModelSwitchConfirmation | None = None + """Compaction confirmation required before the mode change can complete.""" + + defer_implementation: bool | None = None + """Whether the host must defer implementing the requested mode change.""" + + deprecation_warnings: list[str] | None = None + """Deprecation warnings associated with the model selected by the mode change.""" + + message: str | None = None + """User-facing outcome message for the model switch triggered by the mode change.""" + + warning: str | None = None + """User-facing warning produced while applying the mode change.""" + + @staticmethod + def from_dict(obj: Any) -> 'ModeSetResult': + assert isinstance(obj, dict) + model_changed = from_bool(obj.get("modelChanged")) + status = from_str(obj.get("status")) + arm_interactive_continuation = from_union([from_bool, from_none], obj.get("armInteractiveContinuation")) + confirmation = from_union([ModelSwitchConfirmation.from_dict, from_none], obj.get("confirmation")) + defer_implementation = from_union([from_bool, from_none], obj.get("deferImplementation")) + deprecation_warnings = from_union([lambda x: from_list(from_str, x), from_none], obj.get("deprecationWarnings")) + message = from_union([from_str, from_none], obj.get("message")) + warning = from_union([from_str, from_none], obj.get("warning")) + return ModeSetResult(model_changed, status, arm_interactive_continuation, confirmation, defer_implementation, deprecation_warnings, message, warning) + + def to_dict(self) -> dict: + result: dict = {} + result["modelChanged"] = from_bool(self.model_changed) + result["status"] = from_str(self.status) + if self.arm_interactive_continuation is not None: + result["armInteractiveContinuation"] = from_union([from_bool, from_none], self.arm_interactive_continuation) + if self.confirmation is not None: + result["confirmation"] = from_union([lambda x: to_class(ModelSwitchConfirmation, x), from_none], self.confirmation) + if self.defer_implementation is not None: + result["deferImplementation"] = from_union([from_bool, from_none], self.defer_implementation) + if self.deprecation_warnings is not None: + result["deprecationWarnings"] = from_union([lambda x: from_list(from_str, x), from_none], self.deprecation_warnings) + if self.message is not None: + result["message"] = from_union([from_str, from_none], self.message) + if self.warning is not None: + result["warning"] = from_union([from_str, from_none], self.warning) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ModelSwitchToResult: + """The model identifier active on the session after the switch.""" + + confirmation: ModelSwitchConfirmation | None = None + """Compaction confirmation projection when status is confirmation_required""" + + 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. + """ + deprecation_warnings: list[str] | None = None + """Deprecation warnings associated with the selected model or options.""" + + message: str | None = None + """User-facing outcome message for the model switch.""" + + model_id: str | None = None + """Currently active model identifier after the switch""" + + persistence_error: str | None = None + """Persistence failure encountered after applying the model switch.""" + + status: str | None = None + """Lifecycle result for the requested switch""" + + warning: str | None = None + """User-facing warning produced while applying the model switch.""" + + @staticmethod + def from_dict(obj: Any) -> 'ModelSwitchToResult': + assert isinstance(obj, dict) + confirmation = from_union([ModelSwitchConfirmation.from_dict, from_none], obj.get("confirmation")) + deferred = from_union([from_bool, from_none], obj.get("deferred")) + deprecation_warnings = from_union([lambda x: from_list(from_str, x), from_none], obj.get("deprecationWarnings")) + message = from_union([from_str, from_none], obj.get("message")) + model_id = from_union([from_str, from_none], obj.get("modelId")) + persistence_error = from_union([from_str, from_none], obj.get("persistenceError")) + status = from_union([from_str, from_none], obj.get("status")) + warning = from_union([from_str, from_none], obj.get("warning")) + return ModelSwitchToResult(confirmation, deferred, deprecation_warnings, message, model_id, persistence_error, status, warning) + + def to_dict(self) -> dict: + result: dict = {} + if self.confirmation is not None: + result["confirmation"] = from_union([lambda x: to_class(ModelSwitchConfirmation, x), from_none], self.confirmation) + if self.deferred is not None: + result["deferred"] = from_union([from_bool, from_none], self.deferred) + if self.deprecation_warnings is not None: + result["deprecationWarnings"] = from_union([lambda x: from_list(from_str, x), from_none], self.deprecation_warnings) + if self.message is not None: + result["message"] = from_union([from_str, from_none], self.message) + if self.model_id is not None: + result["modelId"] = from_union([from_str, from_none], self.model_id) + if self.persistence_error is not None: + result["persistenceError"] = from_union([from_str, from_none], self.persistence_error) + if self.status is not None: + result["status"] = from_union([from_str, from_none], self.status) + if self.warning is not None: + result["warning"] = from_union([from_str, from_none], self.warning) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ModelBillingTokenPrices: @@ -15974,6 +20001,9 @@ class ModelBillingTokenPrices: cache_read_price: float | None = None """AI Credits cost per billing batch of cached (read) tokens""" + cache_write1_h_price: float | None = None + """AI Credits cost per billing batch of 1-hour cache-write (cache creation) tokens.""" + cache_write_price: float | None = None """AI Credits cost per billing batch of cache-write (cache creation) tokens.""" @@ -16000,13 +20030,14 @@ def from_dict(obj: Any) -> 'ModelBillingTokenPrices': batch_size = from_union([from_int, from_none], obj.get("batchSize")) cache_price = from_union([from_float, from_none], obj.get("cachePrice")) cache_read_price = from_union([from_float, from_none], obj.get("cacheReadPrice")) + cache_write1_h_price = from_union([from_float, from_none], obj.get("cacheWrite1hPrice")) cache_write_price = from_union([from_float, from_none], obj.get("cacheWritePrice")) context_max = from_union([from_int, from_none], obj.get("contextMax")) input_price = from_union([from_float, from_none], obj.get("inputPrice")) long_context = from_union([ModelBillingTokenPricesLongContext.from_dict, from_none], obj.get("longContext")) max_prompt_tokens = from_union([from_int, from_none], obj.get("maxPromptTokens")) output_price = from_union([from_float, from_none], obj.get("outputPrice")) - return ModelBillingTokenPrices(batch_size, cache_price, cache_read_price, cache_write_price, context_max, input_price, long_context, max_prompt_tokens, output_price) + return ModelBillingTokenPrices(batch_size, cache_price, cache_read_price, cache_write1_h_price, cache_write_price, context_max, input_price, long_context, max_prompt_tokens, output_price) def to_dict(self) -> dict: result: dict = {} @@ -16016,6 +20047,8 @@ def to_dict(self) -> dict: result["cachePrice"] = from_union([to_float, from_none], self.cache_price) if self.cache_read_price is not None: result["cacheReadPrice"] = from_union([to_float, from_none], self.cache_read_price) + if self.cache_write1_h_price is not None: + result["cacheWrite1hPrice"] = from_union([to_float, from_none], self.cache_write1_h_price) if self.cache_write_price is not None: result["cacheWritePrice"] = from_union([to_float, from_none], self.cache_write_price) if self.context_max is not None: @@ -16074,7 +20107,10 @@ class SessionModelPriceCategory: """Cost-category metadata for a CAPI model.""" id: str + """CAPI model identifier.""" + price_category: ModelPickerPriceCategory + """Cost category assigned to the model.""" @staticmethod def from_dict(obj: Any) -> 'SessionModelPriceCategory': @@ -16155,47 +20191,38 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class NamedProviderConfig: - """A named BYOK provider connection (transport + credentials).""" - + """External SDK input for a named custom model provider. Ingested by the native protocol + boundary before host dispatch. + """ base_url: str - """API endpoint URL.""" + """Base URL for provider API requests.""" name: str - """Stable identifier referenced by BYOK model definitions. Must not contain '/'.""" + """Unique provider name used to qualify model selection IDs.""" api_key: str | None = None - """API key. Optional for local providers like Ollama.""" + """Static API key used to authenticate provider requests.""" azure: ProviderConfigAzure | None = None - """Azure-specific provider options.""" + """Azure authentication configuration for the provider.""" bearer_token: str | None = None - """Bearer token for authentication. Sets the Authorization header directly. Takes precedence - over apiKey when both are set. - """ + """Static bearer token used to authenticate provider requests.""" + has_bearer_token_provider: bool | None = None - """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. - """ + """Whether the host supplies bearer tokens dynamically.""" + headers: dict[str, str] | None = None - """Custom HTTP headers to include in all outbound requests to the provider.""" + """Additional HTTP headers included with provider requests.""" transport: ProviderTransport | None = None - """Provider transport. Defaults to "http".""" + """Transport used to communicate with the provider.""" type: ProviderType | None = None - """Provider type. Defaults to "openai" for generic OpenAI-compatible APIs.""" + """Provider protocol family.""" wire_api: ProviderWireAPI | None = None - """Wire API format (openai/azure only). Defaults to "completions".""" + """Wire API used to communicate with the provider.""" @staticmethod def from_dict(obj: Any) -> 'NamedProviderConfig': @@ -16234,116 +20261,6 @@ def to_dict(self) -> dict: 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. -@dataclass -class ProviderConfig: - """Custom model-provider configuration (BYOK).""" - - base_url: str - """API endpoint URL.""" - - api_key: str | None = None - """API key. Optional for local providers like Ollama.""" - - azure: ProviderConfigAzure | None = None - """Azure-specific provider options.""" - - bearer_token: str | None = None - """Bearer token for authentication. Sets the Authorization header directly. Takes precedence - over apiKey when both are set. - """ - has_bearer_token_provider: bool | None = None - """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. - """ - headers: dict[str, str] | None = None - """Custom HTTP headers to include in all outbound requests to the provider.""" - - max_context_window_tokens: float | None = None - """Maximum context window tokens for the model.""" - - max_output_tokens: float | None = None - """Maximum output tokens for the model.""" - - max_prompt_tokens: float | None = None - """Maximum prompt/input tokens for the model.""" - - model_id: str | None = None - """Well-known model ID used for capability lookup. When set, agent behavior config and token - limits are inferred from this model. - """ - transport: ProviderTransport | None = None - """Provider transport. Defaults to "http".""" - - type: ProviderType | None = None - """Provider type. Defaults to "openai" for generic OpenAI-compatible APIs.""" - - wire_api: ProviderWireAPI | None = None - """Wire API format (openai/azure only). Defaults to "completions".""" - - wire_model: str | None = None - """The model identifier sent to the provider API for inference (the "wire" model), as - opposed to modelId which is the well-known base. - """ - - @staticmethod - def from_dict(obj: Any) -> 'ProviderConfig': - assert isinstance(obj, dict) - base_url = from_str(obj.get("baseUrl")) - api_key = from_union([from_str, from_none], obj.get("apiKey")) - azure = from_union([ProviderConfigAzure.from_dict, from_none], obj.get("azure")) - bearer_token = from_union([from_str, from_none], obj.get("bearerToken")) - has_bearer_token_provider = from_union([from_bool, from_none], obj.get("hasBearerTokenProvider")) - headers = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("headers")) - max_context_window_tokens = from_union([from_float, from_none], obj.get("maxContextWindowTokens")) - max_output_tokens = from_union([from_float, from_none], obj.get("maxOutputTokens")) - max_prompt_tokens = from_union([from_float, from_none], obj.get("maxPromptTokens")) - model_id = from_union([from_str, from_none], obj.get("modelId")) - transport = from_union([ProviderTransport, from_none], obj.get("transport")) - type = from_union([ProviderType, from_none], obj.get("type")) - wire_api = from_union([ProviderWireAPI, from_none], obj.get("wireApi")) - wire_model = from_union([from_str, from_none], obj.get("wireModel")) - return ProviderConfig(base_url, api_key, azure, bearer_token, has_bearer_token_provider, headers, max_context_window_tokens, max_output_tokens, max_prompt_tokens, model_id, transport, type, wire_api, wire_model) - - def to_dict(self) -> dict: - result: dict = {} - result["baseUrl"] = from_str(self.base_url) - if self.api_key is not None: - result["apiKey"] = from_union([from_str, from_none], self.api_key) - if self.azure is not None: - result["azure"] = from_union([lambda x: to_class(ProviderConfigAzure, x), from_none], self.azure) - if self.bearer_token is not None: - result["bearerToken"] = from_union([from_str, from_none], self.bearer_token) - if self.has_bearer_token_provider is not None: - result["hasBearerTokenProvider"] = from_union([from_bool, from_none], self.has_bearer_token_provider) - if self.headers is not None: - result["headers"] = from_union([lambda x: from_dict(from_str, x), from_none], self.headers) - if self.max_context_window_tokens is not None: - result["maxContextWindowTokens"] = from_union([to_float, from_none], self.max_context_window_tokens) - if self.max_output_tokens is not None: - result["maxOutputTokens"] = from_union([to_float, from_none], self.max_output_tokens) - if self.max_prompt_tokens is not None: - result["maxPromptTokens"] = from_union([to_float, from_none], self.max_prompt_tokens) - if self.model_id is not None: - result["modelId"] = from_union([from_str, from_none], self.model_id) - if self.transport is not None: - result["transport"] = from_union([lambda x: to_enum(ProviderTransport, x), from_none], self.transport) - if self.type is not None: - result["type"] = from_union([lambda x: to_enum(ProviderType, x), from_none], self.type) - if self.wire_api is not None: - result["wireApi"] = from_union([lambda x: to_enum(ProviderWireAPI, x), from_none], self.wire_api) - if self.wire_model is not None: - result["wireModel"] = from_union([from_str, from_none], self.wire_model) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class OptionsUpdateAdditionalContentExclusionPolicyRule: @@ -16351,12 +20268,17 @@ class OptionsUpdateAdditionalContentExclusionPolicyRule: conditions, and source. """ paths: list[str] + """Path patterns covered by this rule.""" + source: OptionsUpdateAdditionalContentExclusionPolicyRuleSource """Source descriptor for a `session.options.update` content-exclusion rule, with source name and type. """ if_any_match: list[str] | None = None + """Conditions of which at least one must match.""" + if_none_match: list[str] | None = None + """Conditions none of which may match.""" @staticmethod def from_dict(obj: Any) -> 'OptionsUpdateAdditionalContentExclusionPolicyRule': @@ -16882,60 +20804,6 @@ def to_dict(self) -> dict: result["serverName"] = from_str(self.server_name) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class PermissionDecisionApproveForLocationApprovalMemory: - """Location-scoped approval details for writes to long-term memory.""" - - kind: ClassVar[str] = "memory" - """Approval covering writes to long-term memory.""" - - @staticmethod - def from_dict(obj: Any) -> 'PermissionDecisionApproveForLocationApprovalMemory': - assert isinstance(obj, dict) - return PermissionDecisionApproveForLocationApprovalMemory() - - def to_dict(self) -> dict: - result: dict = {} - result["kind"] = self.kind - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class PermissionDecisionApproveForSessionApprovalMemory: - """Session-scoped approval details for writes to long-term memory.""" - - kind: ClassVar[str] = "memory" - """Approval covering writes to long-term memory.""" - - @staticmethod - def from_dict(obj: Any) -> 'PermissionDecisionApproveForSessionApprovalMemory': - assert isinstance(obj, dict) - return PermissionDecisionApproveForSessionApprovalMemory() - - def to_dict(self) -> dict: - result: dict = {} - result["kind"] = self.kind - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class PermissionsLocationsAddToolApprovalDetailsMemory: - """Location-persisted tool approval details for writes to long-term memory.""" - - kind: ClassVar[str] = "memory" - """Approval covering writes to long-term memory.""" - - @staticmethod - def from_dict(obj: Any) -> 'PermissionsLocationsAddToolApprovalDetailsMemory': - assert isinstance(obj, dict) - return PermissionsLocationsAddToolApprovalDetailsMemory() - - def to_dict(self) -> dict: - result: dict = {} - result["kind"] = self.kind - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionApproveForLocationApprovalRead: @@ -17218,6 +21086,47 @@ 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.""" + + response_capability: PermissionResponseCapability | None = None + """Whether the responding client could ask a user interactively, was running headlessly, or + had no response path. Omit when the client cannot determine this authoritatively. + """ + + @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")) + response_capability = from_union([PermissionResponseCapability, from_none], obj.get("responseCapability")) + return PermissionDecisionContext(outcome, source, surface, response_capability) + + 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) + if self.response_capability is not None: + result["responseCapability"] = from_union([lambda x: to_enum(PermissionResponseCapability, x), from_none], self.response_capability) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionDeniedByContentExclusionPolicy: @@ -17470,12 +21379,17 @@ class PermissionsConfigureAdditionalContentExclusionPolicyRule: match conditions, and source. """ paths: list[str] + """Path patterns covered by this rule.""" + source: PermissionsConfigureAdditionalContentExclusionPolicyRuleSource """Source descriptor for a `session.permissions.configure` content-exclusion rule, with source name and type. """ if_any_match: list[str] | None = None + """Conditions of which at least one must match.""" + if_none_match: list[str] | None = None + """Conditions none of which may match.""" @staticmethod def from_dict(obj: Any) -> 'PermissionsConfigureAdditionalContentExclusionPolicyRule': @@ -17543,7 +21457,9 @@ class PlanReadSQLTodosResult: """Todo rows read from the session SQL database. Empty when no session database is available.""" rows: list[PlanSQLTodosRow] - """Rows from the session SQL todos table, ordered by creation time and id.""" + """Rows from the session SQL todos table, ordered by creation time with insertion order used + to break ties when available and id used for WITHOUT ROWID tables. + """ @staticmethod def from_dict(obj: Any) -> 'PlanReadSQLTodosResult': @@ -17567,7 +21483,8 @@ class PlanReadSQLTodosWithDependenciesResult: not affect the rows result and vice versa. """ rows: list[PlanSQLTodosRow] - """Rows from the session SQL todos table, ordered by creation time and id. Empty when no + """Rows from the session SQL todos table, ordered by creation time with insertion order used + to break ties when available and id used for WITHOUT ROWID tables. Empty when no database, no todos table, or the SELECT failed. """ @@ -17678,6 +21595,14 @@ class InstalledPluginInfo: for direct repo / URL / local installs; absent for marketplace plugins. Same source yields the same id; distinct sources never collide. """ + installed_from: str | None = None + """Absolute path of the marketplace directory a live plugin was resolved from. Present only + on live, never-persisted records — a plugin belonging to a directory/local marketplace, + which is loaded from its real directory on every pass instead of a copy under the + installed-plugins cache. Its presence is what marks a listed plugin as live: such a + plugin is always present on disk, so `enabled` is its only meaningful state and it is + never "not installed". + """ version: str | None = None """Installed version (when reported by the plugin manifest)""" @@ -17688,8 +21613,9 @@ def from_dict(obj: Any) -> 'InstalledPluginInfo': marketplace = from_str(obj.get("marketplace")) name = from_str(obj.get("name")) direct_source_id = from_union([from_str, from_none], obj.get("directSourceId")) + installed_from = from_union([from_str, from_none], obj.get("installedFrom")) version = from_union([from_str, from_none], obj.get("version")) - return InstalledPluginInfo(enabled, marketplace, name, direct_source_id, version) + return InstalledPluginInfo(enabled, marketplace, name, direct_source_id, installed_from, version) def to_dict(self) -> dict: result: dict = {} @@ -17698,6 +21624,8 @@ def to_dict(self) -> dict: result["name"] = from_str(self.name) if self.direct_source_id is not None: result["directSourceId"] = from_union([from_str, from_none], self.direct_source_id) + if self.installed_from is not None: + result["installedFrom"] = from_union([from_str, from_none], self.installed_from) if self.version is not None: result["version"] = from_union([from_str, from_none], self.version) return result @@ -18497,8 +22425,10 @@ def to_dict(self) -> dict: # 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.""" + """Queued message contents and delivery metadata. + Serializable message fields accepted by queue.insertAt. + """ prompt: str """The user message text.""" @@ -18717,6 +22647,11 @@ class QueuePendingItems: kind: QueuePendingItemsKind """Whether this item is a queued user message or a queued slash command / model change""" + message_id: str | None = None + """Stable identity of the queued user message. Present for message rows and absent for slash + commands and model changes. + """ + @staticmethod def from_dict(obj: Any) -> 'QueuePendingItems': assert isinstance(obj, dict) @@ -18724,7 +22659,8 @@ def from_dict(obj: Any) -> 'QueuePendingItems': display_text = from_str(obj.get("displayText")) id = from_str(obj.get("id")) kind = QueuePendingItemsKind(obj.get("kind")) - return QueuePendingItems(agent_mode, display_text, id, kind) + message_id = from_union([from_str, from_none], obj.get("messageId")) + return QueuePendingItems(agent_mode, display_text, id, kind, message_id) def to_dict(self) -> dict: result: dict = {} @@ -18732,6 +22668,8 @@ def to_dict(self) -> dict: result["displayText"] = from_str(self.display_text) result["id"] = from_str(self.id) result["kind"] = to_enum(QueuePendingItemsKind, self.kind) + if self.message_id is not None: + result["messageId"] = from_union([from_str, from_none], self.message_id) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -18741,10 +22679,8 @@ class _RegisterExtensionToolsParams: """Params to attach an extension loader's tools to a session.""" loader: Any - """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. + """In-process ExtensionLoader handle used only by the CLI and excluded from the public SDK + surface. """ session_id: str """Session to register extension tools on.""" @@ -18841,9 +22777,8 @@ class RemoteControlStatusActive: # Internal: this field is an internal SDK API and is not part of the public surface. prompt_manager: Any = None """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. + excluded from the public SDK surface. Retained as an optional compatibility field; native + remote control does not populate or consume it. """ @staticmethod @@ -18997,11 +22932,14 @@ class SandboxConfigUserPolicyNetwork: """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. + """HTTP proxy for sandboxed process traffic. Linux restricts egress to the proxy endpoint, + requires that endpoint to be reachable over IPv4 (the [::] dual-stack wildcard is + accepted and routed through the IPv4 gateway), and does not support proxy credentials. + macOS relies on applications honoring proxy environment variables. Windows also + configures a per-AppContainer WinHTTP proxy, but enforcement depends on the application's + networking stack. Configure supported credentials in the separate `username` and + `password` fields. A credential-free http:// loopback URL uses the localhost proxy form, + while an https:// or authenticated loopback URL uses the URL form. """ @staticmethod @@ -19324,9 +23262,14 @@ def to_dict(self) -> dict: class SessionFSSqliteTransactionError: """Classified SQLite transaction failure. busyOrLocked guarantees rollback; postCommitAmbiguous must never be retried. + + Classified transaction failure, when execution did not succeed. """ error_class: SessionFSSqliteTransactionErrorClass + """Machine-readable classification of the transaction failure.""" + message: str + """Human-readable transaction failure message.""" @staticmethod def from_dict(obj: Any) -> 'SessionFSSqliteTransactionError': @@ -19436,6 +23379,7 @@ class SessionLimitPredictionTierOption: """AI-credit cap for this tier.""" tier: SessionLimitPredictionTier + """Semantic usage tier.""" @staticmethod def from_dict(obj: Any) -> 'SessionLimitPredictionTierOption': @@ -19450,6 +23394,30 @@ def to_dict(self) -> dict: result["tier"] = to_enum(SessionLimitPredictionTier, self.tier) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionManagedSettings: + """Managed settings an SDK host may inject at session startup. Only permissions are accepted + in this initial contract. + + 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 + """Managed permission policy injected by the SDK host.""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionManagedSettings': + assert isinstance(obj, dict) + permissions = from_union([SessionManagedPermissions.from_dict, from_none], obj.get("permissions")) + return SessionManagedSettings(permissions) + + def to_dict(self) -> dict: + result: dict = {} + 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 SessionOpenOptionsAdditionalContentExclusionPolicyRule: @@ -19457,11 +23425,16 @@ class SessionOpenOptionsAdditionalContentExclusionPolicyRule: conditions, and source. """ paths: list[str] + """Path patterns covered by this rule.""" + source: SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource """Source descriptor for a `sessions.open` content-exclusion rule, with source name and type.""" if_any_match: list[str] | None = None + """Conditions of which at least one must match.""" + if_none_match: list[str] | None = None + """Conditions none of which may match.""" @staticmethod def from_dict(obj: Any) -> 'SessionOpenOptionsAdditionalContentExclusionPolicyRule': @@ -19540,11 +23513,18 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionSettingsJobSnapshot: - """Redacted job settings for a session. The job nonce is excluded.""" + """Redacted job settings for a session. The job nonce is excluded. + Redacted job settings. + """ built_in_tool_availability: SessionSettingsBuiltInToolAvailabilitySnapshot | None = None + """Availability of job-specific built-in tools.""" + event_type: str | None = None + """GitHub Actions event type for the job.""" + is_trigger_job: bool | None = None + """Whether this is the workflow's trigger job.""" @staticmethod def from_dict(obj: Any) -> 'SessionSettingsJobSnapshot': @@ -19762,7 +23742,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class AgentInfo: - """Agent metadata, including identifiers, display details, source, tools, model, MCP + """Agent metadata, including identifiers, display details, source, tools, model, models, MCP servers, skills, and file path. The newly selected custom agent @@ -19789,6 +23769,13 @@ class AgentInfo: """Authored preferred model id for this agent. Runtime model selection may choose a different model; omitted means no authored preference. """ + model_policy: AgentModelPolicy | None = None + """Whether authored models are preferences or required constraints.""" + + models: list[str] | None = None + """Authored preferred model ids for this agent, in priority order. Runtime model selection + chooses the first available 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. @@ -19820,13 +23807,15 @@ def from_dict(obj: Any) -> 'AgentInfo': name = from_str(obj.get("name")) 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")) + model_policy = from_union([AgentModelPolicy, from_none], obj.get("modelPolicy")) + models = from_union([lambda x: from_list(from_str, x), from_none], obj.get("models")) 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, prompt, skills, source, tools, user_invocable) + return AgentInfo(description, display_name, id, name, mcp_servers, model, model_policy, models, path, prompt, skills, source, tools, user_invocable) def to_dict(self) -> dict: result: dict = {} @@ -19838,6 +23827,10 @@ def to_dict(self) -> dict: result["mcpServers"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.mcp_servers) if self.model is not None: result["model"] = from_union([from_str, from_none], self.model) + if self.model_policy is not None: + result["modelPolicy"] = from_union([lambda x: to_enum(AgentModelPolicy, x), from_none], self.model_policy) + if self.models is not None: + result["models"] = from_union([lambda x: from_list(from_str, x), from_none], self.models) if self.path is not None: result["path"] = from_union([from_str, from_none], self.path) if self.prompt is not None: @@ -19905,11 +23898,15 @@ class SkillsInvokedSkill: """Unique identifier for the skill""" path: str - """Path to the SKILL.md file""" - + """Path to the SKILL.md file, or an empty string for an SDK-provided skill without a + filesystem identity + """ allowed_tools: list[str] | None = None """Tools that should be auto-approved when this skill is active, captured at invocation time""" + disable_model_invocation: bool | None = None + """Whether model invocation was disabled when this skill was invoked""" + @staticmethod def from_dict(obj: Any) -> 'SkillsInvokedSkill': assert isinstance(obj, dict) @@ -19918,7 +23915,8 @@ def from_dict(obj: Any) -> 'SkillsInvokedSkill': name = from_str(obj.get("name")) path = from_str(obj.get("path")) allowed_tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("allowedTools")) - return SkillsInvokedSkill(content, invoked_at_turn, name, path, allowed_tools) + disable_model_invocation = from_union([from_bool, from_none], obj.get("disableModelInvocation")) + return SkillsInvokedSkill(content, invoked_at_turn, name, path, allowed_tools, disable_model_invocation) def to_dict(self) -> dict: result: dict = {} @@ -19928,6 +23926,8 @@ def to_dict(self) -> dict: result["path"] = from_str(self.path) if self.allowed_tools is not None: result["allowedTools"] = from_union([lambda x: from_list(from_str, x), from_none], self.allowed_tools) + if self.disable_model_invocation is not None: + result["disableModelInvocation"] = from_union([from_bool, from_none], self.disable_model_invocation) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -19968,6 +23968,62 @@ def to_dict(self) -> dict: result["projectPath"] = from_union([from_str, from_none], self.project_path) 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 _SkillProviderListResult: + """Catalog metadata returned by an SDK session's skill provider. Catalogs are limited to + 1024 descriptors and 1 MiB of aggregate metadata. + """ + skills: list[SkillProviderDescriptor] + """Skill descriptors in provider order. Invocation names must be unique under + case-insensitive comparison. + """ + + @staticmethod + def from_dict(obj: Any) -> '_SkillProviderListResult': + assert isinstance(obj, dict) + skills = from_list(SkillProviderDescriptor.from_dict, obj.get("skills")) + return _SkillProviderListResult(skills) + + def to_dict(self) -> dict: + result: dict = {} + result["skills"] = from_list(lambda x: to_class(SkillProviderDescriptor, x), self.skills) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SlashCommandAddTimelineEntryResult: + entry: SlashCommandTimelineEntry + """Timeline entry the host should append.""" + + kind: ClassVar[str] = "add-timeline-entry" + """Discriminator for an add-timeline-entry result.""" + + prefill_input: str | None = None + """Optional text the host should prefill into the input editor.""" + + runtime_settings_changed: bool | None = None + """Whether command execution changed persisted runtime settings.""" + + @staticmethod + def from_dict(obj: Any) -> 'SlashCommandAddTimelineEntryResult': + assert isinstance(obj, dict) + entry = SlashCommandTimelineEntry.from_dict(obj.get("entry")) + prefill_input = from_union([from_str, from_none], obj.get("prefillInput")) + runtime_settings_changed = from_union([from_bool, from_none], obj.get("runtimeSettingsChanged")) + return SlashCommandAddTimelineEntryResult(entry, prefill_input, runtime_settings_changed) + + def to_dict(self) -> dict: + result: dict = {} + result["entry"] = to_class(SlashCommandTimelineEntry, self.entry) + result["kind"] = self.kind + if self.prefill_input is not None: + result["prefillInput"] = from_union([from_str, from_none], self.prefill_input) + if self.runtime_settings_changed is not None: + result["runtimeSettingsChanged"] = from_union([from_bool, from_none], self.runtime_settings_changed) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SlashCommandAgentPromptResult: @@ -20029,6 +24085,9 @@ class SlashCommandCompletedResult: message: str | None = None """Optional user-facing message describing the completed command""" + mode: SessionMode | None = None + """Optional target session mode applied without submitting an agent prompt""" + runtime_settings_changed: bool | None = None """True when the invocation mutated user runtime settings; consumers caching settings should refresh @@ -20038,14 +24097,17 @@ class SlashCommandCompletedResult: def from_dict(obj: Any) -> 'SlashCommandCompletedResult': assert isinstance(obj, dict) message = from_union([from_str, from_none], obj.get("message")) + mode = from_union([SessionMode, from_none], obj.get("mode")) runtime_settings_changed = from_union([from_bool, from_none], obj.get("runtimeSettingsChanged")) - return SlashCommandCompletedResult(message, runtime_settings_changed) + return SlashCommandCompletedResult(message, mode, runtime_settings_changed) def to_dict(self) -> dict: result: dict = {} result["kind"] = self.kind if self.message is not None: result["message"] = from_union([from_str, from_none], self.message) + if self.mode is not None: + result["mode"] = from_union([lambda x: to_enum(SessionMode, x), from_none], self.mode) if self.runtime_settings_changed is not None: result["runtimeSettingsChanged"] = from_union([from_bool, from_none], self.runtime_settings_changed) return result @@ -20092,6 +24154,39 @@ def to_dict(self) -> dict: result["runtimeSettingsChanged"] = from_union([from_bool, from_none], self.runtime_settings_changed) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SlashCommandSetPlanModelResult: + kind: ClassVar[str] = "set-plan-model" + """Discriminator for a set-plan-model result.""" + + message: str + """User-facing confirmation message for the plan-model selection.""" + + plan_model: str | None = None + """Dedicated model selected for plan mode.""" + + runtime_settings_changed: bool | None = None + """Whether command execution changed persisted runtime settings.""" + + @staticmethod + def from_dict(obj: Any) -> 'SlashCommandSetPlanModelResult': + assert isinstance(obj, dict) + message = from_str(obj.get("message")) + plan_model = from_union([from_str, from_none], obj.get("planModel")) + runtime_settings_changed = from_union([from_bool, from_none], obj.get("runtimeSettingsChanged")) + return SlashCommandSetPlanModelResult(message, plan_model, runtime_settings_changed) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = self.kind + result["message"] = from_str(self.message) + if self.plan_model is not None: + result["planModel"] = from_union([from_str, from_none], self.plan_model) + if self.runtime_settings_changed is not None: + result["runtimeSettingsChanged"] = from_union([from_bool, from_none], self.runtime_settings_changed) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class TaskAgentProgress: @@ -20281,6 +24376,35 @@ def to_dict(self) -> dict: result["pid"] = from_union([from_int, from_none], self.pid) 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 _FactoryToolRunOptions: + """Options for an internal tool-originated factory invocation. + + Tool-originated factory invocation options. + """ + limits: FactoryRunLimits | None = None + """Per-invocation resource ceiling overrides.""" + + resume_from_run_id: str | None = None + """Run identifier whose journal and progress should seed this resumed run.""" + + @staticmethod + def from_dict(obj: Any) -> '_FactoryToolRunOptions': + assert isinstance(obj, dict) + limits = from_union([FactoryRunLimits.from_dict, from_none], obj.get("limits")) + resume_from_run_id = from_union([from_str, from_none], obj.get("resumeFromRunId")) + return _FactoryToolRunOptions(limits, resume_from_run_id) + + def to_dict(self) -> dict: + result: dict = {} + if self.limits is not None: + result["limits"] = from_union([lambda x: to_class(FactoryRunLimits, x), from_none], self.limits) + if self.resume_from_run_id is not None: + result["resumeFromRunId"] = from_union([from_str, from_none], self.resume_from_run_id) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MCPAppsCallToolRequest: @@ -20318,6 +24442,20 @@ def to_dict(self) -> dict: result["arguments"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.arguments) return result +# Experimental: this type is part of an experimental API and may change or be removed. +class MCPServerConfigDeferTools(Enum): + """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) + + Controls whether tools can be loaded on demand. + + Controls whether the runtime may defer loading an external tool definition. + + Tool-loading deferral policy. + """ + AUTO = "auto" + NEVER = "never" + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MCPToolUI: @@ -20442,6 +24580,9 @@ class TaskAgentInfo: completed_at: datetime | None = None """ISO 8601 timestamp when the task finished""" + display_name: str | None = None + """Friendly, non-unique name intended for display""" + error: str | None = None """Error message when the task failed""" @@ -20477,6 +24618,7 @@ def from_dict(obj: Any) -> 'TaskAgentInfo': active_time_ms = from_union([from_int, from_none], obj.get("activeTimeMs")) can_promote_to_background = from_union([from_bool, from_none], obj.get("canPromoteToBackground")) completed_at = from_union([from_datetime, from_none], obj.get("completedAt")) + display_name = from_union([from_str, from_none], obj.get("displayName")) error = from_union([from_str, from_none], obj.get("error")) execution_mode = from_union([TaskExecutionMode, from_none], obj.get("executionMode")) idle_since = from_union([from_datetime, from_none], obj.get("idleSince")) @@ -20484,7 +24626,7 @@ def from_dict(obj: Any) -> 'TaskAgentInfo': model = from_union([from_str, from_none], obj.get("model")) resolved_model = from_union([from_str, from_none], obj.get("resolvedModel")) result = from_union([from_str, from_none], obj.get("result")) - return TaskAgentInfo(agent_type, description, id, prompt, started_at, status, tool_call_id, active_started_at, active_time_ms, can_promote_to_background, completed_at, error, execution_mode, idle_since, latest_response, model, resolved_model, result) + return TaskAgentInfo(agent_type, description, id, prompt, started_at, status, tool_call_id, active_started_at, active_time_ms, can_promote_to_background, completed_at, display_name, error, execution_mode, idle_since, latest_response, model, resolved_model, result) def to_dict(self) -> dict: result: dict = {} @@ -20504,6 +24646,8 @@ def to_dict(self) -> dict: result["canPromoteToBackground"] = from_union([from_bool, from_none], self.can_promote_to_background) if self.completed_at is not None: result["completedAt"] = from_union([lambda x: x.isoformat(), from_none], self.completed_at) + if self.display_name is not None: + result["displayName"] = from_union([from_str, from_none], self.display_name) if self.error is not None: result["error"] = from_union([from_str, from_none], self.error) if self.execution_mode is not None: @@ -20539,6 +24683,58 @@ def to_dict(self) -> dict: result["tools"] = from_list(lambda x: to_class(Tool, x), self.tools) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ToolsShellDescriptorConfig: + """Shell-specific names and description lines for shell tools. + + Shell-specific names and description lines used to materialize built-in shell tool + descriptors. + """ + description_lines: list[str] + """Additional model-facing shell description lines.""" + + display_name: str + """Human-readable shell name.""" + + list_shells_tool_name: str + """Tool name used to list active shells.""" + + read_shell_tool_name: str + """Tool name used to read shell output.""" + + shell_tool_name: str + """Tool name used to start shell commands.""" + + shell_type: str + """Stable shell type identifier.""" + + stop_shell_tool_name: str + """Tool name used to stop shell commands.""" + + @staticmethod + def from_dict(obj: Any) -> 'ToolsShellDescriptorConfig': + assert isinstance(obj, dict) + description_lines = from_list(from_str, obj.get("descriptionLines")) + display_name = from_str(obj.get("displayName")) + list_shells_tool_name = from_str(obj.get("listShellsToolName")) + read_shell_tool_name = from_str(obj.get("readShellToolName")) + shell_tool_name = from_str(obj.get("shellToolName")) + shell_type = from_str(obj.get("shellType")) + stop_shell_tool_name = from_str(obj.get("stopShellToolName")) + return ToolsShellDescriptorConfig(description_lines, display_name, list_shells_tool_name, read_shell_tool_name, shell_tool_name, shell_type, stop_shell_tool_name) + + def to_dict(self) -> dict: + result: dict = {} + result["descriptionLines"] = from_list(from_str, self.description_lines) + result["displayName"] = from_str(self.display_name) + result["listShellsToolName"] = from_str(self.list_shells_tool_name) + result["readShellToolName"] = from_str(self.read_shell_tool_name) + result["shellToolName"] = from_str(self.shell_tool_name) + result["shellType"] = from_str(self.shell_type) + result["stopShellToolName"] = from_str(self.stop_shell_tool_name) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class UserRequestedShellCommandResult: @@ -20833,6 +25029,9 @@ class UIElicitationResponse: action: UIElicitationResponseAction """The user's response: accept (submitted), decline (rejected), or cancel (dismissed)""" + meta: dict[str, Any] | None = None + """MCP response metadata.""" + content: dict[str, float | bool | list[str] | str] | None = None """The form values submitted by the user (present when action is 'accept')""" @@ -20840,12 +25039,15 @@ class UIElicitationResponse: def from_dict(obj: Any) -> 'UIElicitationResponse': assert isinstance(obj, dict) action = UIElicitationResponseAction(obj.get("action")) + meta = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("_meta")) content = from_union([lambda x: from_dict(lambda x: from_union([from_float, from_bool, lambda x: from_list(from_str, x), from_str], x), x), from_none], obj.get("content")) - return UIElicitationResponse(action, content) + return UIElicitationResponse(action, meta, content) def to_dict(self) -> dict: result: dict = {} result["action"] = to_enum(UIElicitationResponseAction, self.action) + if self.meta is not None: + result["_meta"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.meta) if self.content is not None: result["content"] = from_union([lambda x: from_dict(lambda x: from_union([to_float, from_bool, lambda x: from_list(from_str, x), from_str], x), x), from_none], self.content) return result @@ -21249,6 +25451,47 @@ def to_dict(self) -> dict: result["logCapture"] = from_union([lambda x: to_class(AgentRegistryLogCapture, x), from_none], self.log_capture) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MCPPlanProvenance: + """Origin and semantic digest of the exact validated JSON MCP card content bound to this + plan. + + Provenance of the exact validated JSON MCP card content bound privately to a completed + plan and its opaque handle. + """ + authority: str + """Authority associated with the validated card, without path, query, or credentials. Inert + untrusted data. + """ + card_digest: CardDigest + """Semantic digest of the exact validated JSON content bound to the plan handle.""" + + media_type: MCPServerCardMediaType + """JSON MCP media type the validated card was interpreted as.""" + + validated_at: str + """ISO 8601 timestamp at which the runtime completed strict parsing and schema validation of + the card content. + """ + + @staticmethod + def from_dict(obj: Any) -> 'MCPPlanProvenance': + assert isinstance(obj, dict) + authority = from_str(obj.get("authority")) + card_digest = CardDigest.from_dict(obj.get("cardDigest")) + media_type = MCPServerCardMediaType(obj.get("mediaType")) + validated_at = from_str(obj.get("validatedAt")) + return MCPPlanProvenance(authority, card_digest, media_type, validated_at) + + def to_dict(self) -> dict: + result: dict = {} + result["authority"] = from_str(self.authority) + result["cardDigest"] = to_class(CardDigest, self.card_digest) + result["mediaType"] = to_enum(MCPServerCardMediaType, self.media_type) + result["validatedAt"] = from_str(self.validated_at) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SlashCommandInfo: @@ -21394,6 +25637,230 @@ def to_dict(self) -> dict: result["openCanvases"] = from_list(lambda x: to_class(OpenCanvasInstance, x), self.open_canvases) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class CatalogAISkillCandidate: + """An inert AI skill catalog result. AI skills are discovery-only and cannot be represented + as installable through this surface. + """ + display_name: str + """Display name taken verbatim from the card. Inert untrusted text.""" + + handle: str + """Opaque, runtime-instance scoped, TTL-bound, single-use handle for this candidate. Carries + no readable information and is rejected when stale, replayed, or presented to a different + runtime instance. Never logged. + """ + handle_expires_at: str + """ISO 8601 timestamp after which the handle is stale and will be rejected.""" + + installability: Installability + """AI skills are discovery-only and cannot be installed through this surface""" + + kind: CatalogAISkillCandidateKind + """Discriminator: this candidate describes an AI skill""" + + media_type: MediaType + """Media type of the underlying AI skill card""" + + provenance: CatalogAISkillCandidateProvenance + """Where the catalog reference was observed, without the card itself or any content digest.""" + + source: CatalogCandidateSource + """Where the card came from: exactly one of a URL or embedded data, encoded as a tagged + union so neither both nor neither can be represented. + """ + description: str | None = None + """Description taken verbatim from the card. Inert untrusted text.""" + + publisher: str | None = None + """Publisher taken verbatim from the card. Inert untrusted text.""" + + @staticmethod + def from_dict(obj: Any) -> 'CatalogAISkillCandidate': + assert isinstance(obj, dict) + display_name = from_str(obj.get("displayName")) + handle = from_str(obj.get("handle")) + handle_expires_at = from_str(obj.get("handleExpiresAt")) + installability = Installability(obj.get("installability")) + kind = CatalogAISkillCandidateKind(obj.get("kind")) + media_type = MediaType(obj.get("mediaType")) + provenance = CatalogAISkillCandidateProvenance.from_dict(obj.get("provenance")) + source = _load_CatalogCandidateSource(obj.get("source")) + description = from_union([from_str, from_none], obj.get("description")) + publisher = from_union([from_str, from_none], obj.get("publisher")) + return CatalogAISkillCandidate(display_name, handle, handle_expires_at, installability, kind, media_type, provenance, source, description, publisher) + + def to_dict(self) -> dict: + result: dict = {} + result["displayName"] = from_str(self.display_name) + result["handle"] = from_str(self.handle) + result["handleExpiresAt"] = from_str(self.handle_expires_at) + result["installability"] = to_enum(Installability, self.installability) + result["kind"] = to_enum(CatalogAISkillCandidateKind, self.kind) + result["mediaType"] = to_enum(MediaType, self.media_type) + result["provenance"] = to_class(CatalogAISkillCandidateProvenance, self.provenance) + result["source"] = (self.source).to_dict() + if self.description is not None: + result["description"] = from_union([from_str, from_none], self.description) + if self.publisher is not None: + result["publisher"] = from_union([from_str, from_none], self.publisher) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class CatalogCandidate: + """One inert catalog result, represented as an MCP server or discovery-only AI skill variant + so kind, media type, provenance, and installability cannot contradict each other. + + An inert MCP server catalog result. Every free-text field is untrusted external data and + must never be treated as an instruction, and the handle is the only way to refer to the + candidate in a later operation. + + An inert AI skill catalog result. AI skills are discovery-only and cannot be represented + as installable through this surface. + """ + display_name: str + """Display name taken verbatim from the card. Inert untrusted text.""" + + handle: str + """Opaque, runtime-instance scoped, TTL-bound, single-use handle for this candidate. Carries + no readable information and is rejected when stale, replayed, or presented to a different + runtime instance. Never logged. + """ + handle_expires_at: str + """ISO 8601 timestamp after which the handle is stale and will be rejected.""" + + installability: CatalogCandidateInstallability + """Whether this MCP server can be planned for installation, and if policy prevents it. + + AI skills are discovery-only and cannot be installed through this surface + """ + kind: CatalogCandidateKind + """Discriminator: this candidate describes an MCP server + + Discriminator: this candidate describes an AI skill + """ + media_type: CatalogMediaType + """JSON MCP media type of the underlying card. + + Media type of the underlying AI skill card + """ + provenance: CatalogCandidateProvenance + """Where the catalog reference was observed, without the card itself or any content digest.""" + + source: CatalogCandidateSource + """Where the card came from: exactly one of a URL or embedded data, encoded as a tagged + union so neither both nor neither can be represented. + """ + description: str | None = None + """Description taken verbatim from the card. Inert untrusted text.""" + + publisher: str | None = None + """Publisher taken verbatim from the card. Inert untrusted text.""" + + @staticmethod + def from_dict(obj: Any) -> 'CatalogCandidate': + assert isinstance(obj, dict) + display_name = from_str(obj.get("displayName")) + handle = from_str(obj.get("handle")) + handle_expires_at = from_str(obj.get("handleExpiresAt")) + installability = CatalogCandidateInstallability(obj.get("installability")) + kind = CatalogCandidateKind(obj.get("kind")) + media_type = CatalogMediaType(obj.get("mediaType")) + provenance = CatalogCandidateProvenance.from_dict(obj.get("provenance")) + source = _load_CatalogCandidateSource(obj.get("source")) + description = from_union([from_str, from_none], obj.get("description")) + publisher = from_union([from_str, from_none], obj.get("publisher")) + return CatalogCandidate(display_name, handle, handle_expires_at, installability, kind, media_type, provenance, source, description, publisher) + + def to_dict(self) -> dict: + result: dict = {} + result["displayName"] = from_str(self.display_name) + result["handle"] = from_str(self.handle) + result["handleExpiresAt"] = from_str(self.handle_expires_at) + result["installability"] = to_enum(CatalogCandidateInstallability, self.installability) + result["kind"] = to_enum(CatalogCandidateKind, self.kind) + result["mediaType"] = to_enum(CatalogMediaType, self.media_type) + result["provenance"] = to_class(CatalogCandidateProvenance, self.provenance) + result["source"] = (self.source).to_dict() + if self.description is not None: + result["description"] = from_union([from_str, from_none], self.description) + if self.publisher is not None: + result["publisher"] = from_union([from_str, from_none], self.publisher) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class CatalogMCPServerCandidate: + """An inert MCP server catalog result. Every free-text field is untrusted external data and + must never be treated as an instruction, and the handle is the only way to refer to the + candidate in a later operation. + """ + display_name: str + """Display name taken verbatim from the card. Inert untrusted text.""" + + handle: str + """Opaque, runtime-instance scoped, TTL-bound, single-use handle for this candidate. Carries + no readable information and is rejected when stale, replayed, or presented to a different + runtime instance. Never logged. + """ + handle_expires_at: str + """ISO 8601 timestamp after which the handle is stale and will be rejected.""" + + installability: CatalogMCPServerInstallabilityEnum + """Whether this MCP server can be planned for installation, and if policy prevents it.""" + + kind: CatalogMCPServerCandidateKind + """Discriminator: this candidate describes an MCP server""" + + media_type: MCPServerCardMediaType + """JSON MCP media type of the underlying card.""" + + provenance: CatalogMCPServerCandidateProvenance + """Where the catalog reference was observed, without the card itself or any content digest.""" + + source: CatalogCandidateSource + """Where the card came from: exactly one of a URL or embedded data, encoded as a tagged + union so neither both nor neither can be represented. + """ + description: str | None = None + """Description taken verbatim from the card. Inert untrusted text.""" + + publisher: str | None = None + """Publisher taken verbatim from the card. Inert untrusted text.""" + + @staticmethod + def from_dict(obj: Any) -> 'CatalogMCPServerCandidate': + assert isinstance(obj, dict) + display_name = from_str(obj.get("displayName")) + handle = from_str(obj.get("handle")) + handle_expires_at = from_str(obj.get("handleExpiresAt")) + installability = CatalogMCPServerInstallabilityEnum(obj.get("installability")) + kind = CatalogMCPServerCandidateKind(obj.get("kind")) + media_type = MCPServerCardMediaType(obj.get("mediaType")) + provenance = CatalogMCPServerCandidateProvenance.from_dict(obj.get("provenance")) + source = _load_CatalogCandidateSource(obj.get("source")) + description = from_union([from_str, from_none], obj.get("description")) + publisher = from_union([from_str, from_none], obj.get("publisher")) + return CatalogMCPServerCandidate(display_name, handle, handle_expires_at, installability, kind, media_type, provenance, source, description, publisher) + + def to_dict(self) -> dict: + result: dict = {} + result["displayName"] = from_str(self.display_name) + result["handle"] = from_str(self.handle) + result["handleExpiresAt"] = from_str(self.handle_expires_at) + result["installability"] = to_enum(CatalogMCPServerInstallabilityEnum, self.installability) + result["kind"] = to_enum(CatalogMCPServerCandidateKind, self.kind) + result["mediaType"] = to_enum(MCPServerCardMediaType, self.media_type) + result["provenance"] = to_class(CatalogMCPServerCandidateProvenance, self.provenance) + result["source"] = (self.source).to_dict() + if self.description is not None: + result["description"] = from_union([from_str, from_none], self.description) + if self.publisher is not None: + result["publisher"] = from_union([from_str, from_none], self.publisher) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class DebugCollectLogsResult: @@ -21455,6 +25922,43 @@ def to_dict(self) -> dict: result["mode"] = to_enum(DiscoveredExtensionMode, self.mode) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class HooksDiscoverResult: + """Server-discovered hook actions and partial-load diagnostics from user, repository, + plugin, and managed-policy sources. Concrete sessions may include additional + session-specific hook sources. + """ + errors: list[str] + """Errors for hook sources or actions that could not be loaded, making the result partially + incomplete. Other valid actions are still returned. Project-resolution and + repository-settings errors are prefixed with their project path. + """ + hooks: list[DiscoveredHook] + """All discovered hook actions. Byte-identical actions remain separate rows even when they + share a disable key. + """ + warnings: list[str] + """Non-fatal source-loading warnings. Discovery remains complete for the affected source, + although the source had a recoverable issue. Repository-settings warnings are prefixed + with their project path when attribution is available. + """ + + @staticmethod + def from_dict(obj: Any) -> 'HooksDiscoverResult': + assert isinstance(obj, dict) + errors = from_list(from_str, obj.get("errors")) + hooks = from_list(DiscoveredHook.from_dict, obj.get("hooks")) + warnings = from_list(from_str, obj.get("warnings")) + return HooksDiscoverResult(errors, hooks, warnings) + + def to_dict(self) -> dict: + result: dict = {} + result["errors"] = from_list(from_str, self.errors) + result["hooks"] = from_list(lambda x: to_class(DiscoveredHook, x), self.hooks) + result["warnings"] = from_list(from_str, self.warnings) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ExtensionList: @@ -21474,6 +25978,36 @@ def to_dict(self) -> dict: 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. +@dataclass +class PermissionDecisionApproveForLocationApprovalExtensionEnvAccess: + """Location-scoped approval details for an extension's access to sensitive environment + variables, keyed by extension name and the exact set of variable names. + """ + environment_variables: list[str] + """Names of the sensitive environment variables this approval covers. Values are never + persisted. + """ + extension_name: str + """Extension name.""" + + kind: ClassVar[str] = "extension-env-access" + """Approval covering an extension's request to read sensitive environment variables.""" + + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecisionApproveForLocationApprovalExtensionEnvAccess': + assert isinstance(obj, dict) + environment_variables = from_list(from_str, obj.get("environmentVariables")) + extension_name = from_str(obj.get("extensionName")) + return PermissionDecisionApproveForLocationApprovalExtensionEnvAccess(environment_variables, extension_name) + + def to_dict(self) -> dict: + result: dict = {} + result["environmentVariables"] = from_list(from_str, self.environment_variables) + result["extensionName"] = from_str(self.extension_name) + result["kind"] = self.kind + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess: @@ -21498,6 +26032,36 @@ def to_dict(self) -> dict: result["kind"] = self.kind return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class PermissionDecisionApproveForSessionApprovalExtensionEnvAccess: + """Session-scoped approval details for an extension's access to sensitive environment + variables, keyed by extension name and the exact set of variable names. + """ + environment_variables: list[str] + """Names of the sensitive environment variables this approval covers. Values are never + persisted. + """ + extension_name: str + """Extension name.""" + + kind: ClassVar[str] = "extension-env-access" + """Approval covering an extension's request to read sensitive environment variables.""" + + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecisionApproveForSessionApprovalExtensionEnvAccess': + assert isinstance(obj, dict) + environment_variables = from_list(from_str, obj.get("environmentVariables")) + extension_name = from_str(obj.get("extensionName")) + return PermissionDecisionApproveForSessionApprovalExtensionEnvAccess(environment_variables, extension_name) + + def to_dict(self) -> dict: + result: dict = {} + result["environmentVariables"] = from_list(from_str, self.environment_variables) + result["extensionName"] = from_str(self.extension_name) + result["kind"] = self.kind + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess: @@ -21522,6 +26086,36 @@ def to_dict(self) -> dict: result["kind"] = self.kind return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess: + """Location-persisted tool approval details for an extension's access to sensitive + environment variables, keyed by extension name and the exact set of variable names. + """ + environment_variables: list[str] + """Names of the sensitive environment variables this approval covers. Values are never + persisted. + """ + extension_name: str + """Extension name.""" + + kind: ClassVar[str] = "extension-env-access" + """Approval covering an extension's request to read sensitive environment variables.""" + + @staticmethod + def from_dict(obj: Any) -> 'PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess': + assert isinstance(obj, dict) + environment_variables = from_list(from_str, obj.get("environmentVariables")) + extension_name = from_str(obj.get("extensionName")) + return PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess(environment_variables, extension_name) + + def to_dict(self) -> dict: + result: dict = {} + result["environmentVariables"] = from_list(from_str, self.environment_variables) + result["extensionName"] = from_str(self.extension_name) + result["kind"] = self.kind + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess: @@ -21610,6 +26204,124 @@ def to_dict(self) -> dict: result["toolTelemetry"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.tool_telemetry) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ToolResultExpanded: + """Expanded canonical tool result + + Expanded canonical result returned by a session tool. + + Final expanded result returned by the task_complete tool. + """ + result_type: ToolResultType + """Execution outcome classification.""" + + text_result_for_llm: str + """Text result returned to the model.""" + + binary_results_for_llm: list[ExternalToolTextResultForLlmBinaryResultsForLlm] | None = None + """Base64-encoded binary results returned to the model.""" + + citable_sources: list[Any] | None = None + """Sources returned by the tool that the model may cite.""" + + contents: list[ExternalToolTextResultForLlmContent] | None = None + """Structured content blocks returned to the model.""" + + error: str | None = None + """Error message for an unsuccessful execution.""" + + mcp_meta: dict[str, Any] | None = None + """Metadata propagated with the tool result, including information-flow labels.""" + + new_messages: list[ToolResultNewMessage] | None = None + """Messages to inject after the tool result.""" + + post_tool_use_failure_hooks_processed: bool | None = None + """Whether post-tool-use failure hooks have already processed this result.""" + + session_log: str | None = None + """Detailed log content available for session display.""" + + skill_invocation: Any = None + """Skill invocation metadata produced by the tool.""" + + skip_large_output_processing: bool | None = None + """Whether large-output post-processing should be skipped.""" + + structured_content: Any = None + """Structured result content in addition to the model-facing text.""" + + task_completion_decision: TaskCompletionDecision | None = None + """Completion-review decision produced by the task-completion tool.""" + + tool_references: list[str] | None = None + """Deferred tool names made available by this result.""" + + tool_telemetry: Any = None + """Tool-specific telemetry payload.""" + + ui_resource: Any = None + """Optional UI resource produced by the tool.""" + + @staticmethod + def from_dict(obj: Any) -> 'ToolResultExpanded': + assert isinstance(obj, dict) + result_type = ToolResultType(obj.get("resultType")) + text_result_for_llm = from_str(obj.get("textResultForLlm")) + binary_results_for_llm = from_union([lambda x: from_list(ExternalToolTextResultForLlmBinaryResultsForLlm.from_dict, x), from_none], obj.get("binaryResultsForLlm")) + citable_sources = from_union([lambda x: from_list(lambda x: x, x), from_none], obj.get("citableSources")) + contents = from_union([lambda x: from_list(_load_ExternalToolTextResultForLlmContent, x), from_none], obj.get("contents")) + error = from_union([from_str, from_none], obj.get("error")) + mcp_meta = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("mcpMeta")) + new_messages = from_union([lambda x: from_list(ToolResultNewMessage.from_dict, x), from_none], obj.get("newMessages")) + post_tool_use_failure_hooks_processed = from_union([from_bool, from_none], obj.get("postToolUseFailureHooksProcessed")) + session_log = from_union([from_str, from_none], obj.get("sessionLog")) + skill_invocation = obj.get("skillInvocation") + skip_large_output_processing = from_union([from_bool, from_none], obj.get("skipLargeOutputProcessing")) + structured_content = obj.get("structuredContent") + task_completion_decision = from_union([TaskCompletionDecision.from_dict, from_none], obj.get("taskCompletionDecision")) + tool_references = from_union([lambda x: from_list(from_str, x), from_none], obj.get("toolReferences")) + tool_telemetry = obj.get("toolTelemetry") + ui_resource = obj.get("uiResource") + return ToolResultExpanded(result_type, text_result_for_llm, binary_results_for_llm, citable_sources, contents, error, mcp_meta, new_messages, post_tool_use_failure_hooks_processed, session_log, skill_invocation, skip_large_output_processing, structured_content, task_completion_decision, tool_references, tool_telemetry, ui_resource) + + def to_dict(self) -> dict: + result: dict = {} + result["resultType"] = to_enum(ToolResultType, self.result_type) + result["textResultForLlm"] = from_str(self.text_result_for_llm) + if self.binary_results_for_llm is not None: + result["binaryResultsForLlm"] = from_union([lambda x: from_list(lambda x: to_class(ExternalToolTextResultForLlmBinaryResultsForLlm, x), x), from_none], self.binary_results_for_llm) + if self.citable_sources is not None: + result["citableSources"] = from_union([lambda x: from_list(lambda x: x, x), from_none], self.citable_sources) + if self.contents is not None: + result["contents"] = from_union([lambda x: from_list(lambda x: (x).to_dict(), x), from_none], self.contents) + if self.error is not None: + result["error"] = from_union([from_str, from_none], self.error) + if self.mcp_meta is not None: + result["mcpMeta"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.mcp_meta) + if self.new_messages is not None: + result["newMessages"] = from_union([lambda x: from_list(lambda x: to_class(ToolResultNewMessage, x), x), from_none], self.new_messages) + if self.post_tool_use_failure_hooks_processed is not None: + result["postToolUseFailureHooksProcessed"] = from_union([from_bool, from_none], self.post_tool_use_failure_hooks_processed) + if self.session_log is not None: + result["sessionLog"] = from_union([from_str, from_none], self.session_log) + if self.skill_invocation is not None: + result["skillInvocation"] = self.skill_invocation + if self.skip_large_output_processing is not None: + result["skipLargeOutputProcessing"] = from_union([from_bool, from_none], self.skip_large_output_processing) + if self.structured_content is not None: + result["structuredContent"] = self.structured_content + if self.task_completion_decision is not None: + result["taskCompletionDecision"] = from_union([lambda x: to_class(TaskCompletionDecision, x), from_none], self.task_completion_decision) + if self.tool_references is not None: + result["toolReferences"] = from_union([lambda x: from_list(from_str, x), from_none], self.tool_references) + if self.tool_telemetry is not None: + result["toolTelemetry"] = self.tool_telemetry + if self.ui_resource is not None: + result["uiResource"] = self.ui_resource + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ExternalToolTextResultForLlmContentResourceLink: @@ -21674,9 +26386,16 @@ class FactoryRunTerminal: """Prompt-safe terminal factory outcome.""" error: str | None = None + """Human-readable terminal error.""" + failure: FactoryRunFailure | None = None + """Machine-readable terminal failure.""" + reason: str | None = None + """Human-readable terminal reason.""" + result_preview: str | None = None + """Prompt-safe preview of the completed result.""" @staticmethod def from_dict(obj: Any) -> 'FactoryRunTerminal': @@ -21712,11 +26431,15 @@ class FactoryRunResult: status: FactoryRunStatus """Current or terminal factory run status.""" + attempt: int | None = None + """One-based execution attempt represented by this envelope. Absent before the first attempt + starts or when returned by an older runtime. + """ error: str | None = None """Error message for an errored run.""" failure: FactoryRunFailure | None = None - """Machine-readable failure details for an errored run.""" + """Machine-readable failure details for a halted or errored run.""" reason: str | None = None """Reason for a halted or cancelled run.""" @@ -21732,17 +26455,20 @@ def from_dict(obj: Any) -> 'FactoryRunResult': assert isinstance(obj, dict) run_id = from_str(obj.get("runId")) status = FactoryRunStatus(obj.get("status")) + attempt = from_union([from_int, from_none], obj.get("attempt")) 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 = obj.get("result") snapshot = obj.get("snapshot") - return FactoryRunResult(run_id, status, error, failure, reason, result, snapshot) + return FactoryRunResult(run_id, status, attempt, error, failure, reason, result, snapshot) def to_dict(self) -> dict: result: dict = {} result["runId"] = from_str(self.run_id) result["status"] = to_enum(FactoryRunStatus, self.status) + if self.attempt is not None: + result["attempt"] = from_union([from_int, from_none], self.attempt) if self.error is not None: result["error"] = from_union([from_str, from_none], self.error) if self.failure is not None: @@ -21787,16 +26513,27 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class FactoryProgressPage: - """A bidirectional page of factory progress.""" + """A bidirectional page of factory progress. + Bidirectional page of durable factory progress. + """ has_more_newer: bool + """Whether progress records newer than this page exist.""" + has_more_older: bool + """Whether progress records older than this page exist.""" + records: list[FactoryProgressLine] + """Progress records in sequence order.""" + revision: int """Run revision reflected by this page.""" newest_seq: int | None = None + """Newest sequence number in this page, or null when empty.""" + oldest_seq: int | None = None + """Oldest sequence number in this page, or null when empty.""" @staticmethod def from_dict(obj: Any) -> 'FactoryProgressPage': @@ -21849,6 +26586,30 @@ 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 MCPOauthHandlePendingRequest: + """Pending MCP OAuth request ID and host-provided token or cancellation response.""" + + request_id: str + """OAuth request identifier from the mcp.oauth_required event""" + + result: MCPOauthPendingRequestResponse + """Host response to the pending OAuth request.""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPOauthHandlePendingRequest': + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + result = MCPOauthPendingRequestResponse.from_dict(obj.get("result")) + return MCPOauthHandlePendingRequest(request_id, result) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + result["result"] = to_class(MCPOauthPendingRequestResponse, self.result) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class HistoryRewindResult: @@ -21967,6 +26728,13 @@ class InstalledPlugin: cache_path: str | None = None """Path where the plugin is cached locally""" + installed_from: str | None = None + """Absolute path of the marketplace directory a live plugin was resolved from. Present only + on live, never-persisted records — those synthesized at session start for a + directory/local marketplace, whose cache_path points at the real plugin directory on disk + rather than a copy under the installed-plugins cache. Its presence is what marks a record + as live, and no record carrying it is ever written to the persisted installedPlugins key. + """ source: InstalledPluginSource | str | None = None """Source for direct repo installs (when marketplace is empty)""" @@ -21988,10 +26756,11 @@ def from_dict(obj: Any) -> 'InstalledPlugin': marketplace = from_str(obj.get("marketplace")) name = from_str(obj.get("name")) cache_path = from_union([from_str, from_none], obj.get("cache_path")) + installed_from = from_union([from_str, from_none], obj.get("installed_from")) 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, source_sha, version) + return InstalledPlugin(enabled, installed_at, marketplace, name, cache_path, installed_from, source, source_sha, version) def to_dict(self) -> dict: result: dict = {} @@ -22001,6 +26770,8 @@ def to_dict(self) -> dict: result["name"] = from_str(self.name) if self.cache_path is not None: result["cache_path"] = from_union([from_str, from_none], self.cache_path) + if self.installed_from is not None: + result["installed_from"] = from_union([from_str, from_none], self.installed_from) 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: @@ -22030,6 +26801,13 @@ class SessionInstalledPlugin: cache_path: str | None = None """Path where the plugin is cached locally""" + installed_from: str | None = None + """Absolute path of the marketplace directory a live plugin was resolved from. Present only + on live, never-persisted records — those synthesized at session start for a + directory/local marketplace, whose cache_path points at the real plugin directory on disk + rather than a copy under the installed-plugins cache. Its presence is what marks a record + as live, and no record carrying it is ever written to the persisted installedPlugins key. + """ source: SessionInstalledPluginSource | str | None = None """Source descriptor for direct repo installs (when marketplace is empty)""" @@ -22051,10 +26829,11 @@ def from_dict(obj: Any) -> 'SessionInstalledPlugin': marketplace = from_str(obj.get("marketplace")) name = from_str(obj.get("name")) cache_path = from_union([from_str, from_none], obj.get("cache_path")) + installed_from = from_union([from_str, from_none], obj.get("installed_from")) 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, source_sha, version) + return SessionInstalledPlugin(enabled, installed_at, marketplace, name, cache_path, installed_from, source, source_sha, version) def to_dict(self) -> dict: result: dict = {} @@ -22064,6 +26843,8 @@ def to_dict(self) -> dict: result["name"] = from_str(self.name) if self.cache_path is not None: result["cache_path"] = from_union([from_str, from_none], self.cache_path) + if self.installed_from is not None: + result["installed_from"] = from_union([from_str, from_none], self.installed_from) 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: @@ -22215,6 +26996,16 @@ class RemoteSessionMetadataValue: context: SessionContext | None = None """Most recent working directory context.""" + host_activity: str | None = None + """Host-supplied human description of what the session is doing right now ("running tests", + "waiting for approval"). Optional in the protocol and absent on hosts that do not publish + it, so never rely on it -- it enriches `hostStatus`, it does not replace it. + """ + host_status: RemoteSessionHostStatus | None = None + """Live status as the owning host reports it in its session listing, so a row for a session + running elsewhere can show that it is running. Absent for hosts that publish no such + status (the cloud task managers), which read as idle. + """ name: str | None = None """Optional human-friendly name set via /rename.""" @@ -22247,6 +27038,8 @@ def from_dict(obj: Any) -> 'RemoteSessionMetadataValue': session_id = from_str(obj.get("sessionId")) start_time = from_str(obj.get("startTime")) context = from_union([SessionContext.from_dict, from_none], obj.get("context")) + host_activity = from_union([from_str, from_none], obj.get("hostActivity")) + host_status = from_union([RemoteSessionHostStatus, from_none], obj.get("hostStatus")) name = from_union([from_str, from_none], obj.get("name")) pull_request_number = from_union([from_int, from_none], obj.get("pullRequestNumber")) resource_id = from_union([from_str, from_none], obj.get("resourceId")) @@ -22254,7 +27047,7 @@ def from_dict(obj: Any) -> 'RemoteSessionMetadataValue': state = from_union([from_str, from_none], obj.get("state")) summary = from_union([from_str, from_none], obj.get("summary")) task_type = from_union([TaskType, from_none], obj.get("taskType")) - return RemoteSessionMetadataValue(is_remote, modified_time, remote_session_ids, repository, session_id, start_time, context, name, pull_request_number, resource_id, stale_at, state, summary, task_type) + return RemoteSessionMetadataValue(is_remote, modified_time, remote_session_ids, repository, session_id, start_time, context, host_activity, host_status, name, pull_request_number, resource_id, stale_at, state, summary, task_type) def to_dict(self) -> dict: result: dict = {} @@ -22266,6 +27059,10 @@ def to_dict(self) -> dict: result["startTime"] = from_str(self.start_time) if self.context is not None: result["context"] = from_union([lambda x: to_class(SessionContext, x), from_none], self.context) + if self.host_activity is not None: + result["hostActivity"] = from_union([from_str, from_none], self.host_activity) + if self.host_status is not None: + result["hostStatus"] = from_union([lambda x: to_enum(RemoteSessionHostStatus, x), from_none], self.host_status) if self.name is not None: result["name"] = from_union([from_str, from_none], self.name) if self.pull_request_number is not None: @@ -22334,9 +27131,11 @@ class PermissionPathsConfig: """ additional_directories: list[str] | None = None """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). + directory). Conventional `.github/skills/` and `.github/agents/` definitions under them + also join the session catalogs when their subsystem gates are enabled, so supplying a + directory is a trust decision for configuration stored there. When `unrestricted` is + true, these are still pre-populated on the UnrestrictedPathManager so they remain visible + via getDirectories() (e.g. for @-mention completion). """ include_temp_directory: bool | None = None """Whether to include the system temp directory in the allowed list (defaults to true). @@ -22566,129 +27365,6 @@ def to_dict(self) -> dict: result["context"] = to_class(MCPAppsSetHostContextDetails, self.context) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class MCPConfigAddRequest: - """MCP server name and configuration to add to user configuration.""" - - config: MCPServerConfig - """MCP server configuration (stdio process or remote HTTP/SSE)""" - - name: str - """Unique name for the MCP server""" - - @staticmethod - def from_dict(obj: Any) -> 'MCPConfigAddRequest': - assert isinstance(obj, dict) - config = MCPServerConfig.from_dict(obj.get("config")) - name = from_str(obj.get("name")) - return MCPConfigAddRequest(config, name) - - def to_dict(self) -> dict: - result: dict = {} - result["config"] = to_class(MCPServerConfig, self.config) - result["name"] = from_str(self.name) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class MCPConfigList: - """User-configured MCP servers, keyed by server name.""" - - servers: dict[str, MCPServerConfig] - """All MCP servers from user config, keyed by name""" - - @staticmethod - def from_dict(obj: Any) -> 'MCPConfigList': - assert isinstance(obj, dict) - servers = from_dict(MCPServerConfig.from_dict, obj.get("servers")) - return MCPConfigList(servers) - - def to_dict(self) -> dict: - result: dict = {} - result["servers"] = from_dict(lambda x: to_class(MCPServerConfig, x), self.servers) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class MCPConfigUpdateRequest: - """MCP server name and replacement configuration to write to user configuration.""" - - config: MCPServerConfig - """MCP server configuration (stdio process or remote HTTP/SSE)""" - - name: str - """Name of the MCP server to update""" - - @staticmethod - def from_dict(obj: Any) -> 'MCPConfigUpdateRequest': - assert isinstance(obj, dict) - config = MCPServerConfig.from_dict(obj.get("config")) - name = from_str(obj.get("name")) - return MCPConfigUpdateRequest(config, name) - - def to_dict(self) -> dict: - result: dict = {} - result["config"] = to_class(MCPServerConfig, self.config) - result["name"] = from_str(self.name) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class MCPRestartServerRequest: - """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. - """ - server_name: str - """Name of the MCP server to restart""" - - config: MCPServerConfig | None = None - """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). - """ - - @staticmethod - def from_dict(obj: Any) -> 'MCPRestartServerRequest': - assert isinstance(obj, dict) - server_name = from_str(obj.get("serverName")) - config = from_union([MCPServerConfig.from_dict, from_none], obj.get("config")) - return MCPRestartServerRequest(server_name, config) - - def to_dict(self) -> dict: - result: dict = {} - 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. -@dataclass -class MCPStartServerRequest: - """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) - server_name = from_str(obj.get("serverName")) - config = from_union([MCPServerConfig.from_dict, from_none], obj.get("config")) - return MCPStartServerRequest(server_name, config) - - def to_dict(self) -> dict: - result: dict = {} - 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. @dataclass class MCPHeadersHandlePendingHeadersRefreshRequestRequest: @@ -22713,30 +27389,6 @@ def to_dict(self) -> dict: result["result"] = to_class(MCPHeadersHandlePendingHeadersRefreshRequest, self.result) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class MCPOauthHandlePendingRequest: - """Pending MCP OAuth request ID and host-provided token or cancellation response.""" - - request_id: str - """OAuth request identifier from the mcp.oauth_required event""" - - result: MCPOauthPendingRequestResponse - """Host response to the pending OAuth request.""" - - @staticmethod - def from_dict(obj: Any) -> 'MCPOauthHandlePendingRequest': - assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - result = MCPOauthPendingRequestResponse.from_dict(obj.get("result")) - return MCPOauthHandlePendingRequest(request_id, result) - - def to_dict(self) -> dict: - result: dict = {} - result["requestId"] = from_str(self.request_id) - result["result"] = to_class(MCPOauthPendingRequestResponse, self.result) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class DebugCollectLogsEntry: @@ -22952,6 +27604,8 @@ class ModelCapabilitiesOverride: Override individual model capabilities resolved by the runtime + Overrides for model capabilities when they cannot be inferred from modelId. + Initial model capability overrides. Per-property model capability overrides for the selected model. @@ -22977,33 +27631,6 @@ def to_dict(self) -> dict: result["supports"] = from_union([lambda x: to_class(ModelCapabilitiesOverrideSupports, x), from_none], self.supports) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class ProviderTokenAcquireRequest: - """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. - """ - provider_name: str - """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`. - """ - session_id: str - """Target session identifier""" - - @staticmethod - def from_dict(obj: Any) -> 'ProviderTokenAcquireRequest': - assert isinstance(obj, dict) - provider_name = from_str(obj.get("providerName")) - session_id = from_str(obj.get("sessionId")) - return ProviderTokenAcquireRequest(provider_name, session_id) - - def to_dict(self) -> dict: - result: dict = {} - result["providerName"] = from_str(self.provider_name) - 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 OptionsUpdateAdditionalContentExclusionPolicy: @@ -23011,7 +27638,11 @@ class OptionsUpdateAdditionalContentExclusionPolicy: data, and scope. """ last_updated_at: Any + """Opaque policy update timestamp supplied by the host.""" + rules: list[OptionsUpdateAdditionalContentExclusionPolicyRule] + """Content-exclusion rules to apply.""" + scope: AdditionalContentExclusionPolicyScope """Allowed values for the `OptionsUpdateAdditionalContentExclusionPolicyScope` enumeration.""" @@ -23030,6 +27661,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: @@ -23037,7 +27700,11 @@ class PermissionsConfigureAdditionalContentExclusionPolicy: last-updated data, and scope. """ last_updated_at: Any + """Opaque policy update timestamp supplied by the host.""" + rules: list[PermissionsConfigureAdditionalContentExclusionPolicyRule] + """Content-exclusion rules to apply.""" + scope: AdditionalContentExclusionPolicyScope """Allowed values for the `PermissionsConfigureAdditionalContentExclusionPolicyScope` enumeration. @@ -23308,6 +27975,8 @@ class QueueInsertAtRequest: """Parameters for inserting a queued message at a public visible position.""" message: QueueInsertMessage + """Queued message contents and delivery metadata.""" + position: int """Zero-based position in the public visible queue. Values outside the queue clamp to an end.""" @@ -23337,18 +28006,26 @@ class QueuePendingItemsResult: """Display text for messages currently in the immediate steering queue (interjections sent during a running turn). """ + in_flight_steering_count: int | None = None + """How many leading entries of `steeringMessages` have already been folded into the running + turn (and so have an emitted `user.message`), as opposed to still waiting for one. Absent + for hosts that do not distinguish the two. + """ @staticmethod def from_dict(obj: Any) -> 'QueuePendingItemsResult': assert isinstance(obj, dict) items = from_list(QueuePendingItems.from_dict, obj.get("items")) steering_messages = from_list(from_str, obj.get("steeringMessages")) - return QueuePendingItemsResult(items, steering_messages) + in_flight_steering_count = from_union([from_int, from_none], obj.get("inFlightSteeringCount")) + return QueuePendingItemsResult(items, steering_messages, in_flight_steering_count) 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.in_flight_steering_count is not None: + result["inFlightSteeringCount"] = from_union([from_int, from_none], self.in_flight_steering_count) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -23596,6 +28273,7 @@ class SessionFSSqliteTransactionRequest: """Target session identifier""" statements: list[SessionFSSqliteTransactionStatement] + """Ordered SQL statements to execute in one transaction.""" @staticmethod def from_dict(obj: Any) -> 'SessionFSSqliteTransactionRequest': @@ -23617,7 +28295,11 @@ class SessionOpenOptionsAdditionalContentExclusionPolicy: data, and scope. """ last_updated_at: Any + """Opaque policy update timestamp supplied by the host.""" + rules: list[SessionOpenOptionsAdditionalContentExclusionPolicyRule] + """Content-exclusion rules to apply.""" + scope: AdditionalContentExclusionPolicyScope """Allowed values for the `SessionOpenOptionsAdditionalContentExclusionPolicyScope` enumeration. @@ -23643,6 +28325,9 @@ def to_dict(self) -> dict: class ShellOptions: """Per-session settings for built-in shell tools.""" + credentials: ShellCredentials | None = None + """Command-scoped GitHub credential injection for shell commands.""" + init_profile: ShellInitProfile | None = None """Controls automatic non-interactive profile loading where supported. Explicit initScripts are unaffected. @@ -23674,66 +28359,22 @@ class ShellOptions: @staticmethod def from_dict(obj: Any) -> 'ShellOptions': assert isinstance(obj, dict) + credentials = from_union([ShellCredentials.from_dict, from_none], obj.get("credentials")) 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: - """Redacted, serializable view of session runtime settings for SDK boundary consumers. - Secrets and raw feature flags are intentionally excluded. - """ - job: SessionSettingsJobSnapshot - model: SessionSettingsModelSnapshot - online_evaluation: SessionSettingsOnlineEvaluationSnapshot - repo: SessionSettingsRepoSnapshot - validation: SessionSettingsValidationSnapshot - client_name: str | None = None - start_time_ms: float | None = None - timeout_ms: float | None = None - version: str | None = None - - @staticmethod - def from_dict(obj: Any) -> 'SessionSettingsSnapshot': - assert isinstance(obj, dict) - job = SessionSettingsJobSnapshot.from_dict(obj.get("job")) - model = SessionSettingsModelSnapshot.from_dict(obj.get("model")) - online_evaluation = SessionSettingsOnlineEvaluationSnapshot.from_dict(obj.get("onlineEvaluation")) - repo = SessionSettingsRepoSnapshot.from_dict(obj.get("repo")) - validation = SessionSettingsValidationSnapshot.from_dict(obj.get("validation")) - client_name = from_union([from_str, from_none], obj.get("clientName")) - start_time_ms = from_union([from_float, from_none], obj.get("startTimeMs")) - timeout_ms = from_union([from_float, from_none], obj.get("timeoutMs")) - version = from_union([from_str, from_none], obj.get("version")) - return SessionSettingsSnapshot(job, model, online_evaluation, repo, validation, client_name, start_time_ms, timeout_ms, version) + return ShellOptions(credentials, init_profile, init_scripts, process_flags) def to_dict(self) -> dict: result: dict = {} - result["job"] = to_class(SessionSettingsJobSnapshot, self.job) - result["model"] = to_class(SessionSettingsModelSnapshot, self.model) - result["onlineEvaluation"] = to_class(SessionSettingsOnlineEvaluationSnapshot, self.online_evaluation) - result["repo"] = to_class(SessionSettingsRepoSnapshot, self.repo) - result["validation"] = to_class(SessionSettingsValidationSnapshot, self.validation) - if self.client_name is not None: - result["clientName"] = from_union([from_str, from_none], self.client_name) - if self.start_time_ms is not None: - result["startTimeMs"] = from_union([to_float, from_none], self.start_time_ms) - if self.timeout_ms is not None: - result["timeoutMs"] = from_union([to_float, from_none], self.timeout_ms) - if self.version is not None: - result["version"] = from_union([from_str, from_none], self.version) + if self.credentials is not None: + result["credentials"] = from_union([lambda x: to_class(ShellCredentials, x), from_none], self.credentials) + 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. @@ -23922,6 +28563,607 @@ def to_dict(self) -> dict: result["progress"] = from_union([lambda x: to_class(TaskProgress, x), from_none], self.progress) 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 _FactoryToolRunRequest: + """Internal parameters for invoking a registered factory from a tool.""" + + args: Any + """Factory input value.""" + + name: str + """Registered factory name.""" + + options: _FactoryToolRunOptions | None = None + """Tool-originated factory invocation options.""" + + tool_call_id: str | None = None + """Opaque identifier of the originating tool call.""" + + @staticmethod + def from_dict(obj: Any) -> '_FactoryToolRunRequest': + assert isinstance(obj, dict) + args = obj.get("args") + name = from_str(obj.get("name")) + options = from_union([_FactoryToolRunOptions.from_dict, from_none], obj.get("options")) + tool_call_id = from_union([from_str, from_none], obj.get("toolCallId")) + return _FactoryToolRunRequest(args, name, options, tool_call_id) + + def to_dict(self) -> dict: + result: dict = {} + result["args"] = self.args + result["name"] = from_str(self.name) + if self.options is not None: + result["options"] = from_union([lambda x: to_class(_FactoryToolRunOptions, x), from_none], self.options) + if self.tool_call_id is not None: + result["toolCallId"] = from_union([from_str, from_none], self.tool_call_id) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MCPSerializableServerConfig: + """MCP server configuration (stdio process or remote HTTP/SSE) + + Serializable MCP server configuration (stdio process or remote HTTP/SSE) + + 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. + """ + args: list[str] | None = None + """Command-line arguments passed to the Stdio MCP server process.""" + + auth: bool | MCPServerAuthConfigRedirectPort | None = None + """Set to `true` to use defaults, or provide an object with additional auth or OIDC settings.""" + + command: str | None = None + """Executable command used to start the Stdio MCP server process.""" + + config_warnings: list[str] | None = None + """Configuration warnings recorded while loading the server.""" + + cwd: str | None = None + """Working directory for the Stdio MCP server process.""" + + defer_tools: MCPServerConfigDeferTools | None = None + """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_secret_masking: bool | None = None + """Whether secret masking is disabled for calls to this server.""" + + disable_tool_cache: bool | None = None + """Set to true to disable persisted MCP tool snapshots for this server. Live tool discovery + is unaffected. + """ + display_name: str | None = None + """Optional human-readable server name.""" + + env: dict[str, str] | None = None + """Environment variables to pass to the Stdio MCP server process.""" + + events: list[str] | None = None + """Event types this server receives as Copilot notifications.""" + + exclude_tools: list[str] | None = None + """Tool names excluded after the include filter is applied.""" + + 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. + """ + is_default_server: bool | None = None + """Whether this server is a built-in fallback used when the user has not configured their + own server. + """ + notifications: list[str] | None = None + """Copilot notification types this server may send to the host.""" + + oidc: bool | MCPServerAuthConfigRedirectPort | None = None + """Set to `true` to use defaults, or provide an object with additional auth or OIDC settings.""" + + safe_for_telemetry: bool | MCPSafeForTelemetryFields | None = None + """Telemetry-obfuscation policy for this server's tools.""" + + source: McpServerSource | None = None + """The origin of this server configuration.""" + + source_path: str | None = None + """Source file path recorded while loading the config.""" + + source_plugin: str | None = None + """Plugin that provided this server.""" + + source_plugin_spec: bool | None = None + """Whether the providing plugin uses the Open Plugin Spec.""" + + source_plugin_version: str | None = None + """Version of the plugin that provided this server.""" + + timeout: int | None = None + """Timeout in milliseconds for tool discovery and tool calls.""" + + tools: list[str] | None = None + """Tools to include. Defaults to all tools if not specified.""" + + type: MCPSerializableServerConfigType | None = None + """Local transport type. Defaults to stdio when omitted. + + Remote transport type. Defaults to "http" when omitted. + """ + headers: dict[str, str] | None = None + """HTTP headers to include in requests to the remote MCP server.""" + + headers_refresh_ttl_ms: int | None = None + """Dynamic-header refresh cache lifetime in milliseconds.""" + + oauth_client_id: str | None = None + """OAuth client ID for a pre-registered remote MCP OAuth client.""" + + oauth_grant_type: MCPGrantType | None = None + """OAuth grant type to use when authenticating to the remote MCP server.""" + + oauth_public_client: bool | None = None + """Whether the configured OAuth client is public and does not require a client secret.""" + + url: str | None = None + """URL of the remote MCP server endpoint.""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPSerializableServerConfig': + assert isinstance(obj, dict) + args = from_union([lambda x: from_list(from_str, x), from_none], obj.get("args")) + auth = from_union([from_bool, MCPServerAuthConfigRedirectPort.from_dict, from_none], obj.get("auth")) + command = from_union([from_str, from_none], obj.get("command")) + config_warnings = from_union([lambda x: from_list(from_str, x), from_none], obj.get("configWarnings")) + cwd = from_union([from_str, from_none], obj.get("cwd")) + defer_tools = from_union([MCPServerConfigDeferTools, from_none], obj.get("deferTools")) + disable_secret_masking = from_union([from_bool, from_none], obj.get("disableSecretMasking")) + disable_tool_cache = from_union([from_bool, from_none], obj.get("disableToolCache")) + display_name = from_union([from_str, from_none], obj.get("displayName")) + env = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("env")) + events = from_union([lambda x: from_list(from_str, x), from_none], obj.get("events")) + exclude_tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("excludeTools")) + 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")) + notifications = from_union([lambda x: from_list(from_str, x), from_none], obj.get("notifications")) + oidc = from_union([from_bool, MCPServerAuthConfigRedirectPort.from_dict, from_none], obj.get("oidc")) + safe_for_telemetry = from_union([from_bool, MCPSafeForTelemetryFields.from_dict, from_none], obj.get("safeForTelemetry")) + source = from_union([McpServerSource, from_none], obj.get("source")) + source_path = from_union([from_str, from_none], obj.get("sourcePath")) + source_plugin = from_union([from_str, from_none], obj.get("sourcePlugin")) + source_plugin_spec = from_union([from_bool, from_none], obj.get("sourcePluginSpec")) + source_plugin_version = from_union([from_str, from_none], obj.get("sourcePluginVersion")) + 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([MCPSerializableServerConfigType, from_none], obj.get("type")) + headers = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("headers")) + headers_refresh_ttl_ms = from_union([from_int, from_none], obj.get("headersRefreshTtlMs")) + oauth_client_id = from_union([from_str, from_none], obj.get("oauthClientId")) + oauth_grant_type = from_union([MCPGrantType, from_none], obj.get("oauthGrantType")) + oauth_public_client = from_union([from_bool, from_none], obj.get("oauthPublicClient")) + url = from_union([from_str, from_none], obj.get("url")) + return MCPSerializableServerConfig(args, auth, command, config_warnings, cwd, defer_tools, disable_secret_masking, disable_tool_cache, display_name, env, events, exclude_tools, filter_mapping, is_default_server, notifications, oidc, safe_for_telemetry, source, source_path, source_plugin, source_plugin_spec, source_plugin_version, timeout, tools, type, headers, headers_refresh_ttl_ms, oauth_client_id, oauth_grant_type, oauth_public_client, url) + + def to_dict(self) -> dict: + result: dict = {} + if self.args is not None: + result["args"] = from_union([lambda x: from_list(from_str, x), from_none], self.args) + if self.auth is not None: + result["auth"] = from_union([from_bool, lambda x: to_class(MCPServerAuthConfigRedirectPort, x), from_none], self.auth) + if self.command is not None: + result["command"] = from_union([from_str, from_none], self.command) + if self.config_warnings is not None: + result["configWarnings"] = from_union([lambda x: from_list(from_str, x), from_none], self.config_warnings) + if self.cwd is not None: + 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_secret_masking is not None: + result["disableSecretMasking"] = from_union([from_bool, from_none], self.disable_secret_masking) + if self.disable_tool_cache is not None: + result["disableToolCache"] = from_union([from_bool, from_none], self.disable_tool_cache) + if self.display_name is not None: + result["displayName"] = from_union([from_str, from_none], self.display_name) + if self.env is not None: + result["env"] = from_union([lambda x: from_dict(from_str, x), from_none], self.env) + if self.events is not None: + result["events"] = from_union([lambda x: from_list(from_str, x), from_none], self.events) + if self.exclude_tools is not None: + result["excludeTools"] = from_union([lambda x: from_list(from_str, x), from_none], self.exclude_tools) + 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.is_default_server is not None: + result["isDefaultServer"] = from_union([from_bool, from_none], self.is_default_server) + if self.notifications is not None: + result["notifications"] = from_union([lambda x: from_list(from_str, x), from_none], self.notifications) + if self.oidc is not None: + result["oidc"] = from_union([from_bool, lambda x: to_class(MCPServerAuthConfigRedirectPort, x), from_none], self.oidc) + if self.safe_for_telemetry is not None: + result["safeForTelemetry"] = from_union([from_bool, lambda x: to_class(MCPSafeForTelemetryFields, x), from_none], self.safe_for_telemetry) + if self.source is not None: + result["source"] = from_union([lambda x: to_enum(McpServerSource, x), from_none], self.source) + if self.source_path is not None: + result["sourcePath"] = from_union([from_str, from_none], self.source_path) + if self.source_plugin is not None: + result["sourcePlugin"] = from_union([from_str, from_none], self.source_plugin) + if self.source_plugin_spec is not None: + result["sourcePluginSpec"] = from_union([from_bool, from_none], self.source_plugin_spec) + if self.source_plugin_version is not None: + result["sourcePluginVersion"] = from_union([from_str, from_none], self.source_plugin_version) + if self.timeout is not None: + result["timeout"] = from_union([from_int, from_none], self.timeout) + if self.tools is not None: + result["tools"] = from_union([lambda x: from_list(from_str, x), from_none], self.tools) + if self.type is not None: + result["type"] = from_union([lambda x: to_enum(MCPSerializableServerConfigType, x), from_none], self.type) + if self.headers is not None: + result["headers"] = from_union([lambda x: from_dict(from_str, x), from_none], self.headers) + if self.headers_refresh_ttl_ms is not None: + result["headersRefreshTtlMs"] = from_union([from_int, from_none], self.headers_refresh_ttl_ms) + if self.oauth_client_id is not None: + result["oauthClientId"] = from_union([from_str, from_none], self.oauth_client_id) + if self.oauth_grant_type is not None: + result["oauthGrantType"] = from_union([lambda x: to_enum(MCPGrantType, x), from_none], self.oauth_grant_type) + if self.oauth_public_client is not None: + result["oauthPublicClient"] = from_union([from_bool, from_none], self.oauth_public_client) + if self.url is not None: + result["url"] = from_union([from_str, from_none], self.url) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MCPServerConfigHTTP: + """Remote MCP server configuration accessed over HTTP or SSE.""" + + url: str + """URL of the remote MCP server endpoint.""" + + auth: bool | MCPServerAuthConfigRedirectPort | None = None + """Set to `true` to use defaults, or provide an object with additional auth or OIDC settings.""" + + config_warnings: list[str] | None = None + """Configuration warnings recorded while loading the server.""" + + defer_tools: MCPServerConfigDeferTools | None = None + """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_secret_masking: bool | None = None + """Whether secret masking is disabled for calls to this server.""" + + disable_tool_cache: bool | None = None + """Set to true to disable persisted MCP tool snapshots for this server. Live tool discovery + is unaffected. + """ + display_name: str | None = None + """Optional human-readable server name.""" + + events: list[str] | None = None + """Event types this server receives as Copilot notifications.""" + + exclude_tools: list[str] | None = None + """Tool names excluded after the include filter is applied.""" + + 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. + """ + headers: dict[str, str] | None = None + """HTTP headers to include in requests to the remote MCP server.""" + + headers_refresh_ttl_ms: int | None = None + """Dynamic-header refresh cache lifetime in milliseconds.""" + + is_default_server: bool | None = None + """Whether this server is a built-in fallback used when the user has not configured their + own server. + """ + notifications: list[str] | None = None + """Copilot notification types this server may send to the host.""" + + oauth_client_id: str | None = None + """OAuth client ID for a pre-registered remote MCP OAuth client.""" + + oauth_grant_type: MCPGrantType | None = None + """OAuth grant type to use when authenticating to the remote MCP server.""" + + oauth_public_client: bool | None = None + """Whether the configured OAuth client is public and does not require a client secret.""" + + oidc: bool | MCPServerAuthConfigRedirectPort | None = None + """Set to `true` to use defaults, or provide an object with additional auth or OIDC settings.""" + + safe_for_telemetry: bool | MCPSafeForTelemetryFields | None = None + """Telemetry-obfuscation policy for this server's tools.""" + + source: McpServerSource | None = None + """The origin of this server configuration.""" + + source_path: str | None = None + """Source file path recorded while loading the config.""" + + source_plugin: str | None = None + """Plugin that provided this server.""" + + source_plugin_spec: bool | None = None + """Whether the providing plugin uses the Open Plugin Spec.""" + + source_plugin_version: str | None = None + """Version of the plugin that provided this server.""" + + timeout: int | None = None + """Timeout in milliseconds for tool discovery and tool calls.""" + + tools: list[str] | None = None + """Tools to include. Defaults to all tools if not specified.""" + + type: MCPServerConfigHTTPType | None = None + """Remote transport type. Defaults to "http" when omitted.""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPServerConfigHTTP': + assert isinstance(obj, dict) + url = from_str(obj.get("url")) + auth = from_union([from_bool, MCPServerAuthConfigRedirectPort.from_dict, from_none], obj.get("auth")) + config_warnings = from_union([lambda x: from_list(from_str, x), from_none], obj.get("configWarnings")) + defer_tools = from_union([MCPServerConfigDeferTools, from_none], obj.get("deferTools")) + disable_secret_masking = from_union([from_bool, from_none], obj.get("disableSecretMasking")) + disable_tool_cache = from_union([from_bool, from_none], obj.get("disableToolCache")) + display_name = from_union([from_str, from_none], obj.get("displayName")) + events = from_union([lambda x: from_list(from_str, x), from_none], obj.get("events")) + exclude_tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("excludeTools")) + 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")) + headers_refresh_ttl_ms = from_union([from_int, from_none], obj.get("headersRefreshTtlMs")) + is_default_server = from_union([from_bool, from_none], obj.get("isDefaultServer")) + notifications = from_union([lambda x: from_list(from_str, x), from_none], obj.get("notifications")) + oauth_client_id = from_union([from_str, from_none], obj.get("oauthClientId")) + oauth_grant_type = from_union([MCPGrantType, from_none], obj.get("oauthGrantType")) + oauth_public_client = from_union([from_bool, from_none], obj.get("oauthPublicClient")) + oidc = from_union([from_bool, MCPServerAuthConfigRedirectPort.from_dict, from_none], obj.get("oidc")) + safe_for_telemetry = from_union([from_bool, MCPSafeForTelemetryFields.from_dict, from_none], obj.get("safeForTelemetry")) + source = from_union([McpServerSource, from_none], obj.get("source")) + source_path = from_union([from_str, from_none], obj.get("sourcePath")) + source_plugin = from_union([from_str, from_none], obj.get("sourcePlugin")) + source_plugin_spec = from_union([from_bool, from_none], obj.get("sourcePluginSpec")) + source_plugin_version = from_union([from_str, from_none], obj.get("sourcePluginVersion")) + 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, config_warnings, defer_tools, disable_secret_masking, disable_tool_cache, display_name, events, exclude_tools, filter_mapping, headers, headers_refresh_ttl_ms, is_default_server, notifications, oauth_client_id, oauth_grant_type, oauth_public_client, oidc, safe_for_telemetry, source, source_path, source_plugin, source_plugin_spec, source_plugin_version, timeout, tools, type) + + def to_dict(self) -> dict: + result: dict = {} + result["url"] = from_str(self.url) + if self.auth is not None: + result["auth"] = from_union([from_bool, lambda x: to_class(MCPServerAuthConfigRedirectPort, x), from_none], self.auth) + if self.config_warnings is not None: + result["configWarnings"] = from_union([lambda x: from_list(from_str, x), from_none], self.config_warnings) + 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_secret_masking is not None: + result["disableSecretMasking"] = from_union([from_bool, from_none], self.disable_secret_masking) + if self.disable_tool_cache is not None: + result["disableToolCache"] = from_union([from_bool, from_none], self.disable_tool_cache) + if self.display_name is not None: + result["displayName"] = from_union([from_str, from_none], self.display_name) + if self.events is not None: + result["events"] = from_union([lambda x: from_list(from_str, x), from_none], self.events) + if self.exclude_tools is not None: + result["excludeTools"] = from_union([lambda x: from_list(from_str, x), from_none], self.exclude_tools) + 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: + result["headers"] = from_union([lambda x: from_dict(from_str, x), from_none], self.headers) + if self.headers_refresh_ttl_ms is not None: + result["headersRefreshTtlMs"] = from_union([from_int, from_none], self.headers_refresh_ttl_ms) + if self.is_default_server is not None: + result["isDefaultServer"] = from_union([from_bool, from_none], self.is_default_server) + if self.notifications is not None: + result["notifications"] = from_union([lambda x: from_list(from_str, x), from_none], self.notifications) + if self.oauth_client_id is not None: + result["oauthClientId"] = from_union([from_str, from_none], self.oauth_client_id) + if self.oauth_grant_type is not None: + result["oauthGrantType"] = from_union([lambda x: to_enum(MCPGrantType, x), from_none], self.oauth_grant_type) + if self.oauth_public_client is not None: + result["oauthPublicClient"] = from_union([from_bool, from_none], self.oauth_public_client) + if self.oidc is not None: + result["oidc"] = from_union([from_bool, lambda x: to_class(MCPServerAuthConfigRedirectPort, x), from_none], self.oidc) + if self.safe_for_telemetry is not None: + result["safeForTelemetry"] = from_union([from_bool, lambda x: to_class(MCPSafeForTelemetryFields, x), from_none], self.safe_for_telemetry) + if self.source is not None: + result["source"] = from_union([lambda x: to_enum(McpServerSource, x), from_none], self.source) + if self.source_path is not None: + result["sourcePath"] = from_union([from_str, from_none], self.source_path) + if self.source_plugin is not None: + result["sourcePlugin"] = from_union([from_str, from_none], self.source_plugin) + if self.source_plugin_spec is not None: + result["sourcePluginSpec"] = from_union([from_bool, from_none], self.source_plugin_spec) + if self.source_plugin_version is not None: + result["sourcePluginVersion"] = from_union([from_str, from_none], self.source_plugin_version) + if self.timeout is not None: + result["timeout"] = from_union([from_int, from_none], self.timeout) + if self.tools is not None: + result["tools"] = from_union([lambda x: from_list(from_str, x), from_none], self.tools) + if self.type is not None: + result["type"] = from_union([lambda x: to_enum(MCPServerConfigHTTPType, x), from_none], self.type) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MCPServerConfigStdio: + """Stdio MCP server configuration launched as a child process.""" + + command: str + """Executable command used to start the Stdio MCP server process.""" + + args: list[str] | None = None + """Command-line arguments passed to the Stdio MCP server process.""" + + auth: bool | MCPServerAuthConfigRedirectPort | None = None + """Set to `true` to use defaults, or provide an object with additional auth or OIDC settings.""" + + config_warnings: list[str] | None = None + """Configuration warnings recorded while loading the server.""" + + cwd: str | None = None + """Working directory for the Stdio MCP server process.""" + + defer_tools: MCPServerConfigDeferTools | None = None + """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_secret_masking: bool | None = None + """Whether secret masking is disabled for calls to this server.""" + + disable_tool_cache: bool | None = None + """Set to true to disable persisted MCP tool snapshots for this server. Live tool discovery + is unaffected. + """ + display_name: str | None = None + """Optional human-readable server name.""" + + env: dict[str, str] | None = None + """Environment variables to pass to the Stdio MCP server process.""" + + events: list[str] | None = None + """Event types this server receives as Copilot notifications.""" + + exclude_tools: list[str] | None = None + """Tool names excluded after the include filter is applied.""" + + 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. + """ + is_default_server: bool | None = None + """Whether this server is a built-in fallback used when the user has not configured their + own server. + """ + notifications: list[str] | None = None + """Copilot notification types this server may send to the host.""" + + oidc: bool | MCPServerAuthConfigRedirectPort | None = None + """Set to `true` to use defaults, or provide an object with additional auth or OIDC settings.""" + + safe_for_telemetry: bool | MCPSafeForTelemetryFields | None = None + """Telemetry-obfuscation policy for this server's tools.""" + + source: McpServerSource | None = None + """The origin of this server configuration.""" + + source_path: str | None = None + """Source file path recorded while loading the config.""" + + source_plugin: str | None = None + """Plugin that provided this server.""" + + source_plugin_spec: bool | None = None + """Whether the providing plugin uses the Open Plugin Spec.""" + + source_plugin_version: str | None = None + """Version of the plugin that provided this server.""" + + timeout: int | None = None + """Timeout in milliseconds for tool discovery and tool calls.""" + + tools: list[str] | None = None + """Tools to include. Defaults to all tools if not specified.""" + + type: MCPServerConfigStdioType | None = None + """Local transport type. Defaults to stdio when omitted.""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPServerConfigStdio': + assert isinstance(obj, dict) + command = from_str(obj.get("command")) + args = from_union([lambda x: from_list(from_str, x), from_none], obj.get("args")) + auth = from_union([from_bool, MCPServerAuthConfigRedirectPort.from_dict, from_none], obj.get("auth")) + config_warnings = from_union([lambda x: from_list(from_str, x), from_none], obj.get("configWarnings")) + cwd = from_union([from_str, from_none], obj.get("cwd")) + defer_tools = from_union([MCPServerConfigDeferTools, from_none], obj.get("deferTools")) + disable_secret_masking = from_union([from_bool, from_none], obj.get("disableSecretMasking")) + disable_tool_cache = from_union([from_bool, from_none], obj.get("disableToolCache")) + display_name = from_union([from_str, from_none], obj.get("displayName")) + env = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("env")) + events = from_union([lambda x: from_list(from_str, x), from_none], obj.get("events")) + exclude_tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("excludeTools")) + 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")) + notifications = from_union([lambda x: from_list(from_str, x), from_none], obj.get("notifications")) + oidc = from_union([from_bool, MCPServerAuthConfigRedirectPort.from_dict, from_none], obj.get("oidc")) + safe_for_telemetry = from_union([from_bool, MCPSafeForTelemetryFields.from_dict, from_none], obj.get("safeForTelemetry")) + source = from_union([McpServerSource, from_none], obj.get("source")) + source_path = from_union([from_str, from_none], obj.get("sourcePath")) + source_plugin = from_union([from_str, from_none], obj.get("sourcePlugin")) + source_plugin_spec = from_union([from_bool, from_none], obj.get("sourcePluginSpec")) + source_plugin_version = from_union([from_str, from_none], obj.get("sourcePluginVersion")) + 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([MCPServerConfigStdioType, from_none], obj.get("type")) + return MCPServerConfigStdio(command, args, auth, config_warnings, cwd, defer_tools, disable_secret_masking, disable_tool_cache, display_name, env, events, exclude_tools, filter_mapping, is_default_server, notifications, oidc, safe_for_telemetry, source, source_path, source_plugin, source_plugin_spec, source_plugin_version, timeout, tools, type) + + def to_dict(self) -> dict: + result: dict = {} + result["command"] = from_str(self.command) + if self.args is not None: + result["args"] = from_union([lambda x: from_list(from_str, x), from_none], self.args) + if self.auth is not None: + result["auth"] = from_union([from_bool, lambda x: to_class(MCPServerAuthConfigRedirectPort, x), from_none], self.auth) + if self.config_warnings is not None: + result["configWarnings"] = from_union([lambda x: from_list(from_str, x), from_none], self.config_warnings) + if self.cwd is not None: + 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_secret_masking is not None: + result["disableSecretMasking"] = from_union([from_bool, from_none], self.disable_secret_masking) + if self.disable_tool_cache is not None: + result["disableToolCache"] = from_union([from_bool, from_none], self.disable_tool_cache) + if self.display_name is not None: + result["displayName"] = from_union([from_str, from_none], self.display_name) + if self.env is not None: + result["env"] = from_union([lambda x: from_dict(from_str, x), from_none], self.env) + if self.events is not None: + result["events"] = from_union([lambda x: from_list(from_str, x), from_none], self.events) + if self.exclude_tools is not None: + result["excludeTools"] = from_union([lambda x: from_list(from_str, x), from_none], self.exclude_tools) + 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.is_default_server is not None: + result["isDefaultServer"] = from_union([from_bool, from_none], self.is_default_server) + if self.notifications is not None: + result["notifications"] = from_union([lambda x: from_list(from_str, x), from_none], self.notifications) + if self.oidc is not None: + result["oidc"] = from_union([from_bool, lambda x: to_class(MCPServerAuthConfigRedirectPort, x), from_none], self.oidc) + if self.safe_for_telemetry is not None: + result["safeForTelemetry"] = from_union([from_bool, lambda x: to_class(MCPSafeForTelemetryFields, x), from_none], self.safe_for_telemetry) + if self.source is not None: + result["source"] = from_union([lambda x: to_enum(McpServerSource, x), from_none], self.source) + if self.source_path is not None: + result["sourcePath"] = from_union([from_str, from_none], self.source_path) + if self.source_plugin is not None: + result["sourcePlugin"] = from_union([from_str, from_none], self.source_plugin) + if self.source_plugin_spec is not None: + result["sourcePluginSpec"] = from_union([from_bool, from_none], self.source_plugin_spec) + if self.source_plugin_version is not None: + result["sourcePluginVersion"] = from_union([from_str, from_none], self.source_plugin_version) + if self.timeout is not None: + result["timeout"] = from_union([from_int, from_none], self.timeout) + if self.tools is not None: + result["tools"] = from_union([lambda x: from_list(from_str, x), from_none], self.tools) + if self.type is not None: + result["type"] = from_union([lambda x: to_enum(MCPServerConfigStdioType, x), from_none], self.type) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MCPTools: @@ -23956,6 +29198,62 @@ def to_dict(self) -> dict: result["ui"] = from_union([lambda x: to_class(MCPToolUI, x), from_none], self.ui) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ToolsGetBuiltinDescriptorsRequest: + """Options controlling how Rust-owned built-in tool descriptors are materialized.""" + + background_task_notifications_enabled: bool | None = None + """Whether background task completion notifications are enabled.""" + + include_author: bool | None = None + """Whether tool descriptors should include authoring metadata.""" + + reduce_user_intervention: bool | None = None + """Whether descriptors should favor fewer user-intervention prompts.""" + + shell_config: ToolsShellDescriptorConfig | None = None + """Shell-specific names and description lines for shell tools.""" + + shell_supports_power_shell7_syntax: bool | None = None + """Whether the configured shell supports PowerShell 7 syntax.""" + + shell_timeout_ms: float | None = None + """Default shell timeout in milliseconds.""" + + skill_embedding_enabled: bool | None = None + """Whether semantic skill lookup is available.""" + + @staticmethod + def from_dict(obj: Any) -> 'ToolsGetBuiltinDescriptorsRequest': + assert isinstance(obj, dict) + background_task_notifications_enabled = from_union([from_bool, from_none], obj.get("backgroundTaskNotificationsEnabled")) + include_author = from_union([from_bool, from_none], obj.get("includeAuthor")) + reduce_user_intervention = from_union([from_bool, from_none], obj.get("reduceUserIntervention")) + shell_config = from_union([ToolsShellDescriptorConfig.from_dict, from_none], obj.get("shellConfig")) + shell_supports_power_shell7_syntax = from_union([from_bool, from_none], obj.get("shellSupportsPowerShell7Syntax")) + shell_timeout_ms = from_union([from_float, from_none], obj.get("shellTimeoutMs")) + skill_embedding_enabled = from_union([from_bool, from_none], obj.get("skillEmbeddingEnabled")) + return ToolsGetBuiltinDescriptorsRequest(background_task_notifications_enabled, include_author, reduce_user_intervention, shell_config, shell_supports_power_shell7_syntax, shell_timeout_ms, skill_embedding_enabled) + + def to_dict(self) -> dict: + result: dict = {} + if self.background_task_notifications_enabled is not None: + result["backgroundTaskNotificationsEnabled"] = from_union([from_bool, from_none], self.background_task_notifications_enabled) + if self.include_author is not None: + result["includeAuthor"] = from_union([from_bool, from_none], self.include_author) + if self.reduce_user_intervention is not None: + result["reduceUserIntervention"] = from_union([from_bool, from_none], self.reduce_user_intervention) + if self.shell_config is not None: + result["shellConfig"] = from_union([lambda x: to_class(ToolsShellDescriptorConfig, x), from_none], self.shell_config) + if self.shell_supports_power_shell7_syntax is not None: + result["shellSupportsPowerShell7Syntax"] = from_union([from_bool, from_none], self.shell_supports_power_shell7_syntax) + if self.shell_timeout_ms is not None: + result["shellTimeoutMs"] = from_union([to_float, from_none], self.shell_timeout_ms) + if self.skill_embedding_enabled is not None: + result["skillEmbeddingEnabled"] = from_union([from_bool, from_none], self.skill_embedding_enabled) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class UIElicitationArrayAnyOfField: @@ -24269,76 +29567,47 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class UsageGetMetricsResult: - """Accumulated session usage metrics, including premium request cost, token counts, model - breakdown, and code-change totals. +class UsageMetricsAgentMetric: + """Usage attributed to one agent instance, including its identity, API duration, AI units, + and per-model breakdown. """ - code_changes: UsageMetricsCodeChanges - """Aggregated code change metrics""" - - last_call_input_tokens: int - """Input tokens from the most recent main-agent API call""" - - last_call_output_tokens: int - """Output tokens from the most recent main-agent API call""" - model_metrics: dict[str, UsageMetricsModelMetric] - """Per-model token and request metrics, keyed by model identifier""" - - session_start_time: datetime - """ISO 8601 timestamp when the session started""" + """Per-model usage for this agent, keyed by model identifier""" total_api_duration_ms: int - """Total time spent in model API calls (milliseconds)""" - - total_premium_request_cost: float - """Total user-initiated premium request cost across all models (may be fractional due to - multipliers) - """ - total_user_requests: int - """Raw count of user-initiated API requests""" - - current_model: str | None = None - """Currently active model identifier""" + """Time spent in model API calls by this agent, in milliseconds""" - token_details: dict[str, UsageMetricsTokenDetail] | None = None - """Session-wide per-token-type accumulated token counts""" + total_nano_aiu: float + """Accumulated nano-AI units cost for this agent""" - total_nano_aiu: float | None = None - """Session-wide accumulated nano-AI units cost""" + agent_display_name: str | None = None + """Human-readable label for this subagent invocation, copied from the originating + `subagent.started` event. For task-tool subagents this is the invocation's task + description rather than the agent's configured display name, so group by `agentName` for + stable per-agent labels. + """ + agent_name: str | None = None + """Configured agent name, when this is a subagent""" @staticmethod - def from_dict(obj: Any) -> 'UsageGetMetricsResult': + def from_dict(obj: Any) -> 'UsageMetricsAgentMetric': assert isinstance(obj, dict) - code_changes = UsageMetricsCodeChanges.from_dict(obj.get("codeChanges")) - last_call_input_tokens = from_int(obj.get("lastCallInputTokens")) - last_call_output_tokens = from_int(obj.get("lastCallOutputTokens")) model_metrics = from_dict(UsageMetricsModelMetric.from_dict, obj.get("modelMetrics")) - session_start_time = from_datetime(obj.get("sessionStartTime")) total_api_duration_ms = from_int(obj.get("totalApiDurationMs")) - total_premium_request_cost = from_float(obj.get("totalPremiumRequestCost")) - total_user_requests = from_int(obj.get("totalUserRequests")) - current_model = from_union([from_str, from_none], obj.get("currentModel")) - token_details = from_union([lambda x: from_dict(UsageMetricsTokenDetail.from_dict, x), from_none], obj.get("tokenDetails")) - total_nano_aiu = from_union([from_float, from_none], obj.get("totalNanoAiu")) - return UsageGetMetricsResult(code_changes, last_call_input_tokens, last_call_output_tokens, model_metrics, session_start_time, total_api_duration_ms, total_premium_request_cost, total_user_requests, current_model, token_details, total_nano_aiu) + total_nano_aiu = from_float(obj.get("totalNanoAiu")) + agent_display_name = from_union([from_str, from_none], obj.get("agentDisplayName")) + agent_name = from_union([from_str, from_none], obj.get("agentName")) + return UsageMetricsAgentMetric(model_metrics, total_api_duration_ms, total_nano_aiu, agent_display_name, agent_name) def to_dict(self) -> dict: result: dict = {} - result["codeChanges"] = to_class(UsageMetricsCodeChanges, self.code_changes) - result["lastCallInputTokens"] = from_int(self.last_call_input_tokens) - result["lastCallOutputTokens"] = from_int(self.last_call_output_tokens) result["modelMetrics"] = from_dict(lambda x: to_class(UsageMetricsModelMetric, x), self.model_metrics) - result["sessionStartTime"] = self.session_start_time.isoformat() result["totalApiDurationMs"] = from_int(self.total_api_duration_ms) - result["totalPremiumRequestCost"] = to_float(self.total_premium_request_cost) - result["totalUserRequests"] = from_int(self.total_user_requests) - if self.current_model is not None: - result["currentModel"] = from_union([from_str, from_none], self.current_model) - if self.token_details is not None: - result["tokenDetails"] = from_union([lambda x: from_dict(lambda x: to_class(UsageMetricsTokenDetail, x), x), from_none], self.token_details) - if self.total_nano_aiu is not None: - result["totalNanoAiu"] = from_union([to_float, from_none], self.total_nano_aiu) + result["totalNanoAiu"] = to_float(self.total_nano_aiu) + if self.agent_display_name is not None: + result["agentDisplayName"] = from_union([from_str, from_none], self.agent_display_name) + if self.agent_name is not None: + result["agentName"] = from_union([from_str, from_none], self.agent_name) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -24395,6 +29664,90 @@ def to_dict(self) -> dict: 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 MCPInstallPlan: + """A normalised, inert description of what installing an MCP server would involve. Carries + no raw card, no install specification, and no secret value. + + The normalised plan. + """ + configuration_changes: list[MCPPlanConfigurationChange] + """The configuration changes installing would make, described rather than serialised, so the + mutable configuration payload stays behind the runtime boundary. + """ + identity: MCPPlanResourceIdentity + """Normalised identity of the server the plan would install.""" + + plan_handle: str + """Opaque, runtime-instance scoped, TTL-bound, single-use handle for this plan. Rejected + when stale, replayed, or presented to a different runtime instance. Never logged. + """ + plan_handle_expires_at: str + """ISO 8601 timestamp after which the plan handle is stale and will be rejected. Abandoning + a plan needs no call: an unused handle simply expires, so cancellation before commit is + side-effect free. + """ + policy: MCPPlanPolicyResult + """Outcome of evaluating the server against registry and enterprise policy.""" + + provenance: MCPPlanProvenance + """Origin and semantic digest of the exact validated JSON MCP card content bound to this + plan. + """ + reload_required: bool + """Whether applying this plan would require an MCP reload to take effect. Planning itself + never reloads. + """ + requires_interactive_configuration: bool + """Whether the plan cannot be applied without further input, because a required value has no + default or a secret must be supplied. + """ + target: MCPPlanTarget + """Configuration scope and key the plan would write to.""" + + transport_choices: list[MCPPlanTransportChoice] + """Every eligible transport, so a host can present an explicit choice. A completed plan + always has at least one; when none is eligible, planning returns + `CatalogUnavailableTransportError` instead. + """ + recommended_transport_choice_id: str | None = None + """Identifier of the choice the runtime would pick by default. Omitted when there is no + eligible transport, or when the runtime expresses no preference. + """ + + @staticmethod + def from_dict(obj: Any) -> 'MCPInstallPlan': + assert isinstance(obj, dict) + configuration_changes = from_list(MCPPlanConfigurationChange.from_dict, obj.get("configurationChanges")) + identity = MCPPlanResourceIdentity.from_dict(obj.get("identity")) + plan_handle = from_str(obj.get("planHandle")) + plan_handle_expires_at = from_str(obj.get("planHandleExpiresAt")) + policy = MCPPlanPolicyResult.from_dict(obj.get("policy")) + provenance = MCPPlanProvenance.from_dict(obj.get("provenance")) + reload_required = from_bool(obj.get("reloadRequired")) + requires_interactive_configuration = from_bool(obj.get("requiresInteractiveConfiguration")) + target = MCPPlanTarget.from_dict(obj.get("target")) + transport_choices = from_list(_load_MCPPlanTransportChoice, obj.get("transportChoices")) + recommended_transport_choice_id = from_union([from_str, from_none], obj.get("recommendedTransportChoiceId")) + return MCPInstallPlan(configuration_changes, identity, plan_handle, plan_handle_expires_at, policy, provenance, reload_required, requires_interactive_configuration, target, transport_choices, recommended_transport_choice_id) + + def to_dict(self) -> dict: + result: dict = {} + result["configurationChanges"] = from_list(lambda x: to_class(MCPPlanConfigurationChange, x), self.configuration_changes) + result["identity"] = to_class(MCPPlanResourceIdentity, self.identity) + result["planHandle"] = from_str(self.plan_handle) + result["planHandleExpiresAt"] = from_str(self.plan_handle_expires_at) + result["policy"] = to_class(MCPPlanPolicyResult, self.policy) + result["provenance"] = to_class(MCPPlanProvenance, self.provenance) + result["reloadRequired"] = from_bool(self.reload_required) + result["requiresInteractiveConfiguration"] = from_bool(self.requires_interactive_configuration) + result["target"] = to_class(MCPPlanTarget, self.target) + result["transportChoices"] = from_list(lambda x: (x).to_dict(), self.transport_choices) + if self.recommended_transport_choice_id is not None: + result["recommendedTransportChoiceId"] = from_union([from_str, from_none], self.recommended_transport_choice_id) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class CommandList: @@ -24569,6 +29922,50 @@ def to_dict(self) -> dict: result["session"] = from_union([lambda x: to_class(CanvasSessionContext, x), from_none], self.session) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class CatalogSearchSucceeded: + """A completed catalog search: inert candidate summaries, each carrying a single-use handle.""" + + candidates: list[CatalogCandidate] + """Matching candidates, never more than the requested limit. All text is inert untrusted + data. + """ + kind: ClassVar[str] = "succeeded" + """Discriminator: the search completed""" + + negotiated: CatalogNegotiatedContract + """Protocol version and capabilities the runtime honoured.""" + + search_id: str + """Pseudonymous identifier for this search, issued by the runtime or by the catalog + authority it queried and never by the caller, so it cannot be forged or replayed to + attribute an install to a search that never happened. Always present on a success, so a + result set can be tied to the installs it leads to. It identifies a search rather than a + person: it is derived from no user, account, device, or query data, and must never be + joined with user identity to re-identify anyone. + """ + truncated: bool + """Whether further matches existed beyond the requested limit.""" + + @staticmethod + def from_dict(obj: Any) -> 'CatalogSearchSucceeded': + assert isinstance(obj, dict) + candidates = from_list(CatalogCandidate.from_dict, obj.get("candidates")) + negotiated = CatalogNegotiatedContract.from_dict(obj.get("negotiated")) + search_id = from_str(obj.get("searchId")) + truncated = from_bool(obj.get("truncated")) + return CatalogSearchSucceeded(candidates, negotiated, search_id, truncated) + + def to_dict(self) -> dict: + result: dict = {} + result["candidates"] = from_list(lambda x: to_class(CatalogCandidate, x), self.candidates) + result["kind"] = self.kind + result["negotiated"] = to_class(CatalogNegotiatedContract, self.negotiated) + result["searchId"] = from_str(self.search_id) + result["truncated"] = from_bool(self.truncated) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class HandlePendingToolCallRequest: @@ -24601,30 +29998,92 @@ 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 ToolsTaskCompleteEventDataRequest: + """Task-completion tool arguments and final result used to build a label-safe session event + payload. + """ + final_result: ToolResultExpanded + """Final expanded result returned by the task_complete tool.""" + + tool_args: Any + """Arguments supplied to the completed task_complete tool call.""" + + @staticmethod + def from_dict(obj: Any) -> 'ToolsTaskCompleteEventDataRequest': + assert isinstance(obj, dict) + final_result = ToolResultExpanded.from_dict(obj.get("finalResult")) + tool_args = obj.get("toolArgs") + return ToolsTaskCompleteEventDataRequest(final_result, tool_args) + + def to_dict(self) -> dict: + result: dict = {} + result["finalResult"] = to_class(ToolResultExpanded, self.final_result) + result["toolArgs"] = self.tool_args + 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 + """Durable resource consumption.""" + created_at: int + """Epoch milliseconds when the run was created.""" + declared_limits: FactoryDeclaredLimits + """Resource ceilings declared by the factory.""" + declared_phase_count: int + """Number of phases declared by the factory.""" + description: str + """Human-readable factory description.""" + factory_name: str + """Registered factory name.""" + live_agent_count: int + """Number of direct factory agents currently live.""" + observed_at: int + """Epoch milliseconds when this live-overlay snapshot was observed.""" + revision: int + """Monotonic durable run revision.""" + run_id: str + """Factory run identifier.""" + status: FactoryRunStatus + """Current factory run status.""" + total_spawned_agent_count: int + """Total direct factory agents spawned across all attempts.""" + updated_at: int + """Epoch milliseconds when the durable run was last updated.""" + active_segment_started_at: int | None = None + """Epoch milliseconds when the current active segment started, or null while inactive.""" + approved: FactoryDeclaredLimits | None = None + """Approved effective resource ceilings, or null until approved.""" + completed_at: int | None = None + """Epoch milliseconds when the run completed, or null while nonterminal.""" + current_phase: FactoryCurrentPhase | None = None + """Current phase identity, or null before any phase is entered.""" + started_at: int | None = None + """Epoch milliseconds when execution first started, or null before start.""" + terminal: FactoryRunTerminal | None = None + """Terminal run outcome, or null while nonterminal.""" @staticmethod def from_dict(obj: Any) -> 'FactoryRunSummary': @@ -24697,87 +30156,6 @@ def to_dict(self) -> dict: 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: @@ -25138,7 +30516,7 @@ class ProviderModelConfig: in the model list and passed to switchTo) is the provider-qualified `provider/id`. """ provider: str - """Name of the NamedProviderConfig that serves this model.""" + """Name of the configured provider that serves this model.""" capabilities: ModelCapabilitiesOverride | None = None """Optional capability overrides (vision, tool_calls, reasoning, etc.).""" @@ -25196,6 +30574,128 @@ def to_dict(self) -> dict: result["wireModel"] = from_union([from_str, from_none], self.wire_model) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ProviderConfig: + """Custom model-provider configuration (BYOK).""" + + base_url: str + """API endpoint URL.""" + + api_key: str | None = None + """API key. Optional for local providers like Ollama.""" + + azure: ProviderConfigAzure | None = None + """Azure-specific provider options.""" + + bearer_token: str | None = None + """Bearer token for authentication. Sets the Authorization header directly. Takes precedence + over apiKey when both are set. + """ + has_bearer_token_provider: bool | None = None + """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. + """ + headers: dict[str, str] | None = None + """Custom HTTP headers to include in all outbound requests to the provider.""" + + max_context_window_tokens: float | None = None + """Maximum context window tokens for the model.""" + + max_output_tokens: float | None = None + """Maximum output tokens for the model.""" + + max_prompt_tokens: float | None = None + """Maximum prompt/input tokens for the model.""" + + model_capabilities: ModelCapabilitiesOverride | None = None + """Overrides for model capabilities when they cannot be inferred from modelId.""" + + model_id: str | None = None + """Well-known model ID used for capability lookup. When set, agent behavior config and token + limits are inferred from this model. + """ + provider_name: str | None = None + """Provider name used for model and telemetry attribution.""" + + transport: ProviderTransport | None = None + """Provider transport. Defaults to "http".""" + + type: ProviderType | None = None + """Provider type. Defaults to "openai" for generic OpenAI-compatible APIs.""" + + wire_api: ProviderWireAPI | None = None + """Wire API format (openai/azure only). Defaults to "completions".""" + + wire_model: str | None = None + """The model identifier sent to the provider API for inference (the "wire" model), as + opposed to modelId which is the well-known base. + """ + + @staticmethod + def from_dict(obj: Any) -> 'ProviderConfig': + assert isinstance(obj, dict) + base_url = from_str(obj.get("baseUrl")) + api_key = from_union([from_str, from_none], obj.get("apiKey")) + azure = from_union([ProviderConfigAzure.from_dict, from_none], obj.get("azure")) + bearer_token = from_union([from_str, from_none], obj.get("bearerToken")) + has_bearer_token_provider = from_union([from_bool, from_none], obj.get("hasBearerTokenProvider")) + headers = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("headers")) + max_context_window_tokens = from_union([from_float, from_none], obj.get("maxContextWindowTokens")) + max_output_tokens = from_union([from_float, from_none], obj.get("maxOutputTokens")) + max_prompt_tokens = from_union([from_float, from_none], obj.get("maxPromptTokens")) + model_capabilities = from_union([ModelCapabilitiesOverride.from_dict, from_none], obj.get("modelCapabilities")) + model_id = from_union([from_str, from_none], obj.get("modelId")) + provider_name = from_union([from_str, from_none], obj.get("providerName")) + transport = from_union([ProviderTransport, from_none], obj.get("transport")) + type = from_union([ProviderType, from_none], obj.get("type")) + wire_api = from_union([ProviderWireAPI, from_none], obj.get("wireApi")) + wire_model = from_union([from_str, from_none], obj.get("wireModel")) + return ProviderConfig(base_url, api_key, azure, bearer_token, has_bearer_token_provider, headers, max_context_window_tokens, max_output_tokens, max_prompt_tokens, model_capabilities, model_id, provider_name, transport, type, wire_api, wire_model) + + def to_dict(self) -> dict: + result: dict = {} + result["baseUrl"] = from_str(self.base_url) + if self.api_key is not None: + result["apiKey"] = from_union([from_str, from_none], self.api_key) + if self.azure is not None: + result["azure"] = from_union([lambda x: to_class(ProviderConfigAzure, x), from_none], self.azure) + if self.bearer_token is not None: + result["bearerToken"] = from_union([from_str, from_none], self.bearer_token) + if self.has_bearer_token_provider is not None: + result["hasBearerTokenProvider"] = from_union([from_bool, from_none], self.has_bearer_token_provider) + if self.headers is not None: + result["headers"] = from_union([lambda x: from_dict(from_str, x), from_none], self.headers) + if self.max_context_window_tokens is not None: + result["maxContextWindowTokens"] = from_union([to_float, from_none], self.max_context_window_tokens) + if self.max_output_tokens is not None: + result["maxOutputTokens"] = from_union([to_float, from_none], self.max_output_tokens) + if self.max_prompt_tokens is not None: + result["maxPromptTokens"] = from_union([to_float, from_none], self.max_prompt_tokens) + if self.model_capabilities is not None: + result["modelCapabilities"] = from_union([lambda x: to_class(ModelCapabilitiesOverride, x), from_none], self.model_capabilities) + if self.model_id is not None: + result["modelId"] = from_union([from_str, from_none], self.model_id) + if self.provider_name is not None: + result["providerName"] = from_union([from_str, from_none], self.provider_name) + if self.transport is not None: + result["transport"] = from_union([lambda x: to_enum(ProviderTransport, x), from_none], self.transport) + if self.type is not None: + result["type"] = from_union([lambda x: to_enum(ProviderType, x), from_none], self.type) + if self.wire_api is not None: + result["wireApi"] = from_union([lambda x: to_enum(ProviderWireAPI, x), from_none], self.wire_api) + if self.wire_model is not None: + result["wireModel"] = from_union([from_str, from_none], self.wire_model) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionsConfigureParams: @@ -25267,23 +30767,22 @@ class SandboxConfig: add_current_working_directory: bool | None = None """Whether to auto-add the current working directory to readwritePaths. Default: true.""" - allow_dev_tool_caches: 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 exporting CARGO_HOME/GOPATH/etc. Default: true (enabled by default; - set to false to opt out). - """ - 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 tool directories discovered on PATH and in toolchain + environment variables (GOROOT, JAVA_HOME, VIRTUAL_ENV, and similar), and to common + developer-tool caches, config, and toolchains. Writable grants cover scratch caches, the + Unix GitHub CLI cache, and Cargo's registry, git store, and lock/tracker files. A + relocated CARGO_HOME gets the same narrow split: registry and git are read-write; bin is + read-only; the home root, config.toml, and credentials.toml stay ungranted. Set to false + to disable every grant listed above; user-installed toolchains and caches then need + explicit userPolicy.filesystem readonlyPaths and readwritePaths entries. The working + directory (see addCurrentWorkingDirectory), temporary storage, session log paths, and + system locations follow their own rules and stay granted. 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.""" @@ -25292,23 +30791,20 @@ 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")) - allow_dev_tool_caches = from_union([from_bool, from_none], obj.get("allowDevToolCaches")) - 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, allow_dev_tool_caches, 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.allow_dev_tool_caches is not None: - result["allowDevToolCaches"] = from_union([from_bool, from_none], self.allow_dev_tool_caches) - 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 @@ -25319,7 +30815,10 @@ class SessionFSSqliteTransactionResult: """Per-statement results, or a classified transaction error.""" results: list[SessionFSSqliteQueryResult] + """Per-statement query results in input order.""" + error: SessionFSSqliteTransactionError | None = None + """Classified transaction failure, when execution did not succeed.""" @staticmethod def from_dict(obj: Any) -> 'SessionFSSqliteTransactionResult': @@ -25335,6 +30834,129 @@ def to_dict(self) -> dict: 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 MCPConfigAddRequest: + """MCP server name and configuration to add to user configuration.""" + + config: MCPSerializableServerConfig + """MCP server configuration (stdio process or remote HTTP/SSE)""" + + name: str + """Unique name for the MCP server""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPConfigAddRequest': + assert isinstance(obj, dict) + config = MCPSerializableServerConfig.from_dict(obj.get("config")) + name = from_str(obj.get("name")) + return MCPConfigAddRequest(config, name) + + def to_dict(self) -> dict: + result: dict = {} + result["config"] = to_class(MCPSerializableServerConfig, self.config) + result["name"] = from_str(self.name) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MCPConfigList: + """User-configured MCP servers, keyed by server name.""" + + servers: dict[str, MCPSerializableServerConfig] + """All MCP servers from user config, keyed by name""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPConfigList': + assert isinstance(obj, dict) + servers = from_dict(MCPSerializableServerConfig.from_dict, obj.get("servers")) + return MCPConfigList(servers) + + def to_dict(self) -> dict: + result: dict = {} + result["servers"] = from_dict(lambda x: to_class(MCPSerializableServerConfig, x), self.servers) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MCPConfigUpdateRequest: + """MCP server name and replacement configuration to write to user configuration.""" + + config: MCPSerializableServerConfig + """MCP server configuration (stdio process or remote HTTP/SSE)""" + + name: str + """Name of the MCP server to update""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPConfigUpdateRequest': + assert isinstance(obj, dict) + config = MCPSerializableServerConfig.from_dict(obj.get("config")) + name = from_str(obj.get("name")) + return MCPConfigUpdateRequest(config, name) + + def to_dict(self) -> dict: + result: dict = {} + result["config"] = to_class(MCPSerializableServerConfig, self.config) + result["name"] = from_str(self.name) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MCPRestartServerRequest: + """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. + """ + server_name: str + """Name of the MCP server to restart""" + + config: MCPSerializableServerConfig | None = None + """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). + """ + + @staticmethod + def from_dict(obj: Any) -> 'MCPRestartServerRequest': + assert isinstance(obj, dict) + server_name = from_str(obj.get("serverName")) + config = from_union([MCPSerializableServerConfig.from_dict, from_none], obj.get("config")) + return MCPRestartServerRequest(server_name, config) + + def to_dict(self) -> dict: + result: dict = {} + result["serverName"] = from_str(self.server_name) + if self.config is not None: + result["config"] = from_union([lambda x: to_class(MCPSerializableServerConfig, x), from_none], self.config) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MCPStartServerRequest: + """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: MCPSerializableServerConfig | 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) + server_name = from_str(obj.get("serverName")) + config = from_union([MCPSerializableServerConfig.from_dict, from_none], obj.get("config")) + return MCPStartServerRequest(server_name, config) + + def to_dict(self) -> dict: + result: dict = {} + result["serverName"] = from_str(self.server_name) + if self.config is not None: + result["config"] = from_union([lambda x: to_class(MCPSerializableServerConfig, x), from_none], self.config) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MCPListToolsResult: @@ -25362,7 +30984,7 @@ class UIElicitationSchema: properties: dict[str, UIElicitationSchemaProperty] """Form field definitions, keyed by field name""" - type: UIElicitationSchemaType + type: BuiltinToolInputSchemaType """Schema type indicator (always 'object')""" required: list[str] | None = None @@ -25372,56 +30994,169 @@ class UIElicitationSchema: def from_dict(obj: Any) -> 'UIElicitationSchema': assert isinstance(obj, dict) properties = from_dict(UIElicitationSchemaProperty.from_dict, obj.get("properties")) - type = UIElicitationSchemaType(obj.get("type")) + type = BuiltinToolInputSchemaType(obj.get("type")) required = from_union([lambda x: from_list(from_str, x), from_none], obj.get("required")) return UIElicitationSchema(properties, type, required) def to_dict(self) -> dict: result: dict = {} result["properties"] = from_dict(lambda x: to_class(UIElicitationSchemaProperty, x), self.properties) - result["type"] = to_enum(UIElicitationSchemaType, self.type) + result["type"] = to_enum(BuiltinToolInputSchemaType, self.type) if self.required is not None: 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: - """Factory runs in durable creation order.""" +class UsageGetMetricsResult: + """Accumulated session usage metrics, including premium request cost, token counts, model + breakdown, and code-change totals. + """ + code_changes: UsageMetricsCodeChanges + """Aggregated code change metrics""" - runs: list[FactoryRunSummary] + last_call_input_tokens: int + """Input tokens from the most recent main-agent API call""" + + last_call_output_tokens: int + """Output tokens from the most recent main-agent API call""" + + model_metrics: dict[str, UsageMetricsModelMetric] + """Per-model token and request metrics, keyed by model identifier""" + + session_start_time: datetime + """ISO 8601 timestamp when the session started""" + + total_api_duration_ms: int + """Total time spent in model API calls (milliseconds)""" + + total_premium_request_cost: float + """Total user-initiated premium request cost across all models (may be fractional due to + multipliers) + """ + total_user_requests: int + """Raw count of user-initiated API requests""" + + agent_metrics: dict[str, UsageMetricsAgentMetric] | None = None + """Per-agent usage metrics, keyed by agent instance identifier. The main conversation uses + the stable key `main`. + """ + current_model: str | None = None + """Currently active model identifier""" + + token_details: dict[str, UsageMetricsTokenDetail] | None = None + """Session-wide per-token-type accumulated token counts""" + + total_nano_aiu: float | None = None + """Session-wide accumulated nano-AI units cost""" @staticmethod - def from_dict(obj: Any) -> 'FactoryListRunsResult': + def from_dict(obj: Any) -> 'UsageGetMetricsResult': assert isinstance(obj, dict) - runs = from_list(FactoryRunSummary.from_dict, obj.get("runs")) - return FactoryListRunsResult(runs) + code_changes = UsageMetricsCodeChanges.from_dict(obj.get("codeChanges")) + last_call_input_tokens = from_int(obj.get("lastCallInputTokens")) + last_call_output_tokens = from_int(obj.get("lastCallOutputTokens")) + model_metrics = from_dict(UsageMetricsModelMetric.from_dict, obj.get("modelMetrics")) + session_start_time = from_datetime(obj.get("sessionStartTime")) + total_api_duration_ms = from_int(obj.get("totalApiDurationMs")) + total_premium_request_cost = from_float(obj.get("totalPremiumRequestCost")) + total_user_requests = from_int(obj.get("totalUserRequests")) + agent_metrics = from_union([lambda x: from_dict(UsageMetricsAgentMetric.from_dict, x), from_none], obj.get("agentMetrics")) + current_model = from_union([from_str, from_none], obj.get("currentModel")) + token_details = from_union([lambda x: from_dict(UsageMetricsTokenDetail.from_dict, x), from_none], obj.get("tokenDetails")) + total_nano_aiu = from_union([from_float, from_none], obj.get("totalNanoAiu")) + return UsageGetMetricsResult(code_changes, last_call_input_tokens, last_call_output_tokens, model_metrics, session_start_time, total_api_duration_ms, total_premium_request_cost, total_user_requests, agent_metrics, current_model, token_details, total_nano_aiu) def to_dict(self) -> dict: result: dict = {} - result["runs"] = from_list(lambda x: to_class(FactoryRunSummary, x), self.runs) + result["codeChanges"] = to_class(UsageMetricsCodeChanges, self.code_changes) + result["lastCallInputTokens"] = from_int(self.last_call_input_tokens) + result["lastCallOutputTokens"] = from_int(self.last_call_output_tokens) + result["modelMetrics"] = from_dict(lambda x: to_class(UsageMetricsModelMetric, x), self.model_metrics) + result["sessionStartTime"] = self.session_start_time.isoformat() + result["totalApiDurationMs"] = from_int(self.total_api_duration_ms) + result["totalPremiumRequestCost"] = to_float(self.total_premium_request_cost) + result["totalUserRequests"] = from_int(self.total_user_requests) + if self.agent_metrics is not None: + result["agentMetrics"] = from_union([lambda x: from_dict(lambda x: to_class(UsageMetricsAgentMetric, x), x), from_none], self.agent_metrics) + if self.current_model is not None: + result["currentModel"] = from_union([from_str, from_none], self.current_model) + if self.token_details is not None: + result["tokenDetails"] = from_union([lambda x: from_dict(lambda x: to_class(UsageMetricsTokenDetail, x), x), from_none], self.token_details) + if self.total_nano_aiu is not None: + result["totalNanoAiu"] = from_union([to_float, from_none], self.total_nano_aiu) 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. +class MCPPlanInstallPlanned: + """A computed MCP install plan. Nothing has been applied: the plan describes what installing + would change, and the plan handle is what a later apply operation would consume. """ + kind: ClassVar[str] = "planned" + """Discriminator: a plan was computed and nothing was changed""" + + negotiated: CatalogNegotiatedContract + """Protocol version and capabilities the runtime honoured.""" + + plan: MCPInstallPlan + """The normalised plan.""" @staticmethod - def from_dict(obj: Any) -> 'BuiltInModelCatalogEntry': + def from_dict(obj: Any) -> 'MCPPlanInstallPlanned': assert isinstance(obj, dict) - id = from_str(obj.get("id")) - return BuiltInModelCatalogEntry(id) + negotiated = CatalogNegotiatedContract.from_dict(obj.get("negotiated")) + plan = MCPInstallPlan.from_dict(obj.get("plan")) + return MCPPlanInstallPlanned(negotiated, plan) def to_dict(self) -> dict: result: dict = {} - result["id"] = from_str(self.id) + result["kind"] = self.kind + result["negotiated"] = to_class(CatalogNegotiatedContract, self.negotiated) + result["plan"] = to_class(MCPInstallPlan, self.plan) + 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] + """Factory run summaries in durable creation order.""" + + 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. @@ -25474,11 +31209,15 @@ class SessionOpenOptions: 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`). + context and `@`-mention completion). Conventional `.github/skills/` and `.github/agents/` + definitions under each directory also join the session's project catalogs when their + existing subsystem gates are enabled: added-root skills require both + `enableConfigDiscovery` and effective `enableSkills`; added-root agents require + `enableConfigDiscovery`. Supplying a directory therefore activates configuration from it + and should be treated as a trust decision. 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 during session creation and cold + resume and is not persisted, so a cold resume must re-supply the directories. """ agent_context: str | None = None """Runtime context discriminator for agent filtering.""" @@ -25529,13 +31268,17 @@ 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.""" enable_citations: bool | None = None - """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: enable native model citations for supported Anthropic and OpenAI models, + normalized onto the `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 @@ -25568,6 +31311,10 @@ class SessionOpenOptions: enable_script_safety: bool | None = None """Whether shell-script safety heuristics are enabled.""" + enable_skills: bool | None = None + """Whether skill loading is enabled. When omitted, an SDK skill provider enables skills by + default. + """ enable_streaming: bool | None = None """Whether model responses stream as delta events.""" @@ -25598,11 +31345,24 @@ class SessionOpenOptions: feature_flags: dict[str, bool] | None = None """Feature-flag values resolved by the host.""" + # Internal: this field is an internal SDK API and is not part of the public surface. + has_skill_provider: bool | None = None + """Whether the requesting SDK session has a skill provider. The provider remains ephemeral + and is never persisted in session options or history. When enableSkills is false, it + remains bound but dormant and receives no callbacks. Cloud, relay, handoff, and raw + sessions.open flows reject it because they cannot safely pre-register the callback + handler. + """ included_builtin_agents: list[str] | None = None """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. """ + included_builtin_skills: list[str] | None = None + """Built-in skill names to include in this session. When specified, only these + runtime-bundled skills are available. Skills from other sources with the same name remain + available. + """ installed_plugins: list[InstalledPlugin] | None = None """Installed plugins visible to the session.""" @@ -25618,6 +31378,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). @@ -25668,6 +31432,11 @@ class SessionOpenOptions: sandbox_config: SandboxConfig | None = None """Resolved sandbox configuration.""" + # Internal: this field is an internal SDK API and is not part of the public surface. + sandbox_config_source: _SandboxConfigSource | None = None + """Origin of the sandbox choice. The runtime uses this only for internal telemetry + provenance; managed policy is derived independently. + """ session_capabilities: list[SessionCapability] | None = None """Capabilities enabled for this session.""" @@ -25725,12 +31494,14 @@ 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_skills = from_union([from_bool, from_none], obj.get("enableSkills")) 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")) @@ -25739,12 +31510,15 @@ def from_dict(obj: Any) -> 'SessionOpenOptions': excluded_tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("excludedTools")) exp_assignments = obj.get("expAssignments") feature_flags = from_union([lambda x: from_dict(from_bool, x), from_none], obj.get("featureFlags")) + has_skill_provider = from_union([from_bool, from_none], obj.get("hasSkillProvider")) included_builtin_agents = from_union([lambda x: from_list(from_str, x), from_none], obj.get("includedBuiltinAgents")) + included_builtin_skills = from_union([lambda x: from_list(from_str, x), from_none], obj.get("includedBuiltinSkills")) installed_plugins = from_union([lambda x: from_list(InstalledPlugin.from_dict, x), from_none], obj.get("installedPlugins")) integration_id = from_union([from_str, from_none], obj.get("integrationId")) 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")) @@ -25760,6 +31534,7 @@ def from_dict(obj: Any) -> 'SessionOpenOptions': remote_steerable = from_union([from_bool, from_none], obj.get("remoteSteerable")) running_in_interactive_mode = from_union([from_bool, from_none], obj.get("runningInInteractiveMode")) sandbox_config = from_union([SandboxConfig.from_dict, from_none], obj.get("sandboxConfig")) + sandbox_config_source = from_union([_SandboxConfigSource, from_none], obj.get("sandboxConfigSource")) 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")) @@ -25772,7 +31547,7 @@ 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, 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_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, 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) + 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_skills, enable_streaming, env_value_mode, events_log_directory, events_log_includes_subagents, excluded_builtin_agents, excluded_tools, exp_assignments, feature_flags, has_skill_provider, included_builtin_agents, included_builtin_skills, 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, sandbox_config_source, 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 = {} @@ -25812,6 +31587,8 @@ 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: @@ -25824,6 +31601,8 @@ def to_dict(self) -> dict: result["enableOnDemandInstructionDiscovery"] = from_union([from_bool, from_none], self.enable_on_demand_instruction_discovery) if self.enable_script_safety is not None: result["enableScriptSafety"] = from_union([from_bool, from_none], self.enable_script_safety) + if self.enable_skills is not None: + result["enableSkills"] = from_union([from_bool, from_none], self.enable_skills) if self.enable_streaming is not None: result["enableStreaming"] = from_union([from_bool, from_none], self.enable_streaming) if self.env_value_mode is not None: @@ -25840,8 +31619,12 @@ def to_dict(self) -> dict: result["expAssignments"] = self.exp_assignments if self.feature_flags is not None: result["featureFlags"] = from_union([lambda x: from_dict(from_bool, x), from_none], self.feature_flags) + if self.has_skill_provider is not None: + result["hasSkillProvider"] = from_union([from_bool, from_none], self.has_skill_provider) if self.included_builtin_agents is not None: result["includedBuiltinAgents"] = from_union([lambda x: from_list(from_str, x), from_none], self.included_builtin_agents) + if self.included_builtin_skills is not None: + result["includedBuiltinSkills"] = from_union([lambda x: from_list(from_str, x), from_none], self.included_builtin_skills) if self.installed_plugins is not None: result["installedPlugins"] = from_union([lambda x: from_list(lambda x: to_class(InstalledPlugin, x), x), from_none], self.installed_plugins) if self.integration_id is not None: @@ -25852,6 +31635,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: @@ -25882,6 +31667,8 @@ def to_dict(self) -> dict: result["runningInInteractiveMode"] = from_union([from_bool, from_none], self.running_in_interactive_mode) if self.sandbox_config is not None: result["sandboxConfig"] = from_union([lambda x: to_class(SandboxConfig, x), from_none], self.sandbox_config) + if self.sandbox_config_source is not None: + result["sandboxConfigSource"] = from_union([lambda x: to_enum(_SandboxConfigSource, x), from_none], self.sandbox_config_source) if self.session_capabilities is not None: result["sessionCapabilities"] = from_union([lambda x: from_list(lambda x: to_enum(SessionCapability, x), x), from_none], self.session_capabilities) if self.session_id is not None: @@ -25981,8 +31768,9 @@ class SessionUpdateOptionsParams: """Whether to enable cross-session store writes and reads.""" enable_skills: bool | None = None - """Whether to enable skill directory scanning and loading. Falls back to - enableConfigDiscovery when unset. + """Whether skill loading is enabled. Explicit false disables every source, including a bound + SDK provider; changing the value invalidates the loaded skill snapshot. When omitted, + creation falls back to enableConfigDiscovery unless an SDK skill provider is registered. """ enable_streaming: bool | None = None """Whether to stream model responses.""" @@ -26014,6 +31802,11 @@ class SessionUpdateOptionsParams: are available, subject to runtime availability and exclusions. Custom agents with the same name remain available. Set to null to remove the allowlist restriction. """ + included_builtin_skills: list[str] | None = None + """Built-in skill names to include in this session. When specified, only these + runtime-bundled skills are available. Skills from other sources with the same name remain + available. Set to null to remove the allowlist restriction. + """ installed_plugins: list[SessionInstalledPlugin] | None = None """Full set of installed plugins for the session. Replaces the existing list; the runtime invalidates the skills cache only when the list materially changes. @@ -26067,6 +31860,11 @@ class SessionUpdateOptionsParams: sandbox_config: SandboxConfig | None = None """Resolved sandbox configuration.""" + # Internal: this field is an internal SDK API and is not part of the public surface. + sandbox_config_source: _SandboxConfigSource | None = None + """Origin of the sandbox choice. The runtime uses this only for internal telemetry + provenance; managed policy is derived independently. + """ session_capabilities: list[SessionCapability] | None = None """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 @@ -26143,6 +31941,7 @@ def from_dict(obj: Any) -> 'SessionUpdateOptionsParams': 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")) included_builtin_agents = from_union([lambda x: from_list(from_str, x), from_none], obj.get("includedBuiltinAgents")) + included_builtin_skills = from_union([lambda x: from_list(from_str, x), from_none], obj.get("includedBuiltinSkills")) installed_plugins = from_union([lambda x: from_list(SessionInstalledPlugin.from_dict, x), from_none], obj.get("installedPlugins")) integration_id = from_union([from_str, from_none], obj.get("integrationId")) is_experimental_mode = from_union([from_bool, from_none], obj.get("isExperimentalMode")) @@ -26158,6 +31957,7 @@ def from_dict(obj: Any) -> 'SessionUpdateOptionsParams': reasoning_summary = from_union([ReasoningSummary, from_none], obj.get("reasoningSummary")) running_in_interactive_mode = from_union([from_bool, from_none], obj.get("runningInInteractiveMode")) sandbox_config = from_union([SandboxConfig.from_dict, from_none], obj.get("sandboxConfig")) + sandbox_config_source = from_union([_SandboxConfigSource, from_none], obj.get("sandboxConfigSource")) 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")) @@ -26171,7 +31971,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, 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) + 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, included_builtin_skills, 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, sandbox_config_source, 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 = {} @@ -26233,6 +32033,8 @@ def to_dict(self) -> dict: result["featureFlags"] = from_union([lambda x: from_dict(from_bool, x), from_none], self.feature_flags) if self.included_builtin_agents is not None: result["includedBuiltinAgents"] = from_union([lambda x: from_list(from_str, x), from_none], self.included_builtin_agents) + if self.included_builtin_skills is not None: + result["includedBuiltinSkills"] = from_union([lambda x: from_list(from_str, x), from_none], self.included_builtin_skills) if self.installed_plugins is not None: result["installedPlugins"] = from_union([lambda x: from_list(lambda x: to_class(SessionInstalledPlugin, x), x), from_none], self.installed_plugins) if self.integration_id is not None: @@ -26263,6 +32065,8 @@ def to_dict(self) -> dict: result["runningInInteractiveMode"] = from_union([from_bool, from_none], self.running_in_interactive_mode) if self.sandbox_config is not None: result["sandboxConfig"] = from_union([lambda x: to_class(SandboxConfig, x), from_none], self.sandbox_config) + if self.sandbox_config_source is not None: + result["sandboxConfigSource"] = from_union([lambda x: to_enum(_SandboxConfigSource, x), from_none], self.sandbox_config_source) if self.session_capabilities is not None: 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: @@ -26302,37 +32106,35 @@ class UIElicitationRequest: requested_schema: UIElicitationSchema """JSON Schema describing the form fields to present to the user""" + meta: dict[str, Any] | None = None + """MCP request metadata.""" + + mode: MCPElicitationFormMode | None = None + """Elicitation mode. Omitted and form are equivalent for structured elicitation.""" + + task: MCPTaskMetadata | None = None + """MCP task metadata.""" + @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) + meta = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("_meta")) + mode = from_union([MCPElicitationFormMode, from_none], obj.get("mode")) + task = from_union([MCPTaskMetadata.from_dict, from_none], obj.get("task")) + return UIElicitationRequest(message, requested_schema, meta, mode, task) 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["models"] = from_list(lambda x: to_class(BuiltInModelCatalogEntry, x), self.models) + if self.meta is not None: + result["_meta"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.meta) + if self.mode is not None: + result["mode"] = from_union([lambda x: to_enum(MCPElicitationFormMode, x), from_none], self.mode) + if self.task is not None: + result["task"] = from_union([lambda x: to_class(MCPTaskMetadata, x), from_none], self.task) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -26483,6 +32285,10 @@ class CopilotUserResponse: """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. + + Snapshot of the authenticated user's Copilot subscription info, if known. + + Snapshot of the authenticated user's Copilot subscription info, if known """ access_type_sku: str | None = None """Copilot access SKU identifier (e.g. `free_limited_copilot`, @@ -26878,8 +32684,8 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class APIKeyAuthInfo: - """Authentication-info variant for API-key authentication to a non-GitHub LLM provider, - carrying the secret `apiKey` and host. + """Authentication-info input variant for API-key authentication to a non-GitHub LLM + provider, carrying the secret `apiKey` and host. """ api_key: str """The API key. Treat as a secret.""" @@ -26913,6 +32719,120 @@ def to_dict(self) -> dict: result["copilotUser"] = from_union([lambda x: to_class(CopilotUserResponse, x), from_none], self.copilot_user) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class AuthIdentity: + """Credential-free authentication identity safe to expose to hosts and user interfaces.""" + + host: str + """Authentication host""" + + type: AuthInfoType + """Authentication type""" + + copilot_user: CopilotUserResponse | None = None + """Snapshot of the authenticated user's Copilot subscription info, if known""" + + env_var: str | None = None + """Name of the environment variable that supplied the credential, when applicable""" + + login: str | None = None + """Authenticated login, when available""" + + registration_id: str | None = None + """Opaque SDK GitHub credential registration backing this identity. Routing metadata only; + never a credential. + """ + + @staticmethod + def from_dict(obj: Any) -> 'AuthIdentity': + assert isinstance(obj, dict) + host = from_str(obj.get("host")) + type = AuthInfoType(obj.get("type")) + copilot_user = from_union([CopilotUserResponse.from_dict, from_none], obj.get("copilotUser")) + env_var = from_union([from_str, from_none], obj.get("envVar")) + login = from_union([from_str, from_none], obj.get("login")) + registration_id = from_union([from_str, from_none], obj.get("registrationId")) + return AuthIdentity(host, type, copilot_user, env_var, login, registration_id) + + def to_dict(self) -> dict: + result: dict = {} + result["host"] = from_str(self.host) + result["type"] = to_enum(AuthInfoType, self.type) + if self.copilot_user is not None: + result["copilotUser"] = from_union([lambda x: to_class(CopilotUserResponse, x), from_none], self.copilot_user) + if self.env_var is not None: + result["envVar"] = from_union([from_str, from_none], self.env_var) + if self.login is not None: + result["login"] = from_union([from_str, from_none], self.login) + if self.registration_id is not None: + result["registrationId"] = from_union([from_str, from_none], self.registration_id) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class BuiltinToolDescriptor: + """Rust-owned metadata and input schema for a built-in tool.""" + + description: str + """Model-facing description of the tool's behavior.""" + + has_summarise_intention: bool + """Whether the tool provides a specialized intention summary.""" + + is_terminal: bool + """Whether the tool executes commands in a terminal.""" + + name: str + """Stable name used to invoke the built-in tool.""" + + safe_for_telemetry: bool | BuiltinToolSafeTelemetryFields + """Policy describing which tool metadata may be recorded without obfuscation.""" + + format: BuiltinToolFormat | None = None + """Optional custom input format used instead of a JSON Schema.""" + + input_schema: BuiltinToolInputSchema | None = None + """JSON Schema for the tool input, or null when the tool uses a custom format.""" + + instructions: str | None = None + """Optional supplemental usage instructions for the tool.""" + + title: str | None = None + """Optional human-readable title for the tool.""" + + type: str | None = None + """Optional tool category discriminator.""" + + @staticmethod + def from_dict(obj: Any) -> 'BuiltinToolDescriptor': + assert isinstance(obj, dict) + description = from_str(obj.get("description")) + has_summarise_intention = from_bool(obj.get("hasSummariseIntention")) + is_terminal = from_bool(obj.get("isTerminal")) + name = from_str(obj.get("name")) + safe_for_telemetry = from_union([from_bool, BuiltinToolSafeTelemetryFields.from_dict], obj.get("safeForTelemetry")) + format = from_union([BuiltinToolFormat.from_dict, from_none], obj.get("format")) + input_schema = from_union([BuiltinToolInputSchema.from_dict, from_none], obj.get("inputSchema")) + instructions = from_union([from_none, from_str], obj.get("instructions")) + title = from_union([from_none, from_str], obj.get("title")) + type = from_union([from_none, from_str], obj.get("type")) + return BuiltinToolDescriptor(description, has_summarise_intention, is_terminal, name, safe_for_telemetry, format, input_schema, instructions, title, type) + + def to_dict(self) -> dict: + result: dict = {} + result["description"] = from_str(self.description) + result["hasSummariseIntention"] = from_bool(self.has_summarise_intention) + result["isTerminal"] = from_bool(self.is_terminal) + result["name"] = from_str(self.name) + result["safeForTelemetry"] = from_union([from_bool, lambda x: to_class(BuiltinToolSafeTelemetryFields, x)], self.safe_for_telemetry) + result["format"] = from_union([lambda x: to_class(BuiltinToolFormat, x), from_none], self.format) + result["inputSchema"] = from_union([lambda x: to_class(BuiltinToolInputSchema, x), from_none], self.input_schema) + result["instructions"] = from_union([from_none, from_str], self.instructions) + result["title"] = from_union([from_none, from_str], self.title) + result["type"] = from_union([from_none, from_str], self.type) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class CopilotAPITokenAuthInfo: @@ -27005,8 +32925,8 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class EnvAuthInfo: - """Authentication-info variant for a token sourced from an environment variable, with host, - optional login, token, and env var name. + """Authentication-info input variant for a token sourced from an environment variable, with + host, optional login, token, and env var name. """ env_var: str """Name of the environment variable the token was sourced from.""" @@ -27053,11 +32973,225 @@ def to_dict(self) -> dict: result["login"] = from_union([from_str, from_none], self.login) 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 + """Accumulated active agent time in milliseconds.""" + + agent_id: str + """Stable direct-agent identifier.""" + + agent_type: str + """Registered agent type.""" + + label: str + """Friendly, non-unique name intended for display""" + + run_id: str + """Owning factory run identifier.""" + + status: str + """Current durable or live agent status.""" + + tool_call_id: str + """Tool-call identifier that launched the agent.""" + + activity: str | None = None + """Prompt-safe live activity text.""" + + completed_at: int | None = None + """Epoch milliseconds when the agent completed.""" + + display_name: str | None = None + """Friendly, non-unique name intended for display""" + + phase_id: str | None = None + """Phase identifier active when the agent was launched, or null.""" + + requested_model: str | None = None + """Model requested when the agent was launched.""" + + resolved_model: str | None = None + """Concrete model resolved for the agent.""" + + started_at: int | None = None + """Epoch milliseconds when the agent started.""" + + @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")) + display_name = from_union([from_str, from_none], obj.get("displayName")) + 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, display_name, 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) + if self.display_name is not None: + result["displayName"] = from_union([from_str, from_none], self.display_name) + 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 FactoryRunDetail: + """Full factory run observability detail.""" + + agents: list[FactoryAgentSummary] + """Durable identities and live statuses for direct factory agents.""" + + consumed: FactoryRunConsumed + """Durable resource consumption.""" + + created_at: int + """Epoch milliseconds when the run was created.""" + + declared_limits: FactoryDeclaredLimits + """Resource ceilings declared by the factory.""" + + declared_phase_count: int + """Number of phases declared by the factory.""" + + description: str + """Human-readable factory description.""" + + factory_name: str + """Registered factory name.""" + + live_agent_count: int + """Number of direct factory agents currently live.""" + + observed_at: int + """Epoch milliseconds when this live-overlay snapshot was observed.""" + + phases: list[FactoryPhaseObservation] + """Lifecycle and timing observations for each factory phase.""" + + progress: FactoryProgressPage + """Bidirectional page of durable factory progress.""" + + revision: int + """Monotonic durable run revision.""" + + run_id: str + """Factory run identifier.""" + + status: FactoryRunStatus + """Current factory run status.""" + + total_spawned_agent_count: int + """Total direct factory agents spawned across all attempts.""" + + updated_at: int + """Epoch milliseconds when the durable run was last updated.""" + + active_segment_started_at: int | None = None + """Epoch milliseconds when the current active segment started, or null while inactive.""" + + approved: FactoryDeclaredLimits | None = None + """Approved effective resource ceilings, or null until approved.""" + + completed_at: int | None = None + """Epoch milliseconds when the run completed, or null while nonterminal.""" + + current_phase: FactoryCurrentPhase | None = None + """Current phase identity, or null before any phase is entered.""" + + started_at: int | None = None + """Epoch milliseconds when execution first started, or null before start.""" + + terminal: FactoryRunTerminal | None = None + """Terminal run outcome, or null while nonterminal.""" + + @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 GhCLIAuthInfo: - """Authentication-info variant for GitHub CLI credentials, carrying host, login, and the `gh - auth token` value. + """Authentication-info input variant for GitHub CLI credentials, carrying host, login, and + the `gh auth token` value. """ host: str """Authentication host.""" @@ -27211,8 +33345,8 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class HMACAuthInfo: - """Authentication-info variant for GitHub-internal HMAC auth, carrying the public GitHub - host and HMAC secret. + """Authentication-info input variant for GitHub-internal HMAC auth, carrying the public + GitHub host and HMAC secret. """ hmac: str """HMAC secret used to sign requests.""" @@ -27286,6 +33420,58 @@ def to_dict(self) -> dict: result["serverName"] = from_str(self.server_name) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MCPOauthProbeResult: + """Passive MCP OAuth probe result. `authenticated` means the server accepted the probe + request while an OAuth-origin access token was attached; it does not prove the server + required or independently validated that token. The probe does not make a second + unauthenticated request. Failed is an expected probe-domain outcome; JSON-RPC errors are + reserved for API-call failures. + """ + status: Status + """Probe outcome variant discriminator.""" + + http_response: McpOauthHttpResponse | None = None + """HTTP response returned by the server. + + HTTP 401 or 403 response returned by the server. + + HTTP response returned by the server, when the probe reached the server and captured the + complete response. + """ + reason: MCPOauthProbeNeedsAuthReason | None = None + """Why authentication is needed.""" + + www_authenticate_params: McpOauthWWWAuthenticateParams | None = None + """Parsed WWW-Authenticate challenge parameters, when present and parseable.""" + + error: str | None = None + """Human-readable probe failure detail.""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPOauthProbeResult': + assert isinstance(obj, dict) + status = Status(obj.get("status")) + http_response = from_union([McpOauthHttpResponse.from_dict, from_none], obj.get("httpResponse")) + reason = from_union([MCPOauthProbeNeedsAuthReason, from_none], obj.get("reason")) + www_authenticate_params = from_union([McpOauthWWWAuthenticateParams.from_dict, from_none], obj.get("wwwAuthenticateParams")) + error = from_union([from_str, from_none], obj.get("error")) + return MCPOauthProbeResult(status, http_response, reason, www_authenticate_params, error) + + def to_dict(self) -> dict: + result: dict = {} + result["status"] = to_enum(Status, self.status) + if self.http_response is not None: + result["httpResponse"] = from_union([lambda x: to_class(McpOauthHttpResponse, x), from_none], self.http_response) + if self.reason is not None: + result["reason"] = from_union([lambda x: to_enum(MCPOauthProbeNeedsAuthReason, x), from_none], self.reason) + if self.www_authenticate_params is not None: + result["wwwAuthenticateParams"] = from_union([lambda x: to_class(McpOauthWWWAuthenticateParams, x), from_none], self.www_authenticate_params) + 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. # Internal: this type is an internal SDK API and is not part of the public surface. @dataclass @@ -27325,6 +33511,304 @@ def to_dict(self) -> dict: result["transport"] = self.transport return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MCPServerConfig: + """MCP server configuration (stdio, remote HTTP/SSE, or in-process) + + Stdio MCP server configuration launched as a child process. + + Remote MCP server configuration accessed over HTTP or SSE. + + In-process MCP server configuration used by embedded SDK clients. + """ + args: list[str] | None = None + """Command-line arguments passed to the Stdio MCP server process.""" + + auth: bool | MCPServerAuthConfigRedirectPort | None = None + """Set to `true` to use defaults, or provide an object with additional auth or OIDC settings.""" + + command: str | None = None + """Executable command used to start the Stdio MCP server process.""" + + config_warnings: list[str] | None = None + """Configuration warnings recorded while loading the server.""" + + cwd: str | None = None + """Working directory for the Stdio MCP server process.""" + + defer_tools: MCPServerConfigDeferTools | None = None + """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) + + Controls whether tools can be loaded on demand. + """ + disable_secret_masking: bool | None = None + """Whether secret masking is disabled for calls to this server.""" + + disable_tool_cache: bool | None = None + """Set to true to disable persisted MCP tool snapshots for this server. Live tool discovery + is unaffected. + + Whether persisted tool snapshots are disabled. + """ + display_name: str | None = None + """Optional human-readable server name.""" + + env: dict[str, str] | None = None + """Environment variables to pass to the Stdio MCP server process.""" + + events: list[str] | None = None + """Event types this server receives as Copilot notifications.""" + + exclude_tools: list[str] | None = None + """Tool names excluded after the include filter is applied.""" + + 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. + + Content filtering mode to apply to this server's tools. + """ + is_default_server: bool | None = None + """Whether this server is a built-in fallback used when the user has not configured their + own server. + + Whether this server is a built-in fallback. + """ + notifications: list[str] | None = None + """Copilot notification types this server may send to the host.""" + + oidc: bool | MCPServerAuthConfigRedirectPort | None = None + """Set to `true` to use defaults, or provide an object with additional auth or OIDC + settings. + + Set to `true` to use default OIDC settings. + """ + safe_for_telemetry: bool | MCPSafeForTelemetryFields | None = None + """Telemetry-obfuscation policy for this server's tools.""" + + source: McpServerSource | None = None + """The origin of this server configuration.""" + + source_path: str | None = None + """Source file path recorded while loading the config.""" + + source_plugin: str | None = None + """Plugin that provided this server.""" + + source_plugin_spec: bool | None = None + """Whether the providing plugin uses the Open Plugin Spec.""" + + source_plugin_version: str | None = None + """Version of the plugin that provided this server.""" + + timeout: int | None = None + """Timeout in milliseconds for tool discovery and tool calls.""" + + tools: list[str] | None = None + """Tools to include. Defaults to all tools if not specified.""" + + type: MCPServerConfigType | None = None + """Local transport type. Defaults to stdio when omitted. + + Remote transport type. Defaults to "http" when omitted. + """ + headers: dict[str, str] | None = None + """HTTP headers to include in requests to the remote MCP server.""" + + headers_refresh_ttl_ms: int | None = None + """Dynamic-header refresh cache lifetime in milliseconds.""" + + oauth_client_id: str | None = None + """OAuth client ID for a pre-registered remote MCP OAuth client.""" + + oauth_grant_type: MCPGrantType | None = None + """OAuth grant type to use when authenticating to the remote MCP server.""" + + oauth_public_client: bool | None = None + """Whether the configured OAuth client is public and does not require a client secret.""" + + url: str | None = None + """URL of the remote MCP server endpoint.""" + + server_instance: Any = None + """In-process MCP server instance. This value cannot cross a JSON-RPC boundary.""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPServerConfig': + assert isinstance(obj, dict) + args = from_union([lambda x: from_list(from_str, x), from_none], obj.get("args")) + auth = from_union([from_bool, MCPServerAuthConfigRedirectPort.from_dict, from_none], obj.get("auth")) + command = from_union([from_str, from_none], obj.get("command")) + config_warnings = from_union([lambda x: from_list(from_str, x), from_none], obj.get("configWarnings")) + cwd = from_union([from_str, from_none], obj.get("cwd")) + defer_tools = from_union([MCPServerConfigDeferTools, from_none], obj.get("deferTools")) + disable_secret_masking = from_union([from_bool, from_none], obj.get("disableSecretMasking")) + disable_tool_cache = from_union([from_bool, from_none], obj.get("disableToolCache")) + display_name = from_union([from_str, from_none], obj.get("displayName")) + env = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("env")) + events = from_union([lambda x: from_list(from_str, x), from_none], obj.get("events")) + exclude_tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("excludeTools")) + 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")) + notifications = from_union([lambda x: from_list(from_str, x), from_none], obj.get("notifications")) + oidc = from_union([from_bool, MCPServerAuthConfigRedirectPort.from_dict, from_none], obj.get("oidc")) + safe_for_telemetry = from_union([from_bool, MCPSafeForTelemetryFields.from_dict, from_none], obj.get("safeForTelemetry")) + source = from_union([McpServerSource, from_none], obj.get("source")) + source_path = from_union([from_str, from_none], obj.get("sourcePath")) + source_plugin = from_union([from_str, from_none], obj.get("sourcePlugin")) + source_plugin_spec = from_union([from_bool, from_none], obj.get("sourcePluginSpec")) + source_plugin_version = from_union([from_str, from_none], obj.get("sourcePluginVersion")) + 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([MCPServerConfigType, from_none], obj.get("type")) + headers = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("headers")) + headers_refresh_ttl_ms = from_union([from_int, from_none], obj.get("headersRefreshTtlMs")) + oauth_client_id = from_union([from_str, from_none], obj.get("oauthClientId")) + oauth_grant_type = from_union([MCPGrantType, from_none], obj.get("oauthGrantType")) + oauth_public_client = from_union([from_bool, from_none], obj.get("oauthPublicClient")) + url = from_union([from_str, from_none], obj.get("url")) + server_instance = obj.get("serverInstance") + return MCPServerConfig(args, auth, command, config_warnings, cwd, defer_tools, disable_secret_masking, disable_tool_cache, display_name, env, events, exclude_tools, filter_mapping, is_default_server, notifications, oidc, safe_for_telemetry, source, source_path, source_plugin, source_plugin_spec, source_plugin_version, timeout, tools, type, headers, headers_refresh_ttl_ms, oauth_client_id, oauth_grant_type, oauth_public_client, url, server_instance) + + def to_dict(self) -> dict: + result: dict = {} + if self.args is not None: + result["args"] = from_union([lambda x: from_list(from_str, x), from_none], self.args) + if self.auth is not None: + result["auth"] = from_union([from_bool, lambda x: to_class(MCPServerAuthConfigRedirectPort, x), from_none], self.auth) + if self.command is not None: + result["command"] = from_union([from_str, from_none], self.command) + if self.config_warnings is not None: + result["configWarnings"] = from_union([lambda x: from_list(from_str, x), from_none], self.config_warnings) + if self.cwd is not None: + 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_secret_masking is not None: + result["disableSecretMasking"] = from_union([from_bool, from_none], self.disable_secret_masking) + if self.disable_tool_cache is not None: + result["disableToolCache"] = from_union([from_bool, from_none], self.disable_tool_cache) + if self.display_name is not None: + result["displayName"] = from_union([from_str, from_none], self.display_name) + if self.env is not None: + result["env"] = from_union([lambda x: from_dict(from_str, x), from_none], self.env) + if self.events is not None: + result["events"] = from_union([lambda x: from_list(from_str, x), from_none], self.events) + if self.exclude_tools is not None: + result["excludeTools"] = from_union([lambda x: from_list(from_str, x), from_none], self.exclude_tools) + 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.is_default_server is not None: + result["isDefaultServer"] = from_union([from_bool, from_none], self.is_default_server) + if self.notifications is not None: + result["notifications"] = from_union([lambda x: from_list(from_str, x), from_none], self.notifications) + if self.oidc is not None: + result["oidc"] = from_union([from_bool, lambda x: to_class(MCPServerAuthConfigRedirectPort, x), from_none], self.oidc) + if self.safe_for_telemetry is not None: + result["safeForTelemetry"] = from_union([from_bool, lambda x: to_class(MCPSafeForTelemetryFields, x), from_none], self.safe_for_telemetry) + if self.source is not None: + result["source"] = from_union([lambda x: to_enum(McpServerSource, x), from_none], self.source) + if self.source_path is not None: + result["sourcePath"] = from_union([from_str, from_none], self.source_path) + if self.source_plugin is not None: + result["sourcePlugin"] = from_union([from_str, from_none], self.source_plugin) + if self.source_plugin_spec is not None: + result["sourcePluginSpec"] = from_union([from_bool, from_none], self.source_plugin_spec) + if self.source_plugin_version is not None: + result["sourcePluginVersion"] = from_union([from_str, from_none], self.source_plugin_version) + if self.timeout is not None: + result["timeout"] = from_union([from_int, from_none], self.timeout) + if self.tools is not None: + result["tools"] = from_union([lambda x: from_list(from_str, x), from_none], self.tools) + if self.type is not None: + result["type"] = from_union([lambda x: to_enum(MCPServerConfigType, x), from_none], self.type) + if self.headers is not None: + result["headers"] = from_union([lambda x: from_dict(from_str, x), from_none], self.headers) + if self.headers_refresh_ttl_ms is not None: + result["headersRefreshTtlMs"] = from_union([from_int, from_none], self.headers_refresh_ttl_ms) + if self.oauth_client_id is not None: + result["oauthClientId"] = from_union([from_str, from_none], self.oauth_client_id) + if self.oauth_grant_type is not None: + result["oauthGrantType"] = from_union([lambda x: to_enum(MCPGrantType, x), from_none], self.oauth_grant_type) + if self.oauth_public_client is not None: + result["oauthPublicClient"] = from_union([from_bool, from_none], self.oauth_public_client) + if self.url is not None: + result["url"] = from_union([from_str, from_none], self.url) + if self.server_instance is not None: + result["serverInstance"] = self.server_instance + 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 MCPReloadConfig: + """In-process MCP reload configuration.""" + + mcp_servers: dict[str, MCPServerConfig] + active_git_hub_token: str | None = None + cli_enabled_servers: list[str] | None = None + """Server names the CLI enabled for this session via `--enable-mcp-server`.""" + + config_filter: Any = None + disabled_servers: list[str] | None = None + enabled_servers: list[str] | None = None + force_restart: bool | None = None + github_mcp_tool_options: Any = None + github_mcp_user_override: bool | None = None + include_workspace_sources: bool | None = None + mcp3_p_enabled: bool | None = None + secret_store: Any = None + use_cached_tool_snapshots: bool | None = None + + @staticmethod + def from_dict(obj: Any) -> 'MCPReloadConfig': + assert isinstance(obj, dict) + mcp_servers = from_dict(MCPServerConfig.from_dict, obj.get("mcpServers")) + active_git_hub_token = from_union([from_str, from_none], obj.get("activeGitHubToken")) + cli_enabled_servers = from_union([lambda x: from_list(from_str, x), from_none], obj.get("cliEnabledServers")) + config_filter = obj.get("configFilter") + disabled_servers = from_union([lambda x: from_list(from_str, x), from_none], obj.get("disabledServers")) + enabled_servers = from_union([lambda x: from_list(from_str, x), from_none], obj.get("enabledServers")) + force_restart = from_union([from_bool, from_none], obj.get("forceRestart")) + github_mcp_tool_options = obj.get("githubMcpToolOptions") + github_mcp_user_override = from_union([from_bool, from_none], obj.get("githubMcpUserOverride")) + include_workspace_sources = from_union([from_bool, from_none], obj.get("includeWorkspaceSources")) + mcp3_p_enabled = from_union([from_bool, from_none], obj.get("mcp3pEnabled")) + secret_store = obj.get("secretStore") + use_cached_tool_snapshots = from_union([from_bool, from_none], obj.get("useCachedToolSnapshots")) + return MCPReloadConfig(mcp_servers, active_git_hub_token, cli_enabled_servers, config_filter, disabled_servers, enabled_servers, force_restart, github_mcp_tool_options, github_mcp_user_override, include_workspace_sources, mcp3_p_enabled, secret_store, use_cached_tool_snapshots) + + def to_dict(self) -> dict: + result: dict = {} + result["mcpServers"] = from_dict(lambda x: to_class(MCPServerConfig, x), self.mcp_servers) + if self.active_git_hub_token is not None: + result["activeGitHubToken"] = from_union([from_str, from_none], self.active_git_hub_token) + if self.cli_enabled_servers is not None: + result["cliEnabledServers"] = from_union([lambda x: from_list(from_str, x), from_none], self.cli_enabled_servers) + if self.config_filter is not None: + result["configFilter"] = self.config_filter + if self.disabled_servers is not None: + result["disabledServers"] = from_union([lambda x: from_list(from_str, x), from_none], self.disabled_servers) + if self.enabled_servers is not None: + result["enabledServers"] = from_union([lambda x: from_list(from_str, x), from_none], self.enabled_servers) + if self.force_restart is not None: + result["forceRestart"] = from_union([from_bool, from_none], self.force_restart) + if self.github_mcp_tool_options is not None: + result["githubMcpToolOptions"] = self.github_mcp_tool_options + if self.github_mcp_user_override is not None: + result["githubMcpUserOverride"] = from_union([from_bool, from_none], self.github_mcp_user_override) + if self.include_workspace_sources is not None: + result["includeWorkspaceSources"] = from_union([from_bool, from_none], self.include_workspace_sources) + if self.mcp3_p_enabled is not None: + result["mcp3pEnabled"] = from_union([from_bool, from_none], self.mcp3_p_enabled) + if self.secret_store is not None: + result["secretStore"] = self.secret_store + if self.use_cached_tool_snapshots is not None: + result["useCachedToolSnapshots"] = from_union([from_bool, from_none], self.use_cached_tool_snapshots) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MCPResourceAnnotations: @@ -27560,6 +34044,143 @@ def to_dict(self) -> dict: result["nextCursor"] = from_union([from_str, from_none], self.next_cursor) 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 MCPServerConfigMemory: + """In-process MCP server configuration used by embedded SDK clients.""" + + type: MCPServerConfigMemoryType + server_instance: Any = None + """In-process MCP server instance. This value cannot cross a JSON-RPC boundary.""" + + config_warnings: list[str] | None = None + """Configuration warnings recorded while loading the server.""" + + defer_tools: MCPServerConfigDeferTools | None = None + """Controls whether tools can be loaded on demand.""" + + disable_secret_masking: bool | None = None + """Whether secret masking is disabled for calls to this server.""" + + disable_tool_cache: bool | None = None + """Whether persisted tool snapshots are disabled.""" + + display_name: str | None = None + """Optional human-readable server name.""" + + events: list[str] | None = None + """Event types this server receives as Copilot notifications.""" + + exclude_tools: list[str] | None = None + """Tool names excluded after the include filter is applied.""" + + filter_mapping: dict[str, ContentFilterMode] | ContentFilterMode | None = None + """Content filtering mode to apply to this server's tools.""" + + is_default_server: bool | None = None + """Whether this server is a built-in fallback.""" + + notifications: list[str] | None = None + """Copilot notification types this server may send to the host.""" + + oidc: bool | MCPServerAuthConfigRedirectPort | None = None + """Set to `true` to use default OIDC settings.""" + + safe_for_telemetry: bool | MCPSafeForTelemetryFields | None = None + """Telemetry-obfuscation policy for this server's tools.""" + + source: McpServerSource | None = None + """The origin of this server configuration.""" + + source_path: str | None = None + """Source file path recorded while loading the config.""" + + source_plugin: str | None = None + """Plugin that provided this server.""" + + source_plugin_spec: bool | None = None + """Whether the providing plugin uses the Open Plugin Spec.""" + + source_plugin_version: str | None = None + """Version of the plugin that provided this server.""" + + timeout: int | None = None + """Timeout in milliseconds for tool discovery and tool calls.""" + + tools: list[str] | None = None + """Tools to include. Defaults to all tools if not specified.""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPServerConfigMemory': + assert isinstance(obj, dict) + server_instance = obj.get("serverInstance") + type = MCPServerConfigMemoryType(obj.get("type")) + config_warnings = from_union([lambda x: from_list(from_str, x), from_none], obj.get("configWarnings")) + defer_tools = from_union([MCPServerConfigDeferTools, from_none], obj.get("deferTools")) + disable_secret_masking = from_union([from_bool, from_none], obj.get("disableSecretMasking")) + disable_tool_cache = from_union([from_bool, from_none], obj.get("disableToolCache")) + display_name = from_union([from_str, from_none], obj.get("displayName")) + events = from_union([lambda x: from_list(from_str, x), from_none], obj.get("events")) + exclude_tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("excludeTools")) + 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")) + notifications = from_union([lambda x: from_list(from_str, x), from_none], obj.get("notifications")) + oidc = from_union([from_bool, MCPServerAuthConfigRedirectPort.from_dict, from_none], obj.get("oidc")) + safe_for_telemetry = from_union([from_bool, MCPSafeForTelemetryFields.from_dict, from_none], obj.get("safeForTelemetry")) + source = from_union([McpServerSource, from_none], obj.get("source")) + source_path = from_union([from_str, from_none], obj.get("sourcePath")) + source_plugin = from_union([from_str, from_none], obj.get("sourcePlugin")) + source_plugin_spec = from_union([from_bool, from_none], obj.get("sourcePluginSpec")) + source_plugin_version = from_union([from_str, from_none], obj.get("sourcePluginVersion")) + 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 MCPServerConfigMemory(server_instance, type, config_warnings, defer_tools, disable_secret_masking, disable_tool_cache, display_name, events, exclude_tools, filter_mapping, is_default_server, notifications, oidc, safe_for_telemetry, source, source_path, source_plugin, source_plugin_spec, source_plugin_version, timeout, tools) + + def to_dict(self) -> dict: + result: dict = {} + result["serverInstance"] = self.server_instance + result["type"] = to_enum(MCPServerConfigMemoryType, self.type) + if self.config_warnings is not None: + result["configWarnings"] = from_union([lambda x: from_list(from_str, x), from_none], self.config_warnings) + 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_secret_masking is not None: + result["disableSecretMasking"] = from_union([from_bool, from_none], self.disable_secret_masking) + if self.disable_tool_cache is not None: + result["disableToolCache"] = from_union([from_bool, from_none], self.disable_tool_cache) + if self.display_name is not None: + result["displayName"] = from_union([from_str, from_none], self.display_name) + if self.events is not None: + result["events"] = from_union([lambda x: from_list(from_str, x), from_none], self.events) + if self.exclude_tools is not None: + result["excludeTools"] = from_union([lambda x: from_list(from_str, x), from_none], self.exclude_tools) + 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.is_default_server is not None: + result["isDefaultServer"] = from_union([from_bool, from_none], self.is_default_server) + if self.notifications is not None: + result["notifications"] = from_union([lambda x: from_list(from_str, x), from_none], self.notifications) + if self.oidc is not None: + result["oidc"] = from_union([from_bool, lambda x: to_class(MCPServerAuthConfigRedirectPort, x), from_none], self.oidc) + if self.safe_for_telemetry is not None: + result["safeForTelemetry"] = from_union([from_bool, lambda x: to_class(MCPSafeForTelemetryFields, x), from_none], self.safe_for_telemetry) + if self.source is not None: + result["source"] = from_union([lambda x: to_enum(McpServerSource, x), from_none], self.source) + if self.source_path is not None: + result["sourcePath"] = from_union([from_str, from_none], self.source_path) + if self.source_plugin is not None: + result["sourcePlugin"] = from_union([from_str, from_none], self.source_plugin) + if self.source_plugin_spec is not None: + result["sourcePluginSpec"] = from_union([from_bool, from_none], self.source_plugin_spec) + if self.source_plugin_version is not None: + result["sourcePluginVersion"] = from_union([from_str, from_none], self.source_plugin_version) + if self.timeout is not None: + result["timeout"] = from_union([from_int, from_none], self.timeout) + if self.tools is not None: + result["tools"] = from_union([lambda x: from_list(from_str, x), from_none], self.tools) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MetadataContextInfoRequest: @@ -27665,6 +34286,14 @@ 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)""" + + info_messages: list[ModelMessage] | None = None + """Informational notices the service published for this model, such as an upcoming change or + a recommended alternative. Present only when the service published at least one notice. + Hosts should surface these without implying anything is wrong with the model. + """ model_picker_category: ModelPickerCategory | None = None """Model capability category for grouping in the model picker""" @@ -27674,9 +34303,25 @@ class Model: policy: ModelPolicy | None = None """Policy state (if applicable)""" + supported_context_tiers: list[str] | None = None + """Context-window tiers this model offers, when the provider advertises them independently + of tiered token pricing. Copilot models carry their tiers in `billing.tokenPrices`; a + provider that has no pricing to publish (an agent host reached over AHP, for example) + declares them here instead, so the model picker can still offer the tier toggle. + """ supported_reasoning_efforts: list[str] | None = None """Supported reasoning effort levels (only present if model supports reasoning effort)""" + warning_messages: list[ModelMessage] | None = None + """Warnings the service published for this model, such as a deprecated client version. + Present only when the service published at least one warning. The model remains usable; + hosts should surface these as advisory rather than blocking. + """ + warning_text: ModelWarningText | None = None + """Warning text the service requires hosts to surface for this model. Present only when the + service published at least one warning. + """ + @staticmethod def from_dict(obj: Any) -> 'Model': assert isinstance(obj, dict) @@ -27684,11 +34329,16 @@ 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")) + info_messages = from_union([lambda x: from_list(ModelMessage.from_dict, x), from_none], obj.get("infoMessages")) 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_context_tiers = from_union([lambda x: from_list(from_str, x), from_none], obj.get("supportedContextTiers")) supported_reasoning_efforts = from_union([lambda x: from_list(from_str, x), from_none], obj.get("supportedReasoningEfforts")) - return Model(capabilities, id, name, billing, model_picker_category, model_picker_price_category, policy, supported_reasoning_efforts) + warning_messages = from_union([lambda x: from_list(ModelMessage.from_dict, x), from_none], obj.get("warningMessages")) + warning_text = from_union([ModelWarningText.from_dict, from_none], obj.get("warningText")) + return Model(capabilities, id, name, billing, default_reasoning_effort, info_messages, model_picker_category, model_picker_price_category, policy, supported_context_tiers, supported_reasoning_efforts, warning_messages, warning_text) def to_dict(self) -> dict: result: dict = {} @@ -27697,14 +34347,88 @@ 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.info_messages is not None: + result["infoMessages"] = from_union([lambda x: from_list(lambda x: to_class(ModelMessage, x), x), from_none], self.info_messages) 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: result["modelPickerPriceCategory"] = from_union([lambda x: to_enum(ModelPickerPriceCategory, x), from_none], self.model_picker_price_category) if self.policy is not None: result["policy"] = from_union([lambda x: to_class(ModelPolicy, x), from_none], self.policy) + if self.supported_context_tiers is not None: + result["supportedContextTiers"] = from_union([lambda x: from_list(from_str, x), from_none], self.supported_context_tiers) if self.supported_reasoning_efforts is not None: result["supportedReasoningEfforts"] = from_union([lambda x: from_list(from_str, x), from_none], self.supported_reasoning_efforts) + if self.warning_messages is not None: + result["warningMessages"] = from_union([lambda x: from_list(lambda x: to_class(ModelMessage, x), x), from_none], self.warning_messages) + if self.warning_text is not None: + result["warningText"] = from_union([lambda x: to_class(ModelWarningText, x), from_none], self.warning_text) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ModelApplyStartupOverlayRequest: + """Managed, repository, and CLI model overrides to overlay onto the session at startup.""" + + cli_model: str | None = None + """Model explicitly selected by the CLI, when provided.""" + + deferred_resume: bool | None = None + """Whether the overlay is being applied while resuming a deferred session.""" + + device_managed_model: str | None = None + """Model required by device-managed policy, when configured.""" + + policy_helper_model: str | None = None + """Startup default model from the enterprise policy helper, when configured. Weakest of the + managed sources: it applies only when neither device nor server policy names a model, and + an explicit user selection still wins. + """ + repo_context_tier: str | None = None + """Context tier selected by repository settings, when configured.""" + + repo_model: str | None = None + """Model selected by repository settings, when configured.""" + + repo_reasoning_effort: str | None = None + """Reasoning effort selected by repository settings, when configured.""" + + server_managed_model: str | None = None + """Model required by server-managed policy, when configured.""" + + @staticmethod + def from_dict(obj: Any) -> 'ModelApplyStartupOverlayRequest': + assert isinstance(obj, dict) + cli_model = from_union([from_str, from_none], obj.get("cliModel")) + deferred_resume = from_union([from_bool, from_none], obj.get("deferredResume")) + device_managed_model = from_union([from_str, from_none], obj.get("deviceManagedModel")) + policy_helper_model = from_union([from_str, from_none], obj.get("policyHelperModel")) + repo_context_tier = from_union([from_str, from_none], obj.get("repoContextTier")) + repo_model = from_union([from_str, from_none], obj.get("repoModel")) + repo_reasoning_effort = from_union([from_str, from_none], obj.get("repoReasoningEffort")) + server_managed_model = from_union([from_str, from_none], obj.get("serverManagedModel")) + return ModelApplyStartupOverlayRequest(cli_model, deferred_resume, device_managed_model, policy_helper_model, repo_context_tier, repo_model, repo_reasoning_effort, server_managed_model) + + def to_dict(self) -> dict: + result: dict = {} + if self.cli_model is not None: + result["cliModel"] = from_union([from_str, from_none], self.cli_model) + if self.deferred_resume is not None: + result["deferredResume"] = from_union([from_bool, from_none], self.deferred_resume) + if self.device_managed_model is not None: + result["deviceManagedModel"] = from_union([from_str, from_none], self.device_managed_model) + if self.policy_helper_model is not None: + result["policyHelperModel"] = from_union([from_str, from_none], self.policy_helper_model) + if self.repo_context_tier is not None: + result["repoContextTier"] = from_union([from_str, from_none], self.repo_context_tier) + if self.repo_model is not None: + result["repoModel"] = from_union([from_str, from_none], self.repo_model) + if self.repo_reasoning_effort is not None: + result["repoReasoningEffort"] = from_union([from_str, from_none], self.repo_reasoning_effort) + if self.server_managed_model is not None: + result["serverManagedModel"] = from_union([from_str, from_none], self.server_managed_model) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -27738,6 +34462,10 @@ class ModelSwitchToRequest: `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. """ + compaction_decision: str | None = None + """Explicit response to a model-switch compaction preflight. Omit to request a confirmation + projection when compaction is necessary. + """ context_tier: ContextTier | None = None """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. @@ -27752,6 +34480,12 @@ class ModelSwitchToRequest: model_capabilities: ModelCapabilitiesOverride | None = None """Override individual model capabilities resolved by the runtime""" + model_change_scope: str | None = None + """Settings scope used when persisting the selected model.""" + + picker_persistence: ModelPickerPersistenceRequest | None = None + """Optional settings context and explicit-override flags used to persist a picker selection.""" + reasoning_effort: str | None = None """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 @@ -27760,6 +34494,19 @@ class ModelSwitchToRequest: reasoning_summary: ReasoningSummary | None = None """Reasoning summary mode to request for supported model clients""" + repo_scope: str | None = None + """Optional repository settings scope to persist after the switch commits.""" + + require_available: bool | None = None + """Require the target to be currently available and enabled before applying the switch.""" + + run_compaction_preflight: bool | None = None + """When true, evaluate context-window compaction policy before applying the switch.""" + + source: ModelChangeSource | None = None + """Origin to record on the effective `session.model_change` event for trusted in-process + calls. Transport SDK calls are always recorded as `sdk`, regardless of this value. + """ verbosity: Verbosity | None = None """Output verbosity level to request for supported models""" @@ -27767,127 +34514,190 @@ class ModelSwitchToRequest: def from_dict(obj: Any) -> 'ModelSwitchToRequest': assert isinstance(obj, dict) model_id = from_str(obj.get("modelId")) + compaction_decision = from_union([from_str, from_none], obj.get("compactionDecision")) 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")) + model_change_scope = from_union([from_str, from_none], obj.get("modelChangeScope")) + picker_persistence = from_union([ModelPickerPersistenceRequest.from_dict, from_none], obj.get("pickerPersistence")) reasoning_effort = from_union([from_str, from_none], obj.get("reasoningEffort")) reasoning_summary = from_union([ReasoningSummary, from_none], obj.get("reasoningSummary")) + repo_scope = from_union([from_str, from_none], obj.get("repoScope")) + require_available = from_union([from_bool, from_none], obj.get("requireAvailable")) + run_compaction_preflight = from_union([from_bool, from_none], obj.get("runCompactionPreflight")) + source = from_union([ModelChangeSource, from_none], obj.get("source")) verbosity = from_union([Verbosity, from_none], obj.get("verbosity")) - return ModelSwitchToRequest(model_id, context_tier, defer_if_model_change_queued, model_capabilities, reasoning_effort, reasoning_summary, verbosity) + return ModelSwitchToRequest(model_id, compaction_decision, context_tier, defer_if_model_change_queued, model_capabilities, model_change_scope, picker_persistence, reasoning_effort, reasoning_summary, repo_scope, require_available, run_compaction_preflight, source, verbosity) def to_dict(self) -> dict: result: dict = {} result["modelId"] = from_str(self.model_id) + if self.compaction_decision is not None: + result["compactionDecision"] = from_union([from_str, from_none], self.compaction_decision) 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.model_change_scope is not None: + result["modelChangeScope"] = from_union([from_str, from_none], self.model_change_scope) + if self.picker_persistence is not None: + result["pickerPersistence"] = from_union([lambda x: to_class(ModelPickerPersistenceRequest, x), from_none], self.picker_persistence) if self.reasoning_effort is not None: result["reasoningEffort"] = from_union([from_str, from_none], self.reasoning_effort) if self.reasoning_summary is not None: result["reasoningSummary"] = from_union([lambda x: to_enum(ReasoningSummary, x), from_none], self.reasoning_summary) + if self.repo_scope is not None: + result["repoScope"] = from_union([from_str, from_none], self.repo_scope) + if self.require_available is not None: + result["requireAvailable"] = from_union([from_bool, from_none], self.require_available) + if self.run_compaction_preflight is not None: + result["runCompactionPreflight"] = from_union([from_bool, from_none], self.run_compaction_preflight) + if self.source is not None: + result["source"] = from_union([lambda x: to_enum(ModelChangeSource, x), from_none], self.source) if self.verbosity is not None: result["verbosity"] = from_union([lambda x: to_enum(Verbosity, x), from_none], self.verbosity) return result # Experimental: this type is part of an experimental API and may change or be removed. -class PermissionsSetAAllSource(Enum): - """Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers.""" +class PermissionSource(Enum): + """Optional source for permission-mode telemetry. Defaults to `rpc` when omitted for SDK + callers. + Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers. + """ AUTOPILOT_CONFIRMATION = "autopilot_confirmation" CLI_FLAG = "cli_flag" RPC = "rpc" SLASH_COMMAND = "slash_command" + USER_SETTING = "user_setting" # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class PermissionsSetAllowAllRequest: - """Allow-all mode to apply for the session.""" +class PermissionsSetApproveAllRequest: + """Allow-all toggle for tool permission requests, with an optional telemetry source.""" - enabled: bool | None = None - """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"`. - """ - mode: PermissionsAllowAllMode | None = None - """Allow-all mode to apply. `on` enables full allow-all; `auto` enables advisory LLM - auto-approval; `off` disables both. - """ - 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 resolves a default judge - model: `gpt-5.5` for CAPI sessions and the session's active model for BYOK sessions. - """ - source: PermissionsSetAAllSource | None = None + enabled: bool + """Whether to auto-approve all tool permission requests""" + + source: PermissionSource | None = None """Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers.""" @staticmethod - def from_dict(obj: Any) -> 'PermissionsSetAllowAllRequest': + def from_dict(obj: Any) -> 'PermissionsSetApproveAllRequest': assert isinstance(obj, dict) - enabled = from_union([from_bool, from_none], obj.get("enabled")) - mode = from_union([PermissionsAllowAllMode, from_none], obj.get("mode")) - model = from_union([from_str, from_none], obj.get("model")) - source = from_union([PermissionsSetAAllSource, from_none], obj.get("source")) - return PermissionsSetAllowAllRequest(enabled, mode, model, source) + enabled = from_bool(obj.get("enabled")) + source = from_union([PermissionSource, from_none], obj.get("source")) + return PermissionsSetApproveAllRequest(enabled, source) def to_dict(self) -> dict: result: dict = {} - if self.enabled is not None: - result["enabled"] = from_union([from_bool, from_none], self.enabled) - if self.mode is not None: - result["mode"] = from_union([lambda x: to_enum(PermissionsAllowAllMode, x), from_none], self.mode) - if self.model is not None: - result["model"] = from_union([from_str, from_none], self.model) + result["enabled"] = from_bool(self.enabled) if self.source is not None: - result["source"] = from_union([lambda x: to_enum(PermissionsSetAAllSource, x), from_none], self.source) + result["source"] = from_union([lambda x: to_enum(PermissionSource, x), from_none], self.source) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class PermissionsSetApproveAllRequest: - """Allow-all toggle for tool permission requests, with an optional telemetry source.""" +class PermissionsSetModeRequest: + """Permission mode to apply for the session.""" - enabled: bool - """Whether to auto-approve all tool permission requests""" + mode: PermissionMode + """Permission mode to apply""" - source: PermissionsSetAAllSource | None = None - """Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers.""" + assisted_approval_model: str | None = None + """Optional judge model id for assisted mode. When omitted, the session resolves the + provider default: `gpt-5.5` for CAPI sessions and the active session model for BYOK + sessions. + """ + source: PermissionSource | None = None + """Optional source for permission-mode telemetry. Defaults to `rpc` when omitted for SDK + callers. + """ @staticmethod - def from_dict(obj: Any) -> 'PermissionsSetApproveAllRequest': + def from_dict(obj: Any) -> 'PermissionsSetModeRequest': assert isinstance(obj, dict) - enabled = from_bool(obj.get("enabled")) - source = from_union([PermissionsSetAAllSource, from_none], obj.get("source")) - return PermissionsSetApproveAllRequest(enabled, source) + mode = PermissionMode(obj.get("mode")) + assisted_approval_model = from_union([from_str, from_none], obj.get("assistedApprovalModel")) + source = from_union([PermissionSource, from_none], obj.get("source")) + return PermissionsSetModeRequest(mode, assisted_approval_model, source) def to_dict(self) -> dict: result: dict = {} - result["enabled"] = from_bool(self.enabled) + result["mode"] = to_enum(PermissionMode, self.mode) + if self.assisted_approval_model is not None: + result["assistedApprovalModel"] = from_union([from_str, from_none], self.assisted_approval_model) if self.source is not None: - result["source"] = from_union([lambda x: to_enum(PermissionsSetAAllSource, x), from_none], self.source) + result["source"] = from_union([lambda x: to_enum(PermissionSource, x), from_none], self.source) 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.""" - - unsubscribe: Any - """In-process unsubscribe function (CLI-only optimization). Marked internal: replaced by an - explicit `extensions.unregister` RPC in the SDK migration. +class ProtocolExternalToolDefinition: + """Serializable definition of a caller-implemented tool whose execution is handled over the + SDK connection. """ + description: str + """Model-visible explanation of what the tool does.""" + + name: str + """Unique model-visible tool name.""" + + defer: MCPServerConfigDeferTools | None = None + """Tool-loading deferral policy.""" + + is_terminal: bool | None = None + """Whether the tool executes commands in a terminal.""" + + metadata: dict[str, Any] | None = None + """Optional caller-defined metadata associated with the tool.""" + + overrides_built_in_tool: bool | None = None + """Whether this definition replaces a built-in tool with the same name.""" + + parameters: dict[str, Any] | None = None + """JSON Schema describing the tool's input arguments.""" + + skip_permission: bool | None = None + """Whether execution bypasses the normal tool permission prompt.""" + + title: str | None = None + """Optional human-readable display title.""" @staticmethod - def from_dict(obj: Any) -> '_RegisterExtensionToolsResult': + def from_dict(obj: Any) -> 'ProtocolExternalToolDefinition': assert isinstance(obj, dict) - unsubscribe = obj.get("unsubscribe") - return _RegisterExtensionToolsResult(unsubscribe) + description = from_str(obj.get("description")) + name = from_str(obj.get("name")) + defer = from_union([MCPServerConfigDeferTools, from_none], obj.get("defer")) + is_terminal = from_union([from_bool, from_none], obj.get("isTerminal")) + metadata = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("metadata")) + overrides_built_in_tool = from_union([from_bool, from_none], obj.get("overridesBuiltInTool")) + parameters = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("parameters")) + skip_permission = from_union([from_bool, from_none], obj.get("skipPermission")) + title = from_union([from_str, from_none], obj.get("title")) + return ProtocolExternalToolDefinition(description, name, defer, is_terminal, metadata, overrides_built_in_tool, parameters, skip_permission, title) def to_dict(self) -> dict: result: dict = {} - result["unsubscribe"] = self.unsubscribe + result["description"] = from_str(self.description) + result["name"] = from_str(self.name) + if self.defer is not None: + result["defer"] = from_union([lambda x: to_enum(MCPServerConfigDeferTools, x), from_none], self.defer) + if self.is_terminal is not None: + result["isTerminal"] = from_union([from_bool, from_none], self.is_terminal) + if self.metadata is not None: + result["metadata"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.metadata) + if self.overrides_built_in_tool is not None: + result["overridesBuiltInTool"] = from_union([from_bool, from_none], self.overrides_built_in_tool) + if self.parameters is not None: + result["parameters"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.parameters) + if self.skip_permission is not None: + result["skipPermission"] = from_union([from_bool, from_none], self.skip_permission) + if self.title is not None: + result["title"] = from_union([from_str, from_none], self.title) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -27959,6 +34769,8 @@ class SessionLimitPredictionResult: include an explicit reason. """ kind: SessionLimitPredictionResultKind + """Prediction result variant discriminator.""" + prediction: SessionLimitPredictionDetails | None = None """Predicted session limit details.""" @@ -28002,6 +34814,142 @@ def to_dict(self) -> dict: 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 SessionSettingsValidationSnapshot: + """Redacted validation and memory-tool settings. + + Redacted validation and memory-tool settings for a session. + """ + advisory_enabled: bool | None = None + """Whether advisory validation is enabled.""" + + codeql_enabled: bool | None = None + """Whether CodeQL validation is enabled.""" + + code_review_enabled: bool | None = None + """Whether code-review validation is enabled.""" + + code_review_model: str | None = None + """Model used for code-review validation.""" + + dependabot_timeout: float | None = None + """Dependabot validation timeout budget in seconds.""" + + memory_store_enabled: bool | None = None + """Whether the memory-store tool is enabled.""" + + memory_vote_enabled: bool | None = None + """Whether the memory-vote tool is enabled.""" + + secret_scanning_enabled: bool | None = None + """Whether secret-scanning validation is enabled.""" + + timeout: float | None = None + """General validation timeout budget in seconds.""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionSettingsValidationSnapshot': + assert isinstance(obj, dict) + advisory_enabled = from_union([from_bool, from_none], obj.get("advisoryEnabled")) + codeql_enabled = from_union([from_bool, from_none], obj.get("codeqlEnabled")) + code_review_enabled = from_union([from_bool, from_none], obj.get("codeReviewEnabled")) + code_review_model = from_union([from_str, from_none], obj.get("codeReviewModel")) + dependabot_timeout = from_union([from_float, from_none], obj.get("dependabotTimeout")) + memory_store_enabled = from_union([from_bool, from_none], obj.get("memoryStoreEnabled")) + memory_vote_enabled = from_union([from_bool, from_none], obj.get("memoryVoteEnabled")) + secret_scanning_enabled = from_union([from_bool, from_none], obj.get("secretScanningEnabled")) + timeout = from_union([from_float, from_none], obj.get("timeout")) + return SessionSettingsValidationSnapshot(advisory_enabled, codeql_enabled, code_review_enabled, code_review_model, dependabot_timeout, memory_store_enabled, memory_vote_enabled, secret_scanning_enabled, timeout) + + def to_dict(self) -> dict: + result: dict = {} + if self.advisory_enabled is not None: + result["advisoryEnabled"] = from_union([from_bool, from_none], self.advisory_enabled) + if self.codeql_enabled is not None: + result["codeqlEnabled"] = from_union([from_bool, from_none], self.codeql_enabled) + if self.code_review_enabled is not None: + result["codeReviewEnabled"] = from_union([from_bool, from_none], self.code_review_enabled) + if self.code_review_model is not None: + result["codeReviewModel"] = from_union([from_str, from_none], self.code_review_model) + if self.dependabot_timeout is not None: + result["dependabotTimeout"] = from_union([to_float, from_none], self.dependabot_timeout) + if self.memory_store_enabled is not None: + result["memoryStoreEnabled"] = from_union([from_bool, from_none], self.memory_store_enabled) + if self.memory_vote_enabled is not None: + result["memoryVoteEnabled"] = from_union([from_bool, from_none], self.memory_vote_enabled) + if self.secret_scanning_enabled is not None: + result["secretScanningEnabled"] = from_union([from_bool, from_none], self.secret_scanning_enabled) + if self.timeout is not None: + result["timeout"] = from_union([to_float, from_none], self.timeout) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionSettingsSnapshot: + """Redacted, serializable view of session runtime settings for SDK boundary consumers. + Secrets and raw feature flags are intentionally excluded. + """ + job: SessionSettingsJobSnapshot + """Redacted job settings.""" + + model: SessionSettingsModelSnapshot + """Redacted model routing settings.""" + + online_evaluation: SessionSettingsOnlineEvaluationSnapshot + """Online-evaluation settings safe for SDK consumers.""" + + repo: SessionSettingsRepoSnapshot + """Redacted repository and host settings.""" + + validation: SessionSettingsValidationSnapshot + """Redacted validation and memory-tool settings.""" + + client_name: str | None = None + """Name of the SDK client that created the session.""" + + start_time_ms: float | None = None + """Session start time as Unix epoch milliseconds.""" + + timeout_ms: float | None = None + """Session timeout in milliseconds.""" + + version: str | None = None + """Agent runtime version selector copied from the session settings, such as `latest` or a + runtime release identifier. + """ + + @staticmethod + def from_dict(obj: Any) -> 'SessionSettingsSnapshot': + assert isinstance(obj, dict) + job = SessionSettingsJobSnapshot.from_dict(obj.get("job")) + model = SessionSettingsModelSnapshot.from_dict(obj.get("model")) + online_evaluation = SessionSettingsOnlineEvaluationSnapshot.from_dict(obj.get("onlineEvaluation")) + repo = SessionSettingsRepoSnapshot.from_dict(obj.get("repo")) + validation = SessionSettingsValidationSnapshot.from_dict(obj.get("validation")) + client_name = from_union([from_str, from_none], obj.get("clientName")) + start_time_ms = from_union([from_float, from_none], obj.get("startTimeMs")) + timeout_ms = from_union([from_float, from_none], obj.get("timeoutMs")) + version = from_union([from_str, from_none], obj.get("version")) + return SessionSettingsSnapshot(job, model, online_evaluation, repo, validation, client_name, start_time_ms, timeout_ms, version) + + def to_dict(self) -> dict: + result: dict = {} + result["job"] = to_class(SessionSettingsJobSnapshot, self.job) + result["model"] = to_class(SessionSettingsModelSnapshot, self.model) + result["onlineEvaluation"] = to_class(SessionSettingsOnlineEvaluationSnapshot, self.online_evaluation) + result["repo"] = to_class(SessionSettingsRepoSnapshot, self.repo) + result["validation"] = to_class(SessionSettingsValidationSnapshot, self.validation) + if self.client_name is not None: + result["clientName"] = from_union([from_str, from_none], self.client_name) + if self.start_time_ms is not None: + result["startTimeMs"] = from_union([to_float, from_none], self.start_time_ms) + if self.timeout_ms is not None: + result["timeoutMs"] = from_union([to_float, from_none], self.timeout_ms) + if self.version is not None: + result["version"] = from_union([from_str, from_none], self.version) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionsOpenCloud: @@ -28012,10 +34960,8 @@ class SessionsOpenCloud: # Internal: this field is an internal SDK API and is not part of the public surface. on_task_created: Any = None - """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. + """In-process callback invoked when the cloud task is created, before connection. Internal + because function references cannot cross the JSON-RPC boundary. """ options: SessionOpenOptions | None = None """Session options for cloud session creation.""" @@ -28112,6 +35058,164 @@ def to_dict(self) -> dict: result["taskType"] = from_union([lambda x: to_enum(TaskType, x), from_none], self.task_type) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SettableTokenAuthInfo: + """Token authentication accepted by session.gitHubAuth.setCredentials.""" + + host: str + """Authentication host.""" + + token: str + """The token value itself. Treat as a secret.""" + + type: ClassVar[str] = "token" + """SDK-side token authentication; the host configured the token directly via the SDK.""" + + copilot_user: CopilotUserResponse | None = None + """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. + """ + + @staticmethod + def from_dict(obj: Any) -> 'SettableTokenAuthInfo': + assert isinstance(obj, dict) + host = from_str(obj.get("host")) + token = from_str(obj.get("token")) + copilot_user = from_union([CopilotUserResponse.from_dict, from_none], obj.get("copilotUser")) + return SettableTokenAuthInfo(host, token, copilot_user) + + def to_dict(self) -> dict: + result: dict = {} + result["host"] = from_str(self.host) + result["token"] = from_str(self.token) + result["type"] = self.type + if self.copilot_user is not None: + result["copilotUser"] = from_union([lambda x: to_class(CopilotUserResponse, x), from_none], self.copilot_user) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SlashCommandModelPickerDialog: + """Dialog the host should display.""" + + kind: SlashCommandModelPickerDialogKind + """Discriminator for a model-picker dialog.""" + + model_to_enable: str | None = None + """Model that should be enabled before it can be selected.""" + + scope: str | None = None + """Settings scope the picker should modify.""" + + target: str | None = None + """Model-selection target represented by the picker.""" + + @staticmethod + def from_dict(obj: Any) -> 'SlashCommandModelPickerDialog': + assert isinstance(obj, dict) + kind = SlashCommandModelPickerDialogKind(obj.get("kind")) + model_to_enable = from_union([from_str, from_none], obj.get("modelToEnable")) + scope = from_union([from_str, from_none], obj.get("scope")) + target = from_union([from_str, from_none], obj.get("target")) + return SlashCommandModelPickerDialog(kind, model_to_enable, scope, target) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = to_enum(SlashCommandModelPickerDialogKind, self.kind) + if self.model_to_enable is not None: + result["modelToEnable"] = from_union([from_str, from_none], self.model_to_enable) + if self.scope is not None: + result["scope"] = from_union([from_str, from_none], self.scope) + if self.target is not None: + result["target"] = from_union([from_str, from_none], self.target) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SlashCommandSetModelResult: + kind: ClassVar[str] = "set-model" + """Discriminator for a set-model result.""" + + model: str + """Model selected by the command.""" + + reasoning_effort: str | None = None + """Reasoning effort selected for the model.""" + + repo_scope: str | None = None + """Repository settings scope modified by the command.""" + + revert_on_cancel: dict[str, Any] | None = None + """User-settings snapshot to restore if the host cancels the model switch.""" + + runtime_settings_changed: bool | None = None + """Whether command execution changed persisted runtime settings.""" + + scope: str | None = None + """Settings scope modified by the command.""" + + warning: str | None = None + """User-facing warning produced while selecting the model.""" + + @staticmethod + def from_dict(obj: Any) -> 'SlashCommandSetModelResult': + assert isinstance(obj, dict) + model = from_str(obj.get("model")) + reasoning_effort = from_union([from_str, from_none], obj.get("reasoningEffort")) + repo_scope = from_union([from_str, from_none], obj.get("repoScope")) + revert_on_cancel = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("revertOnCancel")) + runtime_settings_changed = from_union([from_bool, from_none], obj.get("runtimeSettingsChanged")) + scope = from_union([from_str, from_none], obj.get("scope")) + warning = from_union([from_str, from_none], obj.get("warning")) + return SlashCommandSetModelResult(model, reasoning_effort, repo_scope, revert_on_cancel, runtime_settings_changed, scope, warning) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = self.kind + result["model"] = from_str(self.model) + if self.reasoning_effort is not None: + result["reasoningEffort"] = from_union([from_str, from_none], self.reasoning_effort) + if self.repo_scope is not None: + result["repoScope"] = from_union([from_str, from_none], self.repo_scope) + if self.revert_on_cancel is not None: + result["revertOnCancel"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.revert_on_cancel) + if self.runtime_settings_changed is not None: + result["runtimeSettingsChanged"] = from_union([from_bool, from_none], self.runtime_settings_changed) + if self.scope is not None: + result["scope"] = from_union([from_str, from_none], self.scope) + if self.warning is not None: + result["warning"] = from_union([from_str, from_none], self.warning) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SlashCommandShowDialogResult: + dialog: SlashCommandModelPickerDialog + """Dialog the host should display.""" + + kind: ClassVar[str] = "show-dialog" + """Discriminator for a show-dialog result.""" + + runtime_settings_changed: bool | None = None + """Whether command execution changed persisted runtime settings.""" + + @staticmethod + def from_dict(obj: Any) -> 'SlashCommandShowDialogResult': + assert isinstance(obj, dict) + dialog = SlashCommandModelPickerDialog.from_dict(obj.get("dialog")) + runtime_settings_changed = from_union([from_bool, from_none], obj.get("runtimeSettingsChanged")) + return SlashCommandShowDialogResult(dialog, runtime_settings_changed) + + def to_dict(self) -> dict: + result: dict = {} + result["dialog"] = to_class(SlashCommandModelPickerDialog, self.dialog) + result["kind"] = self.kind + if self.runtime_settings_changed is not None: + result["runtimeSettingsChanged"] = from_union([from_bool, from_none], self.runtime_settings_changed) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SubagentSettingsEntry: @@ -28126,13 +35230,17 @@ class SubagentSettingsEntry: model: str | None = None """Model override for matching subagents""" + model_policy: AgentModelPolicy | None = None + """Whether the configured model strategy is preferred or required""" + @staticmethod def from_dict(obj: Any) -> 'SubagentSettingsEntry': assert isinstance(obj, dict) context_tier = from_union([SubagentSettingsEntryContextTier, from_none], obj.get("contextTier")) effort_level = from_union([from_str, from_none], obj.get("effortLevel")) model = from_union([from_str, from_none], obj.get("model")) - return SubagentSettingsEntry(context_tier, effort_level, model) + model_policy = from_union([AgentModelPolicy, from_none], obj.get("modelPolicy")) + return SubagentSettingsEntry(context_tier, effort_level, model, model_policy) def to_dict(self) -> dict: result: dict = {} @@ -28142,6 +35250,8 @@ def to_dict(self) -> dict: result["effortLevel"] = from_union([from_str, from_none], self.effort_level) if self.model is not None: result["model"] = from_union([from_str, from_none], self.model) + if self.model_policy is not None: + result["modelPolicy"] = from_union([lambda x: to_enum(AgentModelPolicy, x), from_none], self.model_policy) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -28186,8 +35296,8 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class TokenAuthInfo: - """Authentication-info variant for SDK-configured token authentication, carrying host and - the secret token value. + """Authentication-info input variant for SDK-configured token authentication, carrying host + and the secret token value. """ host: str """Authentication host.""" @@ -28203,6 +35313,8 @@ class TokenAuthInfo: GitHub API `/copilot_internal/v2/token` user response shape — the runtime trusts this verbatim and does not re-fetch when set. """ + registration_id: str | None = None + """Opaque native GitHub credential registration backing this token identity, when applicable.""" @staticmethod def from_dict(obj: Any) -> 'TokenAuthInfo': @@ -28210,7 +35322,8 @@ def from_dict(obj: Any) -> 'TokenAuthInfo': host = from_str(obj.get("host")) token = from_str(obj.get("token")) copilot_user = from_union([CopilotUserResponse.from_dict, from_none], obj.get("copilotUser")) - return TokenAuthInfo(host, token, copilot_user) + registration_id = from_union([from_str, from_none], obj.get("registrationId")) + return TokenAuthInfo(host, token, copilot_user, registration_id) def to_dict(self) -> dict: result: dict = {} @@ -28219,6 +35332,62 @@ def to_dict(self) -> dict: result["type"] = self.type if self.copilot_user is not None: result["copilotUser"] = from_union([lambda x: to_class(CopilotUserResponse, x), from_none], self.copilot_user) + if self.registration_id is not None: + result["registrationId"] = from_union([from_str, from_none], self.registration_id) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class TokenProviderAuthInfo: + """Authentication-info variant backed by an SDK GitHub token callback. It carries routing + metadata but never a plaintext token. + """ + host: str + """Authentication host.""" + + registration_id: str + """Opaque SDK callback registration identifier.""" + + type: ClassVar[str] = "token-provider" + """SDK callback-backed GitHub token authentication.""" + + copilot_user: CopilotUserResponse | None = None + """Snapshot of the authenticated user's Copilot subscription info, if known.""" + + @staticmethod + def from_dict(obj: Any) -> 'TokenProviderAuthInfo': + assert isinstance(obj, dict) + host = from_str(obj.get("host")) + registration_id = from_str(obj.get("registrationId")) + copilot_user = from_union([CopilotUserResponse.from_dict, from_none], obj.get("copilotUser")) + return TokenProviderAuthInfo(host, registration_id, copilot_user) + + def to_dict(self) -> dict: + result: dict = {} + result["host"] = from_str(self.host) + result["registrationId"] = from_str(self.registration_id) + result["type"] = self.type + if self.copilot_user is not None: + result["copilotUser"] = from_union([lambda x: to_class(CopilotUserResponse, x), from_none], self.copilot_user) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ToolsGetBuiltinDescriptorsResult: + """Rust-owned built-in tool descriptors for the session.""" + + tools: list[BuiltinToolDescriptor] + """Built-in tool descriptors materialized for the session.""" + + @staticmethod + def from_dict(obj: Any) -> 'ToolsGetBuiltinDescriptorsResult': + assert isinstance(obj, dict) + tools = from_list(BuiltinToolDescriptor.from_dict, obj.get("tools")) + return ToolsGetBuiltinDescriptorsResult(tools) + + def to_dict(self) -> dict: + result: dict = {} + result["tools"] = from_list(lambda x: to_class(BuiltinToolDescriptor, x), self.tools) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -28242,40 +35411,22 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class UIEphemeralQueryRequest: - """Transient question to answer without adding it to conversation history.""" - - question: str - """Question to answer from the current conversation context.""" - - # Internal: this field is an internal SDK API and is not part of the public surface. - abort_signal: Any = None - """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: this field is an internal SDK API and is not part of the public surface. - on_chunk: Any = None - """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. +class ToolsSetRequest: + """Complete externally implemented tool list for the calling connection. An empty list + removes every tool previously supplied by that connection. """ + tools: list[ProtocolExternalToolDefinition] + """Complete replacement list for the calling connection.""" @staticmethod - def from_dict(obj: Any) -> 'UIEphemeralQueryRequest': + def from_dict(obj: Any) -> 'ToolsSetRequest': assert isinstance(obj, dict) - question = from_str(obj.get("question")) - abort_signal = obj.get("abortSignal") - on_chunk = obj.get("onChunk") - return UIEphemeralQueryRequest(question, abort_signal, on_chunk) + tools = from_list(ProtocolExternalToolDefinition.from_dict, obj.get("tools")) + return ToolsSetRequest(tools) def to_dict(self) -> dict: result: dict = {} - result["question"] = from_str(self.question) - if self.abort_signal is not None: - result["abortSignal"] = self.abort_signal - if self.on_chunk is not None: - result["onChunk"] = self.on_chunk + result["tools"] = from_list(lambda x: to_class(ProtocolExternalToolDefinition, x), self.tools) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -28382,13 +35533,21 @@ class RPC: 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_identity: AuthIdentity auth_info: AuthInfo auth_info_type: AuthInfoType + auth_validation_error: AuthValidationError + auth_validation_errors: list[AuthValidationError] built_in_model_catalog: BuiltInModelCatalog built_in_model_catalog_entry: BuiltInModelCatalogEntry + builtin_tool_descriptor: BuiltinToolDescriptor + builtin_tool_format: BuiltinToolFormat + builtin_tool_format_type: BuiltinToolFormatType + builtin_tool_input_schema: BuiltinToolInputSchema + builtin_tool_input_schema_type: BuiltinToolInputSchemaType + builtin_tool_safe_for_telemetry: bool | BuiltinToolSafeTelemetryFields + builtin_tool_safe_telemetry_fields: BuiltinToolSafeTelemetryFields cancel_user_requested_shell_command_result: CancelUserRequestedShellCommandResult canvas_action: CanvasAction canvas_action_invoke_request: CanvasActionInvokeRequest @@ -28404,11 +35563,63 @@ class RPC: canvas_provider_invoke_action_request: CanvasProviderInvokeActionRequest canvas_provider_open_request: CanvasProviderOpenRequest canvas_provider_open_result: CanvasProviderOpenResult + canvas_provider_register_request: CanvasProviderRegisterRequest + canvas_provider_unregister_request: CanvasProviderUnregisterRequest canvas_session_context: CanvasSessionContext capi_session_options: CapiSessionOptions + card_digest: CardDigest + card_digest_algorithm: CardDigestAlgorithm + card_digest_value: str + catalog_ai_skill_candidate: CatalogAISkillCandidate + catalog_ai_skill_candidate_provenance: CatalogAISkillCandidateProvenance + catalog_authentication_required_error: CatalogAuthenticationRequiredError + catalog_authentication_required_reason: CatalogAuthenticationRequiredReason + catalog_candidate: CatalogCandidate + catalog_candidate_kind: CatalogCandidateKind + catalog_candidate_source: CatalogCandidateSource + catalog_candidate_source_embedded: CatalogCandidateSourceEmbedded + catalog_candidate_source_url: CatalogCandidateSourceURL + catalog_capability: CatalogCapability + catalog_capability_id: str + catalog_client_contract: CatalogClientContract + catalog_contract_violation_error: CatalogContractViolationError + catalog_contract_violation_reason: CatalogContractViolationReason + catalog_handle_rejected_error: CatalogHandleRejectedError + catalog_handle_rejection_reason: CatalogHandleRejectionReason + catalog_handle_type: CatalogHandleType + catalog_invalid_request_error: CatalogInvalidRequestError + catalog_invalid_request_field: CatalogInvalidRequestField + catalog_malformed_card_error: CatalogMalformedCardError + catalog_malformed_card_reason: CatalogMalformedCardReason + catalog_mcp_server_candidate: CatalogMCPServerCandidate + catalog_mcp_server_candidate_provenance: CatalogMCPServerCandidateProvenance + catalog_mcp_server_installability: CatalogMCPServerInstallabilityEnum + catalog_media_type: CatalogMediaType + catalog_negotiated_contract: CatalogNegotiatedContract + catalog_negotiation_refused_error: CatalogNegotiationRefusedError + catalog_negotiation_refused_reason: CatalogNegotiationRefusedReason + catalog_network_failure_error: CatalogNetworkFailureError + catalog_network_failure_reason: CatalogNetworkFailureReason + catalog_not_installable_error: CatalogNotInstallableError + catalog_not_installable_reason: CatalogNotInstallableReason + catalog_policy_rejected_error: CatalogPolicyRejectedError + catalog_search_request: CatalogSearchRequest + catalog_search_result: CatalogSearchResult + catalog_search_succeeded: CatalogSearchSucceeded + catalog_unavailable_error: CatalogUnavailableError + catalog_unavailable_reason: CatalogUnavailableReason + catalog_unavailable_transport_error: CatalogUnavailableTransportError + catalog_unavailable_transport_reason: CatalogUnavailableTransportReason + catalog_unsafe_retrieval_error: CatalogUnsafeRetrievalError + catalog_unsafe_retrieval_reason: CatalogUnsafeRetrievalReason + catalog_unsupported_kind_error: CatalogUnsupportedKindError command_list: CommandList + commands_finalize_invocation_effect_request: CommandsFinalizeInvocationEffectRequest + commands_finalize_invocation_effect_result: CommandsFinalizeInvocationEffectResult commands_handle_pending_command_request: CommandsHandlePendingCommandRequest commands_handle_pending_command_result: CommandsHandlePendingCommandResult + commands_invocation_effect_outcome: CommandsInvocationEffectOutcome + commands_invocation_origin: CommandsInvocationOrigin commands_invoke_request: CommandsInvokeRequest commands_list_request: Any commands_respond_to_queued_command_request: CommandsRespondToQueuedCommandRequest @@ -28417,6 +35628,7 @@ class RPC: completions_request_request: CompletionsRequestRequest completions_request_result: CompletionsRequestResult configure_session_extensions_params: _ConfigureSessionExtensionsParams + connect_client_info: _ConnectClientInfo connected_remote_session_metadata: ConnectedRemoteSessionMetadata connected_remote_session_metadata_kind: ConnectedRemoteSessionMetadataKind connected_remote_session_metadata_repository: ConnectedRemoteSessionMetadataRepository @@ -28456,6 +35668,7 @@ class RPC: discovered_extensions_disable_request: DiscoveredExtensionsDisableRequest discovered_extensions_enable_request: DiscoveredExtensionsEnableRequest discovered_extension_source: DiscoveredExtensionSource + discovered_hook: DiscoveredHook discovered_mcp_server: DiscoveredMCPServer discovered_mcp_server_type: DiscoveredMCPServerType enqueue_command_params: EnqueueCommandParams @@ -28473,6 +35686,9 @@ class RPC: 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 @@ -28531,6 +35747,9 @@ class RPC: factory_run_status: FactoryRunStatus factory_run_summary: FactoryRunSummary factory_run_terminal: FactoryRunTerminal + factory_tool_resume_request: _FactoryToolResumeRequest + factory_tool_run_options: _FactoryToolRunOptions + factory_tool_run_request: _FactoryToolRunRequest filter_mapping: dict[str, ContentFilterMode] | ContentFilterMode fleet_start_request: FleetStartRequest fleet_start_result: FleetStartResult @@ -28541,6 +35760,9 @@ class RPC: git_hub_telemetry_client_info: GitHubTelemetryClientInfo git_hub_telemetry_event: GitHubTelemetryEvent git_hub_telemetry_notification: GitHubTelemetryNotification + git_hub_token_acquire_reason: GitHubTokenAcquireReason + git_hub_token_acquire_request: GitHubTokenAcquireRequest + git_hub_token_acquire_result: GitHubTokenAcquireResult handle_pending_tool_call_request: HandlePendingToolCallRequest handle_pending_tool_call_result: HandlePendingToolCallResult history_abort_manual_compaction_result: HistoryAbortManualCompactionResult @@ -28569,7 +35791,10 @@ class RPC: hmac_auth_info: HMACAuthInfo hook_invoke_request: _HookInvokeRequest hook_invoke_response: _HookInvokeResponse - hook_type: _HookType + hook_origin: HookOrigin + hooks_discover_request: HooksDiscoverRequest + hooks_discover_result: HooksDiscoverResult + hook_type: HookType installed_plugin: InstalledPlugin installed_plugin_info: InstalledPluginInfo installed_plugin_source: InstalledPluginSource | str @@ -28604,6 +35829,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 @@ -28648,15 +35874,18 @@ class RPC: mcp_disable_request: MCPDisableRequest mcp_discover_request: MCPDiscoverRequest mcp_discover_result: MCPDiscoverResult + mcp_elicitation_form_mode: MCPElicitationFormMode mcp_enable_request: MCPEnableRequest mcp_execute_sampling_params: MCPExecuteSamplingParams mcp_execute_sampling_request: dict[str, Any] mcp_execute_sampling_result: dict[str, Any] + mcp_failed_server: MCPFailedServer mcp_filtered_server: MCPFilteredServer mcp_headers_handle_pending_headers_refresh_request: MCPHeadersHandlePendingHeadersRefreshRequest mcp_headers_handle_pending_headers_refresh_request_request: MCPHeadersHandlePendingHeadersRefreshRequestRequest mcp_headers_handle_pending_headers_refresh_request_result: MCPHeadersHandlePendingHeadersRefreshRequestResult mcp_host_state: MCPHostState + mcp_install_plan: MCPInstallPlan mcp_is_server_running_request: MCPIsServerRunningRequest mcp_is_server_running_result: MCPIsServerRunningResult mcp_list_tools_request: MCPListToolsRequest @@ -28668,9 +35897,47 @@ class RPC: mcp_oauth_login_request: MCPOauthLoginRequest mcp_oauth_login_result: MCPOauthLoginResult mcp_oauth_pending_request_response: MCPOauthPendingRequestResponse + mcp_oauth_probe_needs_auth_reason: MCPOauthProbeNeedsAuthReason + mcp_oauth_probe_request: MCPOauthProbeRequest + mcp_oauth_probe_result: MCPOauthProbeResult mcp_oauth_respond_request: MCPOauthRespondRequest mcp_oauth_respond_result: MCPOauthRespondResult + mcp_plan_configuration_change: MCPPlanConfigurationChange + mcp_plan_configuration_operation: MCPPlanConfigurationOperation + mcp_plan_enum_value_type: MCPPlan + mcp_plan_install_planned: MCPPlanInstallPlanned + mcp_plan_install_request: MCPPlanInstallRequest + mcp_plan_install_result: MCPPlanInstallResult + mcp_plan_install_source: MCPPlanInstallSource + mcp_plan_install_source_candidate: MCPPlanInstallSourceCandidate + mcp_plan_install_source_candidate_kind: MCPPlanInstallSourceCandidateKind + mcp_plan_install_source_card: MCPPlanInstallSourceCard + mcp_plan_install_source_card_kind: MCPPlanInstallSourceCardKind + mcp_plan_package_install_method: MCPPlanPackageInstallMethod + mcp_plan_package_transport: MCPPlanPackageTransport + mcp_plan_policy_decision: MCPPlanPolicyDecision + mcp_plan_policy_result: MCPPlanPolicyResult + mcp_plan_policy_source: MCPPlanPolicySource + mcp_plan_provenance: MCPPlanProvenance + mcp_plan_remote_install_method: MCPPlanRemoteInstallMethod + mcp_plan_remote_transport: MCPPlanRemoteTransport + mcp_plan_required_value: MCPPlanRequiredValue + mcp_plan_required_value_enum: MCPPlanRequiredValueEnum + mcp_plan_required_value_enum_kind: MCPPlan + mcp_plan_required_value_scalar: MCPPlanRequiredValueScalar + mcp_plan_required_value_scalar_kind: MCPPlanRequiredValueScalarKind + mcp_plan_resource_identity: MCPPlanResourceIdentity + mcp_plan_scalar_value_type: MCPPlanScalarValueTypeEnum + mcp_plan_scope: MCPPlanScope + mcp_plan_secret_placeholder: MCPPlanSecretPlaceholder + mcp_plan_secret_reference: str + mcp_plan_target: MCPPlanTarget + mcp_plan_transport_choice: MCPPlanTransportChoice + mcp_plan_transport_choice_package: MCPPlanTransportChoicePackage + mcp_plan_transport_choice_remote: MCPPlanTransportChoiceRemote + mcp_plan_value_category: MCPPlanValueCategory mcp_register_external_client_request: MCPRegisterExternalClientRequest + mcp_reload_config: MCPReloadConfig mcp_reload_with_config_request: MCPReloadWithConfigRequest mcp_remove_git_hub_result: MCPRemoveGitHubResult mcp_resource: MCPResource @@ -28685,17 +35952,29 @@ class RPC: mcp_resources_read_result: MCPResourcesReadResult mcp_resource_template: MCPResourceTemplate mcp_restart_server_request: MCPRestartServerRequest + mcp_safe_for_telemetry: bool | MCPSafeForTelemetryFields + mcp_safe_for_telemetry_fields: MCPSafeForTelemetryFields mcp_sampling_execution_action: MCPSamplingExecutionAction mcp_sampling_execution_result: MCPSamplingExecutionResult + mcp_serializable_server_config: MCPSerializableServerConfig mcp_server: MCPServer mcp_server_auth_config: bool | MCPServerAuthConfigRedirectPort mcp_server_auth_config_redirect_port: MCPServerAuthConfigRedirectPort + mcp_server_card_embedded: MCPServerCardEmbedded + mcp_server_card_embedded_kind: MCPServerCardEmbeddedKind + mcp_server_card_media_type: MCPServerCardMediaType + mcp_server_card_reference: MCPServerCardReference + mcp_server_card_url: MCPServerCardURL + mcp_server_card_url_kind: MCPServerCardURLKind mcp_server_config: MCPServerConfig mcp_server_config_defer_tools: MCPServerConfigDeferTools mcp_server_config_http: MCPServerConfigHTTP mcp_server_config_http_oauth_grant_type: MCPGrantType mcp_server_config_http_type: MCPServerConfigHTTPType + mcp_server_config_memory: MCPServerConfigMemory + mcp_server_config_memory_type: MCPServerConfigMemoryType mcp_server_config_stdio: MCPServerConfigStdio + mcp_server_config_stdio_type: MCPServerConfigStdioType mcp_server_failure_info: MCPServerFailureInfo mcp_server_list: MCPServerList mcp_server_needs_auth_info: MCPServerNeedsAuthInfo @@ -28705,6 +35984,7 @@ class RPC: mcp_start_server_request: MCPStartServerRequest mcp_start_servers_result: MCPStartServersResult mcp_stop_server_request: MCPStopServerRequest + mcp_task_metadata: MCPTaskMetadata mcp_tools: MCPTools mcp_tool_ui: MCPToolUI mcp_tool_ui_visibility: MCPToolUIVisibility @@ -28727,6 +36007,7 @@ class RPC: metadata_snapshot_remote_metadata_repository: MetadataSnapshotRemoteMetadataRepository metadata_snapshot_remote_metadata_task_type: TaskType model: Model + model_apply_startup_overlay_request: ModelApplyStartupOverlayRequest model_billing: ModelBilling model_billing_promo: ModelBillingPromo model_billing_token_prices: ModelBillingTokenPrices @@ -28741,16 +36022,23 @@ class RPC: model_capabilities_supports: ModelCapabilitiesSupports model_list: ModelList model_list_request: Any + model_message: ModelMessage model_picker_category: ModelPickerCategory + model_picker_persistence_request: ModelPickerPersistenceRequest model_picker_price_category: ModelPickerPriceCategory + model_picker_settings_context: ModelPickerSettingsContext model_policy: ModelPolicy model_policy_state: ModelPolicyState model_set_reasoning_effort_request: ModelSetReasoningEffortRequest model_set_reasoning_effort_result: ModelSetReasoningEffortResult models_list_request: ModelsListRequest + model_switch_confirmation: ModelSwitchConfirmation model_switch_to_request: ModelSwitchToRequest model_switch_to_result: ModelSwitchToResult + model_warning_text: ModelWarningText mode_set_request: ModeSetRequest + mode_set_result: ModeSetResult + move_mcp_loading_to_background_result: MoveMCPLoadingToBackgroundResult named_provider_config: NamedProviderConfig name_get_result: NameGetResult name_set_auto_request: NameSetAutoRequest @@ -28775,6 +36063,7 @@ class RPC: permission_decision_approve_for_location_approval: PermissionDecisionApproveForLocationApproval permission_decision_approve_for_location_approval_commands: PermissionDecisionApproveForLocationApprovalCommands permission_decision_approve_for_location_approval_custom_tool: PermissionDecisionApproveForLocationApprovalCustomTool + permission_decision_approve_for_location_approval_extension_env_access: PermissionDecisionApproveForLocationApprovalExtensionEnvAccess 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 @@ -28787,6 +36076,7 @@ class RPC: permission_decision_approve_for_session_approval: PermissionDecisionApproveForSessionApproval permission_decision_approve_for_session_approval_commands: PermissionDecisionApproveForSessionApprovalCommands permission_decision_approve_for_session_approval_custom_tool: PermissionDecisionApproveForSessionApprovalCustomTool + permission_decision_approve_for_session_approval_extension_env_access: PermissionDecisionApproveForSessionApprovalExtensionEnvAccess 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 @@ -28798,13 +36088,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 @@ -28812,6 +36106,7 @@ class RPC: permission_location_resolve_params: PermissionLocationResolveParams permission_location_resolve_result: PermissionLocationResolveResult permission_location_type: PermissionLocationType + permission_mode_source: PermissionSource permission_paths_add_params: PermissionPathsAddParams permission_paths_allowed_check_params: PermissionPathsAllowedCheckParams permission_paths_allowed_check_result: PermissionPathsAllowedCheckResult @@ -28822,8 +36117,8 @@ class RPC: permission_paths_workspace_check_result: PermissionPathsWorkspaceCheckResult permission_prompt_shown_notification: PermissionPromptShownNotification permission_request_result: PermissionRequestResult + permission_response_capability: PermissionResponseCapability permission_rules_set: PermissionRulesSet - permissions_allow_all_mode: PermissionsAllowAllMode permissions_configure_additional_content_exclusion_policy: PermissionsConfigureAdditionalContentExclusionPolicy permissions_configure_additional_content_exclusion_policy_rule: PermissionsConfigureAdditionalContentExclusionPolicyRule permissions_configure_additional_content_exclusion_policy_rule_source: PermissionsConfigureAdditionalContentExclusionPolicyRuleSource @@ -28831,10 +36126,12 @@ class RPC: permissions_configure_params: PermissionsConfigureParams permissions_configure_result: PermissionsConfigureResult permissions_folder_trust_add_trusted_result: PermissionsFolderTrustAddTrustedResult - permissions_get_allow_all_request: PermissionsGetAllowAllRequest + permissions_get_mode_request: PermissionsGetModeRequest + permissions_get_mode_result: PermissionsGetModeResult permissions_locations_add_tool_approval_details: PermissionsLocationsAddToolApprovalDetails permissions_locations_add_tool_approval_details_commands: PermissionsLocationsAddToolApprovalDetailsCommands permissions_locations_add_tool_approval_details_custom_tool: PermissionsLocationsAddToolApprovalDetailsCustomTool + permissions_locations_add_tool_approval_details_extension_env_access: PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess 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 @@ -28854,11 +36151,11 @@ class RPC: permissions_pending_requests_request: PermissionsPendingRequestsRequest permissions_reset_session_approvals_request: PermissionsResetSessionApprovalsRequest permissions_reset_session_approvals_result: PermissionsResetSessionApprovalsResult - permissions_set_allow_all_request: PermissionsSetAllowAllRequest - permissions_set_allow_all_source: PermissionsSetAAllSource permissions_set_approve_all_request: PermissionsSetApproveAllRequest permissions_set_approve_all_result: PermissionsSetApproveAllResult - permissions_set_approve_all_source: PermissionsSetAAllSource + permissions_set_approve_all_source: PermissionSource + permissions_set_mode_request: PermissionsSetModeRequest + permissions_set_mode_result: PermissionsSetModeResult permissions_set_required_request: PermissionsSetRequiredRequest permissions_set_required_result: PermissionsSetRequiredResult permissions_urls_set_unrestricted_mode_result: PermissionsUrlsSetUnrestrictedModeResult @@ -28876,6 +36173,7 @@ class RPC: plugin_install_result: PluginInstallResult plugin_list: PluginList plugin_list_result: PluginListResult + plugins_builtin_set_request: PluginsBuiltinSetRequest plugins_disable_request: PluginsDisableRequest plugins_enable_request: PluginsEnableRequest plugins_install_request: PluginsInstallRequest @@ -28889,6 +36187,8 @@ class RPC: plugin_update_all_entry: PluginUpdateAllEntry plugin_update_all_result: PluginUpdateAllResult plugin_update_result: PluginUpdateResult + protocol_external_tool_defer: MCPServerConfigDeferTools + protocol_external_tool_definition: ProtocolExternalToolDefinition provider_add_request: ProviderAddRequest provider_add_result: ProviderAddResult provider_config: ProviderConfig @@ -28978,6 +36278,7 @@ class RPC: remote_notify_steerable_changed_request: RemoteNotifySteerableChangedRequest remote_notify_steerable_changed_result: RemoteNotifySteerableChangedResult remote_session_connection_result: RemoteSessionConnectionResult + remote_session_host_status: RemoteSessionHostStatus remote_session_metadata_repository: RemoteSessionMetadataRepository remote_session_metadata_task_type: TaskType remote_session_metadata_value: RemoteSessionMetadataValue @@ -28985,6 +36286,8 @@ class RPC: remote_session_repository: RemoteSessionRepository run_options: RunOptions sandbox_config: SandboxConfig + sandbox_config_auth: SandboxConfigAuth + sandbox_config_source: _SandboxConfigSource sandbox_config_user_policy: SandboxConfigUserPolicy sandbox_config_user_policy_experimental: SandboxConfigUserPolicyExperimental sandbox_config_user_policy_experimental_seatbelt: SandboxConfigUserPolicyExperimentalSeatbelt @@ -28992,6 +36295,7 @@ class RPC: sandbox_config_user_policy_network: SandboxConfigUserPolicyNetwork sandbox_config_user_policy_network_proxy: SandboxConfigUserPolicyNetworkProxy sandbox_config_user_policy_seatbelt: SandboxConfigUserPolicySeatbelt + sandbox_enforcement_status: SandboxEnforcementStatus schedule_add_at_request: ScheduleAddAtRequest schedule_add_cron_request: ScheduleAddCronRequest schedule_add_request: ScheduleAddRequest @@ -29020,7 +36324,10 @@ class RPC: server_skill_list: ServerSkillList session_activity: SessionActivity session_agent_list_request: SessionAgentListRequest + session_auth_login_request: SessionAuthLoginRequest + session_auth_logout_user_request: SessionAuthLogoutUserRequest session_auth_status: SessionAuthStatus + session_auth_switch_request: SessionAuthSwitchRequest session_bulk_delete_result: SessionBulkDeleteResult session_cancel_all_background_agents_result: int session_capability: SessionCapability @@ -29062,6 +36369,9 @@ class RPC: session_fs_stat_request: SessionFSStatRequest session_fs_stat_result: SessionFSStatResult session_fs_write_file_request: SessionFSWriteFileRequest + session_git_hub_auth_get_all_auth_available_result: list[SessionAuthStatus] + session_git_hub_auth_logout_result: bool + session_git_hub_auth_logout_user_result: bool session_history_compact_request: SessionHistoryCompactRequest session_installed_plugin: SessionInstalledPlugin session_installed_plugin_source: SessionInstalledPluginSource | str @@ -29083,6 +36393,8 @@ class RPC: 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 @@ -29155,6 +36467,7 @@ class RPC: sessions_open_status: SessionsOpenStatus session_source: SessionSource sessions_prune_old_request: SessionsPruneOldRequest + sessions_read_persisted_events_request: SessionsReadPersistedEventsRequest sessions_register_extension_tools_on_session_options: SessionsRegisterExtensionToolsOnSessionOptions sessions_release_lock_request: SessionsReleaseLockRequest sessions_release_lock_result: SessionsReleaseLockResult @@ -29174,7 +36487,10 @@ class RPC: session_visibility_status: SessionVisibilityStatus session_working_directory_context: SessionWorkingDirectoryContext session_working_directory_context_host_type: HostType + settable_auth_info: SettableAuthInfo + settable_token_auth_info: SettableTokenAuthInfo shell_cancel_user_requested_request: ShellCancelUserRequestedRequest + shell_credentials: ShellCredentials shell_exec_request: ShellExecRequest shell_exec_result: ShellExecResult shell_execute_user_requested_request: ShellExecuteUserRequestedRequest @@ -29191,7 +36507,13 @@ class RPC: skill_discovery_path_list: SkillDiscoveryPathList skill_discovery_scope: SkillDiscoveryScope skill_list: SkillList + skill_provider_descriptor: SkillProviderDescriptor + skill_provider_list_request: SkillProviderListRequest + skill_provider_list_result: _SkillProviderListResult + skill_provider_read_request: _SkillProviderReadRequest + skill_provider_read_result: _SkillProviderReadResult skills_config_set_disabled_skills_request: SkillsConfigSetDisabledSkillsRequest + skills_config_set_skill_disabled_request: SkillsConfigSetSkillDisabledRequest skills_disable_request: SkillsDisableRequest skills_discover_request: SkillsDiscoverRequest skills_enable_request: SkillsEnableRequest @@ -29199,6 +36521,7 @@ class RPC: skills_get_invoked_result: SkillsGetInvokedResult skills_invoked_skill: SkillsInvokedSkill skills_load_diagnostics: SkillsLoadDiagnostics + slash_command_add_timeline_entry_result: SlashCommandAddTimelineEntryResult slash_command_agent_prompt_result: SlashCommandAgentPromptResult slash_command_completed_result: SlashCommandCompletedResult slash_command_info: SlashCommandInfo @@ -29207,13 +36530,20 @@ class RPC: slash_command_input_completion: SlashCommandInputCompletion slash_command_invocation_result: SlashCommandInvocationResult slash_command_kind: SlashCommandKind + slash_command_model_picker_dialog: SlashCommandModelPickerDialog slash_command_select_subcommand_option: SlashCommandSelectSubcommandOption slash_command_select_subcommand_result: SlashCommandSelectSubcommandResult + slash_command_set_model_result: SlashCommandSetModelResult + slash_command_set_plan_model_result: SlashCommandSetPlanModelResult + slash_command_show_dialog_result: SlashCommandShowDialogResult slash_command_text_result: SlashCommandTextResult + slash_command_timeline_entry: SlashCommandTimelineEntry subagent_settings_entry: SubagentSettingsEntry subagent_settings_entry_context_tier: SubagentSettingsEntryContextTier task_agent_info: TaskAgentInfo task_agent_progress: TaskAgentProgress + task_complete_data: TaskCompleteData + task_completion_decision: TaskCompletionDecision task_execution_mode: TaskExecutionMode task_info: TaskInfo task_list: TaskList @@ -29240,11 +36570,23 @@ class RPC: tasks_wait_for_pending_result: TasksWaitForPendingResult telemetry_set_feature_overrides_request: TelemetrySetFeatureOverridesRequest token_auth_info: TokenAuthInfo + token_provider_auth_info: TokenProviderAuthInfo tool: Tool tool_list: ToolList + tool_result: ToolResultExpanded | str + tool_result_expanded: ToolResultExpanded + tool_result_new_message: ToolResultNewMessage + tool_result_type: ToolResultType + tools_execute_request: ToolsExecuteRequest + tools_get_builtin_descriptors_request: ToolsGetBuiltinDescriptorsRequest + tools_get_builtin_descriptors_result: ToolsGetBuiltinDescriptorsResult tools_get_current_metadata_result: ToolsGetCurrentMetadataResult tools_initialize_and_validate_result: ToolsInitializeAndValidateResult tools_list_request: ToolsListRequest + tools_set_request: ToolsSetRequest + tools_set_result: ToolsSetResult + tools_shell_descriptor_config: ToolsShellDescriptorConfig + tools_task_complete_event_data_request: ToolsTaskCompleteEventDataRequest tools_update_subagent_settings_result: ToolsUpdateSubagentSettingsResult ui_auto_mode_switch_response: UIAutoModeSwitchResponse ui_elicitation_array_any_of_field: UIElicitationArrayAnyOfField @@ -29288,6 +36630,7 @@ class RPC: ui_user_input_response: UIUserInputResponse update_subagent_settings_request: UpdateSubagentSettingsRequest usage_get_metrics_result: UsageGetMetricsResult + usage_metrics_agent_metric: UsageMetricsAgentMetric usage_metrics_code_changes: UsageMetricsCodeChanges usage_metrics_model_metric: UsageMetricsModelMetric usage_metrics_model_metric_requests: UsageMetricsModelMetricRequests @@ -29331,6 +36674,7 @@ class RPC: workspaces_workspace_details_host_type: HostType workspaces_write_autopilot_objective_request: WorkspacesWriteAutopilotObjectiveRequest workspaces_write_autopilot_objective_result: WorkspacesWriteAutopilotObjectiveResult + session_auth_info_result: AuthIdentity | None = None session_context_attribution: SessionContextAttribution | None = None session_context_info: SessionContextInfo | None = None subagent_settings: SubagentSettings | None = None @@ -29383,13 +36727,21 @@ def from_dict(obj: Any) -> 'RPC': 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_identity = AuthIdentity.from_dict(obj.get("AuthIdentity")) auth_info = _load_AuthInfo(obj.get("AuthInfo")) auth_info_type = AuthInfoType(obj.get("AuthInfoType")) + auth_validation_error = AuthValidationError.from_dict(obj.get("AuthValidationError")) + auth_validation_errors = from_list(AuthValidationError.from_dict, obj.get("AuthValidationErrors")) built_in_model_catalog = BuiltInModelCatalog.from_dict(obj.get("BuiltInModelCatalog")) built_in_model_catalog_entry = BuiltInModelCatalogEntry.from_dict(obj.get("BuiltInModelCatalogEntry")) + builtin_tool_descriptor = BuiltinToolDescriptor.from_dict(obj.get("BuiltinToolDescriptor")) + builtin_tool_format = BuiltinToolFormat.from_dict(obj.get("BuiltinToolFormat")) + builtin_tool_format_type = BuiltinToolFormatType(obj.get("BuiltinToolFormatType")) + builtin_tool_input_schema = BuiltinToolInputSchema.from_dict(obj.get("BuiltinToolInputSchema")) + builtin_tool_input_schema_type = BuiltinToolInputSchemaType(obj.get("BuiltinToolInputSchemaType")) + builtin_tool_safe_for_telemetry = from_union([from_bool, BuiltinToolSafeTelemetryFields.from_dict], obj.get("BuiltinToolSafeForTelemetry")) + builtin_tool_safe_telemetry_fields = BuiltinToolSafeTelemetryFields.from_dict(obj.get("BuiltinToolSafeTelemetryFields")) 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")) @@ -29405,11 +36757,63 @@ def from_dict(obj: Any) -> 'RPC': canvas_provider_invoke_action_request = CanvasProviderInvokeActionRequest.from_dict(obj.get("CanvasProviderInvokeActionRequest")) canvas_provider_open_request = CanvasProviderOpenRequest.from_dict(obj.get("CanvasProviderOpenRequest")) canvas_provider_open_result = CanvasProviderOpenResult.from_dict(obj.get("CanvasProviderOpenResult")) + canvas_provider_register_request = CanvasProviderRegisterRequest.from_dict(obj.get("CanvasProviderRegisterRequest")) + canvas_provider_unregister_request = CanvasProviderUnregisterRequest.from_dict(obj.get("CanvasProviderUnregisterRequest")) canvas_session_context = CanvasSessionContext.from_dict(obj.get("CanvasSessionContext")) capi_session_options = CapiSessionOptions.from_dict(obj.get("CapiSessionOptions")) + card_digest = CardDigest.from_dict(obj.get("CardDigest")) + card_digest_algorithm = CardDigestAlgorithm(obj.get("CardDigestAlgorithm")) + card_digest_value = from_str(obj.get("CardDigestValue")) + catalog_ai_skill_candidate = CatalogAISkillCandidate.from_dict(obj.get("CatalogAiSkillCandidate")) + catalog_ai_skill_candidate_provenance = CatalogAISkillCandidateProvenance.from_dict(obj.get("CatalogAiSkillCandidateProvenance")) + catalog_authentication_required_error = CatalogAuthenticationRequiredError.from_dict(obj.get("CatalogAuthenticationRequiredError")) + catalog_authentication_required_reason = CatalogAuthenticationRequiredReason(obj.get("CatalogAuthenticationRequiredReason")) + catalog_candidate = CatalogCandidate.from_dict(obj.get("CatalogCandidate")) + catalog_candidate_kind = CatalogCandidateKind(obj.get("CatalogCandidateKind")) + catalog_candidate_source = _load_CatalogCandidateSource(obj.get("CatalogCandidateSource")) + catalog_candidate_source_embedded = CatalogCandidateSourceEmbedded.from_dict(obj.get("CatalogCandidateSourceEmbedded")) + catalog_candidate_source_url = CatalogCandidateSourceURL.from_dict(obj.get("CatalogCandidateSourceUrl")) + catalog_capability = CatalogCapability(obj.get("CatalogCapability")) + catalog_capability_id = from_str(obj.get("CatalogCapabilityId")) + catalog_client_contract = CatalogClientContract.from_dict(obj.get("CatalogClientContract")) + catalog_contract_violation_error = CatalogContractViolationError.from_dict(obj.get("CatalogContractViolationError")) + catalog_contract_violation_reason = CatalogContractViolationReason(obj.get("CatalogContractViolationReason")) + catalog_handle_rejected_error = CatalogHandleRejectedError.from_dict(obj.get("CatalogHandleRejectedError")) + catalog_handle_rejection_reason = CatalogHandleRejectionReason(obj.get("CatalogHandleRejectionReason")) + catalog_handle_type = CatalogHandleType(obj.get("CatalogHandleType")) + catalog_invalid_request_error = CatalogInvalidRequestError.from_dict(obj.get("CatalogInvalidRequestError")) + catalog_invalid_request_field = CatalogInvalidRequestField(obj.get("CatalogInvalidRequestField")) + catalog_malformed_card_error = CatalogMalformedCardError.from_dict(obj.get("CatalogMalformedCardError")) + catalog_malformed_card_reason = CatalogMalformedCardReason(obj.get("CatalogMalformedCardReason")) + catalog_mcp_server_candidate = CatalogMCPServerCandidate.from_dict(obj.get("CatalogMcpServerCandidate")) + catalog_mcp_server_candidate_provenance = CatalogMCPServerCandidateProvenance.from_dict(obj.get("CatalogMcpServerCandidateProvenance")) + catalog_mcp_server_installability = CatalogMCPServerInstallabilityEnum(obj.get("CatalogMcpServerInstallability")) + catalog_media_type = CatalogMediaType(obj.get("CatalogMediaType")) + catalog_negotiated_contract = CatalogNegotiatedContract.from_dict(obj.get("CatalogNegotiatedContract")) + catalog_negotiation_refused_error = CatalogNegotiationRefusedError.from_dict(obj.get("CatalogNegotiationRefusedError")) + catalog_negotiation_refused_reason = CatalogNegotiationRefusedReason(obj.get("CatalogNegotiationRefusedReason")) + catalog_network_failure_error = CatalogNetworkFailureError.from_dict(obj.get("CatalogNetworkFailureError")) + catalog_network_failure_reason = CatalogNetworkFailureReason(obj.get("CatalogNetworkFailureReason")) + catalog_not_installable_error = CatalogNotInstallableError.from_dict(obj.get("CatalogNotInstallableError")) + catalog_not_installable_reason = CatalogNotInstallableReason(obj.get("CatalogNotInstallableReason")) + catalog_policy_rejected_error = CatalogPolicyRejectedError.from_dict(obj.get("CatalogPolicyRejectedError")) + catalog_search_request = CatalogSearchRequest.from_dict(obj.get("CatalogSearchRequest")) + catalog_search_result = _load_CatalogSearchResult(obj.get("CatalogSearchResult")) + catalog_search_succeeded = CatalogSearchSucceeded.from_dict(obj.get("CatalogSearchSucceeded")) + catalog_unavailable_error = CatalogUnavailableError.from_dict(obj.get("CatalogUnavailableError")) + catalog_unavailable_reason = CatalogUnavailableReason(obj.get("CatalogUnavailableReason")) + catalog_unavailable_transport_error = CatalogUnavailableTransportError.from_dict(obj.get("CatalogUnavailableTransportError")) + catalog_unavailable_transport_reason = CatalogUnavailableTransportReason(obj.get("CatalogUnavailableTransportReason")) + catalog_unsafe_retrieval_error = CatalogUnsafeRetrievalError.from_dict(obj.get("CatalogUnsafeRetrievalError")) + catalog_unsafe_retrieval_reason = CatalogUnsafeRetrievalReason(obj.get("CatalogUnsafeRetrievalReason")) + catalog_unsupported_kind_error = CatalogUnsupportedKindError.from_dict(obj.get("CatalogUnsupportedKindError")) command_list = CommandList.from_dict(obj.get("CommandList")) + commands_finalize_invocation_effect_request = CommandsFinalizeInvocationEffectRequest.from_dict(obj.get("CommandsFinalizeInvocationEffectRequest")) + commands_finalize_invocation_effect_result = CommandsFinalizeInvocationEffectResult.from_dict(obj.get("CommandsFinalizeInvocationEffectResult")) commands_handle_pending_command_request = CommandsHandlePendingCommandRequest.from_dict(obj.get("CommandsHandlePendingCommandRequest")) commands_handle_pending_command_result = CommandsHandlePendingCommandResult.from_dict(obj.get("CommandsHandlePendingCommandResult")) + commands_invocation_effect_outcome = CommandsInvocationEffectOutcome(obj.get("CommandsInvocationEffectOutcome")) + commands_invocation_origin = CommandsInvocationOrigin(obj.get("CommandsInvocationOrigin")) commands_invoke_request = CommandsInvokeRequest.from_dict(obj.get("CommandsInvokeRequest")) commands_list_request = obj.get("CommandsListRequest") commands_respond_to_queued_command_request = CommandsRespondToQueuedCommandRequest.from_dict(obj.get("CommandsRespondToQueuedCommandRequest")) @@ -29418,6 +36822,7 @@ def from_dict(obj: Any) -> 'RPC': completions_request_request = CompletionsRequestRequest.from_dict(obj.get("CompletionsRequestRequest")) completions_request_result = CompletionsRequestResult.from_dict(obj.get("CompletionsRequestResult")) configure_session_extensions_params = _ConfigureSessionExtensionsParams.from_dict(obj.get("ConfigureSessionExtensionsParams")) + connect_client_info = _ConnectClientInfo.from_dict(obj.get("ConnectClientInfo")) connected_remote_session_metadata = ConnectedRemoteSessionMetadata.from_dict(obj.get("ConnectedRemoteSessionMetadata")) connected_remote_session_metadata_kind = ConnectedRemoteSessionMetadataKind(obj.get("ConnectedRemoteSessionMetadataKind")) connected_remote_session_metadata_repository = ConnectedRemoteSessionMetadataRepository.from_dict(obj.get("ConnectedRemoteSessionMetadataRepository")) @@ -29457,6 +36862,7 @@ def from_dict(obj: Any) -> 'RPC': 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_hook = DiscoveredHook.from_dict(obj.get("DiscoveredHook")) 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")) @@ -29474,6 +36880,9 @@ def from_dict(obj: Any) -> 'RPC': 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")) @@ -29532,6 +36941,9 @@ def from_dict(obj: Any) -> 'RPC': 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")) + factory_tool_resume_request = _FactoryToolResumeRequest.from_dict(obj.get("FactoryToolResumeRequest")) + factory_tool_run_options = _FactoryToolRunOptions.from_dict(obj.get("FactoryToolRunOptions")) + factory_tool_run_request = _FactoryToolRunRequest.from_dict(obj.get("FactoryToolRunRequest")) 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")) @@ -29542,6 +36954,9 @@ def from_dict(obj: Any) -> 'RPC': git_hub_telemetry_client_info = GitHubTelemetryClientInfo.from_dict(obj.get("GitHubTelemetryClientInfo")) git_hub_telemetry_event = GitHubTelemetryEvent.from_dict(obj.get("GitHubTelemetryEvent")) git_hub_telemetry_notification = GitHubTelemetryNotification.from_dict(obj.get("GitHubTelemetryNotification")) + git_hub_token_acquire_reason = GitHubTokenAcquireReason(obj.get("GitHubTokenAcquireReason")) + git_hub_token_acquire_request = GitHubTokenAcquireRequest.from_dict(obj.get("GitHubTokenAcquireRequest")) + git_hub_token_acquire_result = GitHubTokenAcquireResult.from_dict(obj.get("GitHubTokenAcquireResult")) handle_pending_tool_call_request = HandlePendingToolCallRequest.from_dict(obj.get("HandlePendingToolCallRequest")) handle_pending_tool_call_result = HandlePendingToolCallResult.from_dict(obj.get("HandlePendingToolCallResult")) history_abort_manual_compaction_result = HistoryAbortManualCompactionResult.from_dict(obj.get("HistoryAbortManualCompactionResult")) @@ -29570,7 +36985,10 @@ def from_dict(obj: Any) -> 'RPC': hmac_auth_info = HMACAuthInfo.from_dict(obj.get("HMACAuthInfo")) hook_invoke_request = _HookInvokeRequest.from_dict(obj.get("HookInvokeRequest")) hook_invoke_response = _HookInvokeResponse.from_dict(obj.get("HookInvokeResponse")) - hook_type = _HookType(obj.get("HookType")) + hook_origin = HookOrigin(obj.get("HookOrigin")) + hooks_discover_request = HooksDiscoverRequest.from_dict(obj.get("HooksDiscoverRequest")) + hooks_discover_result = HooksDiscoverResult.from_dict(obj.get("HooksDiscoverResult")) + hook_type = HookType(obj.get("HookType")) installed_plugin = InstalledPlugin.from_dict(obj.get("InstalledPlugin")) installed_plugin_info = InstalledPluginInfo.from_dict(obj.get("InstalledPluginInfo")) installed_plugin_source = from_union([InstalledPluginSource.from_dict, from_str], obj.get("InstalledPluginSource")) @@ -29605,6 +37023,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")) @@ -29649,15 +37068,18 @@ def from_dict(obj: Any) -> 'RPC': mcp_disable_request = MCPDisableRequest.from_dict(obj.get("McpDisableRequest")) mcp_discover_request = MCPDiscoverRequest.from_dict(obj.get("McpDiscoverRequest")) mcp_discover_result = MCPDiscoverResult.from_dict(obj.get("McpDiscoverResult")) + mcp_elicitation_form_mode = MCPElicitationFormMode(obj.get("McpElicitationFormMode")) mcp_enable_request = MCPEnableRequest.from_dict(obj.get("McpEnableRequest")) mcp_execute_sampling_params = MCPExecuteSamplingParams.from_dict(obj.get("McpExecuteSamplingParams")) mcp_execute_sampling_request = from_dict(lambda x: x, obj.get("McpExecuteSamplingRequest")) mcp_execute_sampling_result = from_dict(lambda x: x, obj.get("McpExecuteSamplingResult")) + mcp_failed_server = MCPFailedServer.from_dict(obj.get("McpFailedServer")) mcp_filtered_server = MCPFilteredServer.from_dict(obj.get("McpFilteredServer")) mcp_headers_handle_pending_headers_refresh_request = MCPHeadersHandlePendingHeadersRefreshRequest.from_dict(obj.get("McpHeadersHandlePendingHeadersRefreshRequest")) mcp_headers_handle_pending_headers_refresh_request_request = MCPHeadersHandlePendingHeadersRefreshRequestRequest.from_dict(obj.get("McpHeadersHandlePendingHeadersRefreshRequestRequest")) mcp_headers_handle_pending_headers_refresh_request_result = MCPHeadersHandlePendingHeadersRefreshRequestResult.from_dict(obj.get("McpHeadersHandlePendingHeadersRefreshRequestResult")) mcp_host_state = MCPHostState.from_dict(obj.get("McpHostState")) + mcp_install_plan = MCPInstallPlan.from_dict(obj.get("McpInstallPlan")) mcp_is_server_running_request = MCPIsServerRunningRequest.from_dict(obj.get("McpIsServerRunningRequest")) mcp_is_server_running_result = MCPIsServerRunningResult.from_dict(obj.get("McpIsServerRunningResult")) mcp_list_tools_request = MCPListToolsRequest.from_dict(obj.get("McpListToolsRequest")) @@ -29669,9 +37091,47 @@ def from_dict(obj: Any) -> 'RPC': 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_probe_needs_auth_reason = MCPOauthProbeNeedsAuthReason(obj.get("McpOauthProbeNeedsAuthReason")) + mcp_oauth_probe_request = MCPOauthProbeRequest.from_dict(obj.get("McpOauthProbeRequest")) + mcp_oauth_probe_result = MCPOauthProbeResult.from_dict(obj.get("McpOauthProbeResult")) mcp_oauth_respond_request = MCPOauthRespondRequest.from_dict(obj.get("McpOauthRespondRequest")) mcp_oauth_respond_result = MCPOauthRespondResult.from_dict(obj.get("McpOauthRespondResult")) + mcp_plan_configuration_change = MCPPlanConfigurationChange.from_dict(obj.get("McpPlanConfigurationChange")) + mcp_plan_configuration_operation = MCPPlanConfigurationOperation(obj.get("McpPlanConfigurationOperation")) + mcp_plan_enum_value_type = MCPPlan(obj.get("McpPlanEnumValueType")) + mcp_plan_install_planned = MCPPlanInstallPlanned.from_dict(obj.get("McpPlanInstallPlanned")) + mcp_plan_install_request = MCPPlanInstallRequest.from_dict(obj.get("McpPlanInstallRequest")) + mcp_plan_install_result = _load_MCPPlanInstallResult(obj.get("McpPlanInstallResult")) + mcp_plan_install_source = _load_MCPPlanInstallSource(obj.get("McpPlanInstallSource")) + mcp_plan_install_source_candidate = MCPPlanInstallSourceCandidate.from_dict(obj.get("McpPlanInstallSourceCandidate")) + mcp_plan_install_source_candidate_kind = MCPPlanInstallSourceCandidateKind(obj.get("McpPlanInstallSourceCandidateKind")) + mcp_plan_install_source_card = MCPPlanInstallSourceCard.from_dict(obj.get("McpPlanInstallSourceCard")) + mcp_plan_install_source_card_kind = MCPPlanInstallSourceCardKind(obj.get("McpPlanInstallSourceCardKind")) + mcp_plan_package_install_method = MCPPlanPackageInstallMethod(obj.get("McpPlanPackageInstallMethod")) + mcp_plan_package_transport = MCPPlanPackageTransport(obj.get("McpPlanPackageTransport")) + mcp_plan_policy_decision = MCPPlanPolicyDecision(obj.get("McpPlanPolicyDecision")) + mcp_plan_policy_result = MCPPlanPolicyResult.from_dict(obj.get("McpPlanPolicyResult")) + mcp_plan_policy_source = MCPPlanPolicySource(obj.get("McpPlanPolicySource")) + mcp_plan_provenance = MCPPlanProvenance.from_dict(obj.get("McpPlanProvenance")) + mcp_plan_remote_install_method = MCPPlanRemoteInstallMethod(obj.get("McpPlanRemoteInstallMethod")) + mcp_plan_remote_transport = MCPPlanRemoteTransport(obj.get("McpPlanRemoteTransport")) + mcp_plan_required_value = _load_MCPPlanRequiredValue(obj.get("McpPlanRequiredValue")) + mcp_plan_required_value_enum = MCPPlanRequiredValueEnum.from_dict(obj.get("McpPlanRequiredValueEnum")) + mcp_plan_required_value_enum_kind = MCPPlan(obj.get("McpPlanRequiredValueEnumKind")) + mcp_plan_required_value_scalar = MCPPlanRequiredValueScalar.from_dict(obj.get("McpPlanRequiredValueScalar")) + mcp_plan_required_value_scalar_kind = MCPPlanRequiredValueScalarKind(obj.get("McpPlanRequiredValueScalarKind")) + mcp_plan_resource_identity = MCPPlanResourceIdentity.from_dict(obj.get("McpPlanResourceIdentity")) + mcp_plan_scalar_value_type = MCPPlanScalarValueTypeEnum(obj.get("McpPlanScalarValueType")) + mcp_plan_scope = MCPPlanScope(obj.get("McpPlanScope")) + mcp_plan_secret_placeholder = MCPPlanSecretPlaceholder.from_dict(obj.get("McpPlanSecretPlaceholder")) + mcp_plan_secret_reference = from_str(obj.get("McpPlanSecretReference")) + mcp_plan_target = MCPPlanTarget.from_dict(obj.get("McpPlanTarget")) + mcp_plan_transport_choice = _load_MCPPlanTransportChoice(obj.get("McpPlanTransportChoice")) + mcp_plan_transport_choice_package = MCPPlanTransportChoicePackage.from_dict(obj.get("McpPlanTransportChoicePackage")) + mcp_plan_transport_choice_remote = MCPPlanTransportChoiceRemote.from_dict(obj.get("McpPlanTransportChoiceRemote")) + mcp_plan_value_category = MCPPlanValueCategory(obj.get("McpPlanValueCategory")) mcp_register_external_client_request = MCPRegisterExternalClientRequest.from_dict(obj.get("McpRegisterExternalClientRequest")) + mcp_reload_config = MCPReloadConfig.from_dict(obj.get("McpReloadConfig")) mcp_reload_with_config_request = MCPReloadWithConfigRequest.from_dict(obj.get("McpReloadWithConfigRequest")) mcp_remove_git_hub_result = MCPRemoveGitHubResult.from_dict(obj.get("McpRemoveGitHubResult")) mcp_resource = MCPResource.from_dict(obj.get("McpResource")) @@ -29686,17 +37146,29 @@ def from_dict(obj: Any) -> 'RPC': mcp_resources_read_result = MCPResourcesReadResult.from_dict(obj.get("McpResourcesReadResult")) mcp_resource_template = MCPResourceTemplate.from_dict(obj.get("McpResourceTemplate")) mcp_restart_server_request = MCPRestartServerRequest.from_dict(obj.get("McpRestartServerRequest")) + mcp_safe_for_telemetry = from_union([from_bool, MCPSafeForTelemetryFields.from_dict], obj.get("McpSafeForTelemetry")) + mcp_safe_for_telemetry_fields = MCPSafeForTelemetryFields.from_dict(obj.get("McpSafeForTelemetryFields")) mcp_sampling_execution_action = MCPSamplingExecutionAction(obj.get("McpSamplingExecutionAction")) mcp_sampling_execution_result = MCPSamplingExecutionResult.from_dict(obj.get("McpSamplingExecutionResult")) + mcp_serializable_server_config = MCPSerializableServerConfig.from_dict(obj.get("McpSerializableServerConfig")) mcp_server = MCPServer.from_dict(obj.get("McpServer")) mcp_server_auth_config = from_union([from_bool, MCPServerAuthConfigRedirectPort.from_dict], obj.get("McpServerAuthConfig")) mcp_server_auth_config_redirect_port = MCPServerAuthConfigRedirectPort.from_dict(obj.get("McpServerAuthConfigRedirectPort")) + mcp_server_card_embedded = MCPServerCardEmbedded.from_dict(obj.get("McpServerCardEmbedded")) + mcp_server_card_embedded_kind = MCPServerCardEmbeddedKind(obj.get("McpServerCardEmbeddedKind")) + mcp_server_card_media_type = MCPServerCardMediaType(obj.get("McpServerCardMediaType")) + mcp_server_card_reference = _load_MCPServerCardReference(obj.get("McpServerCardReference")) + mcp_server_card_url = MCPServerCardURL.from_dict(obj.get("McpServerCardUrl")) + mcp_server_card_url_kind = MCPServerCardURLKind(obj.get("McpServerCardUrlKind")) mcp_server_config = MCPServerConfig.from_dict(obj.get("McpServerConfig")) mcp_server_config_defer_tools = MCPServerConfigDeferTools(obj.get("McpServerConfigDeferTools")) mcp_server_config_http = MCPServerConfigHTTP.from_dict(obj.get("McpServerConfigHttp")) mcp_server_config_http_oauth_grant_type = MCPGrantType(obj.get("McpServerConfigHttpOauthGrantType")) mcp_server_config_http_type = MCPServerConfigHTTPType(obj.get("McpServerConfigHttpType")) + mcp_server_config_memory = MCPServerConfigMemory.from_dict(obj.get("McpServerConfigMemory")) + mcp_server_config_memory_type = MCPServerConfigMemoryType(obj.get("McpServerConfigMemoryType")) mcp_server_config_stdio = MCPServerConfigStdio.from_dict(obj.get("McpServerConfigStdio")) + mcp_server_config_stdio_type = MCPServerConfigStdioType(obj.get("McpServerConfigStdioType")) mcp_server_failure_info = MCPServerFailureInfo.from_dict(obj.get("McpServerFailureInfo")) mcp_server_list = MCPServerList.from_dict(obj.get("McpServerList")) mcp_server_needs_auth_info = MCPServerNeedsAuthInfo.from_dict(obj.get("McpServerNeedsAuthInfo")) @@ -29706,6 +37178,7 @@ def from_dict(obj: Any) -> 'RPC': mcp_start_server_request = MCPStartServerRequest.from_dict(obj.get("McpStartServerRequest")) mcp_start_servers_result = MCPStartServersResult.from_dict(obj.get("McpStartServersResult")) mcp_stop_server_request = MCPStopServerRequest.from_dict(obj.get("McpStopServerRequest")) + mcp_task_metadata = MCPTaskMetadata.from_dict(obj.get("McpTaskMetadata")) mcp_tools = MCPTools.from_dict(obj.get("McpTools")) mcp_tool_ui = MCPToolUI.from_dict(obj.get("McpToolUi")) mcp_tool_ui_visibility = MCPToolUIVisibility(obj.get("McpToolUiVisibility")) @@ -29728,6 +37201,7 @@ def from_dict(obj: Any) -> 'RPC': metadata_snapshot_remote_metadata_repository = MetadataSnapshotRemoteMetadataRepository.from_dict(obj.get("MetadataSnapshotRemoteMetadataRepository")) metadata_snapshot_remote_metadata_task_type = TaskType(obj.get("MetadataSnapshotRemoteMetadataTaskType")) model = Model.from_dict(obj.get("Model")) + model_apply_startup_overlay_request = ModelApplyStartupOverlayRequest.from_dict(obj.get("ModelApplyStartupOverlayRequest")) model_billing = ModelBilling.from_dict(obj.get("ModelBilling")) model_billing_promo = ModelBillingPromo.from_dict(obj.get("ModelBillingPromo")) model_billing_token_prices = ModelBillingTokenPrices.from_dict(obj.get("ModelBillingTokenPrices")) @@ -29742,16 +37216,23 @@ def from_dict(obj: Any) -> 'RPC': model_capabilities_supports = ModelCapabilitiesSupports.from_dict(obj.get("ModelCapabilitiesSupports")) model_list = ModelList.from_dict(obj.get("ModelList")) model_list_request = obj.get("ModelListRequest") + model_message = ModelMessage.from_dict(obj.get("ModelMessage")) model_picker_category = ModelPickerCategory(obj.get("ModelPickerCategory")) + model_picker_persistence_request = ModelPickerPersistenceRequest.from_dict(obj.get("ModelPickerPersistenceRequest")) model_picker_price_category = ModelPickerPriceCategory(obj.get("ModelPickerPriceCategory")) + model_picker_settings_context = ModelPickerSettingsContext.from_dict(obj.get("ModelPickerSettingsContext")) model_policy = ModelPolicy.from_dict(obj.get("ModelPolicy")) model_policy_state = ModelPolicyState(obj.get("ModelPolicyState")) model_set_reasoning_effort_request = ModelSetReasoningEffortRequest.from_dict(obj.get("ModelSetReasoningEffortRequest")) model_set_reasoning_effort_result = ModelSetReasoningEffortResult.from_dict(obj.get("ModelSetReasoningEffortResult")) models_list_request = ModelsListRequest.from_dict(obj.get("ModelsListRequest")) + model_switch_confirmation = ModelSwitchConfirmation.from_dict(obj.get("ModelSwitchConfirmation")) model_switch_to_request = ModelSwitchToRequest.from_dict(obj.get("ModelSwitchToRequest")) model_switch_to_result = ModelSwitchToResult.from_dict(obj.get("ModelSwitchToResult")) + model_warning_text = ModelWarningText.from_dict(obj.get("ModelWarningText")) mode_set_request = ModeSetRequest.from_dict(obj.get("ModeSetRequest")) + mode_set_result = ModeSetResult.from_dict(obj.get("ModeSetResult")) + move_mcp_loading_to_background_result = MoveMCPLoadingToBackgroundResult.from_dict(obj.get("MoveMcpLoadingToBackgroundResult")) named_provider_config = NamedProviderConfig.from_dict(obj.get("NamedProviderConfig")) name_get_result = NameGetResult.from_dict(obj.get("NameGetResult")) name_set_auto_request = NameSetAutoRequest.from_dict(obj.get("NameSetAutoRequest")) @@ -29776,6 +37257,7 @@ def from_dict(obj: Any) -> 'RPC': permission_decision_approve_for_location_approval = _load_PermissionDecisionApproveForLocationApproval(obj.get("PermissionDecisionApproveForLocationApproval")) permission_decision_approve_for_location_approval_commands = PermissionDecisionApproveForLocationApprovalCommands.from_dict(obj.get("PermissionDecisionApproveForLocationApprovalCommands")) permission_decision_approve_for_location_approval_custom_tool = PermissionDecisionApproveForLocationApprovalCustomTool.from_dict(obj.get("PermissionDecisionApproveForLocationApprovalCustomTool")) + permission_decision_approve_for_location_approval_extension_env_access = PermissionDecisionApproveForLocationApprovalExtensionEnvAccess.from_dict(obj.get("PermissionDecisionApproveForLocationApprovalExtensionEnvAccess")) 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")) @@ -29788,6 +37270,7 @@ def from_dict(obj: Any) -> 'RPC': permission_decision_approve_for_session_approval = _load_PermissionDecisionApproveForSessionApproval(obj.get("PermissionDecisionApproveForSessionApproval")) permission_decision_approve_for_session_approval_commands = PermissionDecisionApproveForSessionApprovalCommands.from_dict(obj.get("PermissionDecisionApproveForSessionApprovalCommands")) permission_decision_approve_for_session_approval_custom_tool = PermissionDecisionApproveForSessionApprovalCustomTool.from_dict(obj.get("PermissionDecisionApproveForSessionApprovalCustomTool")) + permission_decision_approve_for_session_approval_extension_env_access = PermissionDecisionApproveForSessionApprovalExtensionEnvAccess.from_dict(obj.get("PermissionDecisionApproveForSessionApprovalExtensionEnvAccess")) 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")) @@ -29799,13 +37282,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")) @@ -29813,6 +37300,7 @@ def from_dict(obj: Any) -> 'RPC': permission_location_resolve_params = PermissionLocationResolveParams.from_dict(obj.get("PermissionLocationResolveParams")) permission_location_resolve_result = PermissionLocationResolveResult.from_dict(obj.get("PermissionLocationResolveResult")) permission_location_type = PermissionLocationType(obj.get("PermissionLocationType")) + permission_mode_source = PermissionSource(obj.get("PermissionModeSource")) permission_paths_add_params = PermissionPathsAddParams.from_dict(obj.get("PermissionPathsAddParams")) permission_paths_allowed_check_params = PermissionPathsAllowedCheckParams.from_dict(obj.get("PermissionPathsAllowedCheckParams")) permission_paths_allowed_check_result = PermissionPathsAllowedCheckResult.from_dict(obj.get("PermissionPathsAllowedCheckResult")) @@ -29823,8 +37311,8 @@ def from_dict(obj: Any) -> 'RPC': permission_paths_workspace_check_result = PermissionPathsWorkspaceCheckResult.from_dict(obj.get("PermissionPathsWorkspaceCheckResult")) permission_prompt_shown_notification = PermissionPromptShownNotification.from_dict(obj.get("PermissionPromptShownNotification")) permission_request_result = PermissionRequestResult.from_dict(obj.get("PermissionRequestResult")) + permission_response_capability = PermissionResponseCapability(obj.get("PermissionResponseCapability")) permission_rules_set = PermissionRulesSet.from_dict(obj.get("PermissionRulesSet")) - permissions_allow_all_mode = PermissionsAllowAllMode(obj.get("PermissionsAllowAllMode")) permissions_configure_additional_content_exclusion_policy = PermissionsConfigureAdditionalContentExclusionPolicy.from_dict(obj.get("PermissionsConfigureAdditionalContentExclusionPolicy")) permissions_configure_additional_content_exclusion_policy_rule = PermissionsConfigureAdditionalContentExclusionPolicyRule.from_dict(obj.get("PermissionsConfigureAdditionalContentExclusionPolicyRule")) permissions_configure_additional_content_exclusion_policy_rule_source = PermissionsConfigureAdditionalContentExclusionPolicyRuleSource.from_dict(obj.get("PermissionsConfigureAdditionalContentExclusionPolicyRuleSource")) @@ -29832,10 +37320,12 @@ def from_dict(obj: Any) -> 'RPC': permissions_configure_params = PermissionsConfigureParams.from_dict(obj.get("PermissionsConfigureParams")) permissions_configure_result = PermissionsConfigureResult.from_dict(obj.get("PermissionsConfigureResult")) permissions_folder_trust_add_trusted_result = PermissionsFolderTrustAddTrustedResult.from_dict(obj.get("PermissionsFolderTrustAddTrustedResult")) - permissions_get_allow_all_request = PermissionsGetAllowAllRequest.from_dict(obj.get("PermissionsGetAllowAllRequest")) + permissions_get_mode_request = PermissionsGetModeRequest.from_dict(obj.get("PermissionsGetModeRequest")) + permissions_get_mode_result = PermissionsGetModeResult.from_dict(obj.get("PermissionsGetModeResult")) permissions_locations_add_tool_approval_details = _load_PermissionsLocationsAddToolApprovalDetails(obj.get("PermissionsLocationsAddToolApprovalDetails")) permissions_locations_add_tool_approval_details_commands = PermissionsLocationsAddToolApprovalDetailsCommands.from_dict(obj.get("PermissionsLocationsAddToolApprovalDetailsCommands")) permissions_locations_add_tool_approval_details_custom_tool = PermissionsLocationsAddToolApprovalDetailsCustomTool.from_dict(obj.get("PermissionsLocationsAddToolApprovalDetailsCustomTool")) + permissions_locations_add_tool_approval_details_extension_env_access = PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess.from_dict(obj.get("PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess")) 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")) @@ -29855,11 +37345,11 @@ def from_dict(obj: Any) -> 'RPC': permissions_pending_requests_request = PermissionsPendingRequestsRequest.from_dict(obj.get("PermissionsPendingRequestsRequest")) permissions_reset_session_approvals_request = PermissionsResetSessionApprovalsRequest.from_dict(obj.get("PermissionsResetSessionApprovalsRequest")) permissions_reset_session_approvals_result = PermissionsResetSessionApprovalsResult.from_dict(obj.get("PermissionsResetSessionApprovalsResult")) - permissions_set_allow_all_request = PermissionsSetAllowAllRequest.from_dict(obj.get("PermissionsSetAllowAllRequest")) - permissions_set_allow_all_source = PermissionsSetAAllSource(obj.get("PermissionsSetAllowAllSource")) permissions_set_approve_all_request = PermissionsSetApproveAllRequest.from_dict(obj.get("PermissionsSetApproveAllRequest")) permissions_set_approve_all_result = PermissionsSetApproveAllResult.from_dict(obj.get("PermissionsSetApproveAllResult")) - permissions_set_approve_all_source = PermissionsSetAAllSource(obj.get("PermissionsSetApproveAllSource")) + permissions_set_approve_all_source = PermissionSource(obj.get("PermissionsSetApproveAllSource")) + permissions_set_mode_request = PermissionsSetModeRequest.from_dict(obj.get("PermissionsSetModeRequest")) + permissions_set_mode_result = PermissionsSetModeResult.from_dict(obj.get("PermissionsSetModeResult")) permissions_set_required_request = PermissionsSetRequiredRequest.from_dict(obj.get("PermissionsSetRequiredRequest")) permissions_set_required_result = PermissionsSetRequiredResult.from_dict(obj.get("PermissionsSetRequiredResult")) permissions_urls_set_unrestricted_mode_result = PermissionsUrlsSetUnrestrictedModeResult.from_dict(obj.get("PermissionsUrlsSetUnrestrictedModeResult")) @@ -29877,6 +37367,7 @@ def from_dict(obj: Any) -> 'RPC': plugin_install_result = PluginInstallResult.from_dict(obj.get("PluginInstallResult")) plugin_list = PluginList.from_dict(obj.get("PluginList")) plugin_list_result = PluginListResult.from_dict(obj.get("PluginListResult")) + plugins_builtin_set_request = PluginsBuiltinSetRequest.from_dict(obj.get("PluginsBuiltinSetRequest")) plugins_disable_request = PluginsDisableRequest.from_dict(obj.get("PluginsDisableRequest")) plugins_enable_request = PluginsEnableRequest.from_dict(obj.get("PluginsEnableRequest")) plugins_install_request = PluginsInstallRequest.from_dict(obj.get("PluginsInstallRequest")) @@ -29890,6 +37381,8 @@ def from_dict(obj: Any) -> 'RPC': plugin_update_all_entry = PluginUpdateAllEntry.from_dict(obj.get("PluginUpdateAllEntry")) plugin_update_all_result = PluginUpdateAllResult.from_dict(obj.get("PluginUpdateAllResult")) plugin_update_result = PluginUpdateResult.from_dict(obj.get("PluginUpdateResult")) + protocol_external_tool_defer = MCPServerConfigDeferTools(obj.get("ProtocolExternalToolDefer")) + protocol_external_tool_definition = ProtocolExternalToolDefinition.from_dict(obj.get("ProtocolExternalToolDefinition")) provider_add_request = ProviderAddRequest.from_dict(obj.get("ProviderAddRequest")) provider_add_result = ProviderAddResult.from_dict(obj.get("ProviderAddResult")) provider_config = ProviderConfig.from_dict(obj.get("ProviderConfig")) @@ -29979,6 +37472,7 @@ def from_dict(obj: Any) -> 'RPC': remote_notify_steerable_changed_request = RemoteNotifySteerableChangedRequest.from_dict(obj.get("RemoteNotifySteerableChangedRequest")) remote_notify_steerable_changed_result = RemoteNotifySteerableChangedResult.from_dict(obj.get("RemoteNotifySteerableChangedResult")) remote_session_connection_result = RemoteSessionConnectionResult.from_dict(obj.get("RemoteSessionConnectionResult")) + remote_session_host_status = RemoteSessionHostStatus(obj.get("RemoteSessionHostStatus")) remote_session_metadata_repository = RemoteSessionMetadataRepository.from_dict(obj.get("RemoteSessionMetadataRepository")) remote_session_metadata_task_type = TaskType(obj.get("RemoteSessionMetadataTaskType")) remote_session_metadata_value = RemoteSessionMetadataValue.from_dict(obj.get("RemoteSessionMetadataValue")) @@ -29986,6 +37480,8 @@ 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_source = _SandboxConfigSource(obj.get("SandboxConfigSource")) 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")) @@ -29993,6 +37489,7 @@ def from_dict(obj: Any) -> 'RPC': 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")) + sandbox_enforcement_status = SandboxEnforcementStatus.from_dict(obj.get("SandboxEnforcementStatus")) 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")) @@ -30021,7 +37518,10 @@ def from_dict(obj: Any) -> 'RPC': 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_login_request = SessionAuthLoginRequest.from_dict(obj.get("SessionAuthLoginRequest")) + session_auth_logout_user_request = SessionAuthLogoutUserRequest.from_dict(obj.get("SessionAuthLogoutUserRequest")) session_auth_status = SessionAuthStatus.from_dict(obj.get("SessionAuthStatus")) + session_auth_switch_request = SessionAuthSwitchRequest.from_dict(obj.get("SessionAuthSwitchRequest")) 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")) @@ -30063,6 +37563,9 @@ def from_dict(obj: Any) -> 'RPC': 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_git_hub_auth_get_all_auth_available_result = from_list(SessionAuthStatus.from_dict, obj.get("SessionGitHubAuthGetAllAuthAvailableResult")) + session_git_hub_auth_logout_result = from_bool(obj.get("SessionGitHubAuthLogoutResult")) + session_git_hub_auth_logout_user_result = from_bool(obj.get("SessionGitHubAuthLogoutUserResult")) 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")) @@ -30084,6 +37587,8 @@ def from_dict(obj: Any) -> 'RPC': 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")) @@ -30156,6 +37661,7 @@ def from_dict(obj: Any) -> 'RPC': sessions_open_status = SessionsOpenStatus(obj.get("SessionsOpenStatus")) session_source = SessionSource(obj.get("SessionSource")) sessions_prune_old_request = SessionsPruneOldRequest.from_dict(obj.get("SessionsPruneOldRequest")) + sessions_read_persisted_events_request = SessionsReadPersistedEventsRequest.from_dict(obj.get("SessionsReadPersistedEventsRequest")) sessions_register_extension_tools_on_session_options = SessionsRegisterExtensionToolsOnSessionOptions.from_dict(obj.get("SessionsRegisterExtensionToolsOnSessionOptions")) sessions_release_lock_request = SessionsReleaseLockRequest.from_dict(obj.get("SessionsReleaseLockRequest")) sessions_release_lock_result = SessionsReleaseLockResult.from_dict(obj.get("SessionsReleaseLockResult")) @@ -30175,7 +37681,10 @@ def from_dict(obj: Any) -> 'RPC': session_visibility_status = SessionVisibilityStatus(obj.get("SessionVisibilityStatus")) session_working_directory_context = SessionWorkingDirectoryContext.from_dict(obj.get("SessionWorkingDirectoryContext")) session_working_directory_context_host_type = HostType(obj.get("SessionWorkingDirectoryContextHostType")) + settable_auth_info = _load_SettableAuthInfo(obj.get("SettableAuthInfo")) + settable_token_auth_info = SettableTokenAuthInfo.from_dict(obj.get("SettableTokenAuthInfo")) shell_cancel_user_requested_request = ShellCancelUserRequestedRequest.from_dict(obj.get("ShellCancelUserRequestedRequest")) + shell_credentials = ShellCredentials.from_dict(obj.get("ShellCredentials")) 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")) @@ -30192,7 +37701,13 @@ def from_dict(obj: Any) -> 'RPC': skill_discovery_path_list = SkillDiscoveryPathList.from_dict(obj.get("SkillDiscoveryPathList")) skill_discovery_scope = SkillDiscoveryScope(obj.get("SkillDiscoveryScope")) skill_list = SkillList.from_dict(obj.get("SkillList")) + skill_provider_descriptor = SkillProviderDescriptor.from_dict(obj.get("SkillProviderDescriptor")) + skill_provider_list_request = SkillProviderListRequest.from_dict(obj.get("SkillProviderListRequest")) + skill_provider_list_result = _SkillProviderListResult.from_dict(obj.get("SkillProviderListResult")) + skill_provider_read_request = _SkillProviderReadRequest.from_dict(obj.get("SkillProviderReadRequest")) + skill_provider_read_result = _SkillProviderReadResult.from_dict(obj.get("SkillProviderReadResult")) skills_config_set_disabled_skills_request = SkillsConfigSetDisabledSkillsRequest.from_dict(obj.get("SkillsConfigSetDisabledSkillsRequest")) + skills_config_set_skill_disabled_request = SkillsConfigSetSkillDisabledRequest.from_dict(obj.get("SkillsConfigSetSkillDisabledRequest")) skills_disable_request = SkillsDisableRequest.from_dict(obj.get("SkillsDisableRequest")) skills_discover_request = SkillsDiscoverRequest.from_dict(obj.get("SkillsDiscoverRequest")) skills_enable_request = SkillsEnableRequest.from_dict(obj.get("SkillsEnableRequest")) @@ -30200,6 +37715,7 @@ def from_dict(obj: Any) -> 'RPC': skills_get_invoked_result = SkillsGetInvokedResult.from_dict(obj.get("SkillsGetInvokedResult")) skills_invoked_skill = SkillsInvokedSkill.from_dict(obj.get("SkillsInvokedSkill")) skills_load_diagnostics = SkillsLoadDiagnostics.from_dict(obj.get("SkillsLoadDiagnostics")) + slash_command_add_timeline_entry_result = SlashCommandAddTimelineEntryResult.from_dict(obj.get("SlashCommandAddTimelineEntryResult")) slash_command_agent_prompt_result = SlashCommandAgentPromptResult.from_dict(obj.get("SlashCommandAgentPromptResult")) slash_command_completed_result = SlashCommandCompletedResult.from_dict(obj.get("SlashCommandCompletedResult")) slash_command_info = SlashCommandInfo.from_dict(obj.get("SlashCommandInfo")) @@ -30208,13 +37724,20 @@ def from_dict(obj: Any) -> 'RPC': slash_command_input_completion = SlashCommandInputCompletion(obj.get("SlashCommandInputCompletion")) slash_command_invocation_result = _load_SlashCommandInvocationResult(obj.get("SlashCommandInvocationResult")) slash_command_kind = SlashCommandKind(obj.get("SlashCommandKind")) + slash_command_model_picker_dialog = SlashCommandModelPickerDialog.from_dict(obj.get("SlashCommandModelPickerDialog")) slash_command_select_subcommand_option = SlashCommandSelectSubcommandOption.from_dict(obj.get("SlashCommandSelectSubcommandOption")) slash_command_select_subcommand_result = SlashCommandSelectSubcommandResult.from_dict(obj.get("SlashCommandSelectSubcommandResult")) + slash_command_set_model_result = SlashCommandSetModelResult.from_dict(obj.get("SlashCommandSetModelResult")) + slash_command_set_plan_model_result = SlashCommandSetPlanModelResult.from_dict(obj.get("SlashCommandSetPlanModelResult")) + slash_command_show_dialog_result = SlashCommandShowDialogResult.from_dict(obj.get("SlashCommandShowDialogResult")) slash_command_text_result = SlashCommandTextResult.from_dict(obj.get("SlashCommandTextResult")) + slash_command_timeline_entry = SlashCommandTimelineEntry.from_dict(obj.get("SlashCommandTimelineEntry")) subagent_settings_entry = SubagentSettingsEntry.from_dict(obj.get("SubagentSettingsEntry")) subagent_settings_entry_context_tier = SubagentSettingsEntryContextTier(obj.get("SubagentSettingsEntryContextTier")) task_agent_info = TaskAgentInfo.from_dict(obj.get("TaskAgentInfo")) task_agent_progress = TaskAgentProgress.from_dict(obj.get("TaskAgentProgress")) + task_complete_data = TaskCompleteData.from_dict(obj.get("TaskCompleteData")) + task_completion_decision = TaskCompletionDecision.from_dict(obj.get("TaskCompletionDecision")) task_execution_mode = TaskExecutionMode(obj.get("TaskExecutionMode")) task_info = _load_TaskInfo(obj.get("TaskInfo")) task_list = TaskList.from_dict(obj.get("TaskList")) @@ -30241,11 +37764,23 @@ def from_dict(obj: Any) -> 'RPC': tasks_wait_for_pending_result = TasksWaitForPendingResult.from_dict(obj.get("TasksWaitForPendingResult")) telemetry_set_feature_overrides_request = TelemetrySetFeatureOverridesRequest.from_dict(obj.get("TelemetrySetFeatureOverridesRequest")) token_auth_info = TokenAuthInfo.from_dict(obj.get("TokenAuthInfo")) + token_provider_auth_info = TokenProviderAuthInfo.from_dict(obj.get("TokenProviderAuthInfo")) tool = Tool.from_dict(obj.get("Tool")) tool_list = ToolList.from_dict(obj.get("ToolList")) + tool_result = from_union([ToolResultExpanded.from_dict, from_str], obj.get("ToolResult")) + tool_result_expanded = ToolResultExpanded.from_dict(obj.get("ToolResultExpanded")) + tool_result_new_message = ToolResultNewMessage.from_dict(obj.get("ToolResultNewMessage")) + tool_result_type = ToolResultType(obj.get("ToolResultType")) + tools_execute_request = ToolsExecuteRequest.from_dict(obj.get("ToolsExecuteRequest")) + tools_get_builtin_descriptors_request = ToolsGetBuiltinDescriptorsRequest.from_dict(obj.get("ToolsGetBuiltinDescriptorsRequest")) + tools_get_builtin_descriptors_result = ToolsGetBuiltinDescriptorsResult.from_dict(obj.get("ToolsGetBuiltinDescriptorsResult")) tools_get_current_metadata_result = ToolsGetCurrentMetadataResult.from_dict(obj.get("ToolsGetCurrentMetadataResult")) tools_initialize_and_validate_result = ToolsInitializeAndValidateResult.from_dict(obj.get("ToolsInitializeAndValidateResult")) tools_list_request = ToolsListRequest.from_dict(obj.get("ToolsListRequest")) + tools_set_request = ToolsSetRequest.from_dict(obj.get("ToolsSetRequest")) + tools_set_result = ToolsSetResult.from_dict(obj.get("ToolsSetResult")) + tools_shell_descriptor_config = ToolsShellDescriptorConfig.from_dict(obj.get("ToolsShellDescriptorConfig")) + tools_task_complete_event_data_request = ToolsTaskCompleteEventDataRequest.from_dict(obj.get("ToolsTaskCompleteEventDataRequest")) tools_update_subagent_settings_result = ToolsUpdateSubagentSettingsResult.from_dict(obj.get("ToolsUpdateSubagentSettingsResult")) ui_auto_mode_switch_response = UIAutoModeSwitchResponse(obj.get("UIAutoModeSwitchResponse")) ui_elicitation_array_any_of_field = UIElicitationArrayAnyOfField.from_dict(obj.get("UIElicitationArrayAnyOfField")) @@ -30289,6 +37824,7 @@ def from_dict(obj: Any) -> 'RPC': ui_user_input_response = UIUserInputResponse.from_dict(obj.get("UIUserInputResponse")) update_subagent_settings_request = UpdateSubagentSettingsRequest.from_dict(obj.get("UpdateSubagentSettingsRequest")) usage_get_metrics_result = UsageGetMetricsResult.from_dict(obj.get("UsageGetMetricsResult")) + usage_metrics_agent_metric = UsageMetricsAgentMetric.from_dict(obj.get("UsageMetricsAgentMetric")) usage_metrics_code_changes = UsageMetricsCodeChanges.from_dict(obj.get("UsageMetricsCodeChanges")) usage_metrics_model_metric = UsageMetricsModelMetric.from_dict(obj.get("UsageMetricsModelMetric")) usage_metrics_model_metric_requests = UsageMetricsModelMetricRequests.from_dict(obj.get("UsageMetricsModelMetricRequests")) @@ -30332,12 +37868,13 @@ def from_dict(obj: Any) -> 'RPC': 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_auth_info_result = from_union([AuthIdentity.from_dict, from_none], obj.get("SessionAuthInfoResult")) 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_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, 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_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, 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_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_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_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_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) + 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, api_key_auth_info, auth_identity, auth_info, auth_info_type, auth_validation_error, auth_validation_errors, built_in_model_catalog, built_in_model_catalog_entry, builtin_tool_descriptor, builtin_tool_format, builtin_tool_format_type, builtin_tool_input_schema, builtin_tool_input_schema_type, builtin_tool_safe_for_telemetry, builtin_tool_safe_telemetry_fields, 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_provider_register_request, canvas_provider_unregister_request, canvas_session_context, capi_session_options, card_digest, card_digest_algorithm, card_digest_value, catalog_ai_skill_candidate, catalog_ai_skill_candidate_provenance, catalog_authentication_required_error, catalog_authentication_required_reason, catalog_candidate, catalog_candidate_kind, catalog_candidate_source, catalog_candidate_source_embedded, catalog_candidate_source_url, catalog_capability, catalog_capability_id, catalog_client_contract, catalog_contract_violation_error, catalog_contract_violation_reason, catalog_handle_rejected_error, catalog_handle_rejection_reason, catalog_handle_type, catalog_invalid_request_error, catalog_invalid_request_field, catalog_malformed_card_error, catalog_malformed_card_reason, catalog_mcp_server_candidate, catalog_mcp_server_candidate_provenance, catalog_mcp_server_installability, catalog_media_type, catalog_negotiated_contract, catalog_negotiation_refused_error, catalog_negotiation_refused_reason, catalog_network_failure_error, catalog_network_failure_reason, catalog_not_installable_error, catalog_not_installable_reason, catalog_policy_rejected_error, catalog_search_request, catalog_search_result, catalog_search_succeeded, catalog_unavailable_error, catalog_unavailable_reason, catalog_unavailable_transport_error, catalog_unavailable_transport_reason, catalog_unsafe_retrieval_error, catalog_unsafe_retrieval_reason, catalog_unsupported_kind_error, command_list, commands_finalize_invocation_effect_request, commands_finalize_invocation_effect_result, commands_handle_pending_command_request, commands_handle_pending_command_result, commands_invocation_effect_outcome, commands_invocation_origin, 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, connect_client_info, 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, discovered_canvas, discovered_extension, discovered_extension_mode, discovered_extension_plugin, discovered_extensions, discovered_extensions_disable_request, discovered_extensions_enable_request, discovered_extension_source, discovered_hook, 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, factory_tool_resume_request, factory_tool_run_options, factory_tool_run_request, 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, git_hub_token_acquire_reason, git_hub_token_acquire_request, git_hub_token_acquire_result, 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_origin, hooks_discover_request, hooks_discover_result, 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_elicitation_form_mode, mcp_enable_request, mcp_execute_sampling_params, mcp_execute_sampling_request, mcp_execute_sampling_result, mcp_failed_server, 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_install_plan, 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_probe_needs_auth_reason, mcp_oauth_probe_request, mcp_oauth_probe_result, mcp_oauth_respond_request, mcp_oauth_respond_result, mcp_plan_configuration_change, mcp_plan_configuration_operation, mcp_plan_enum_value_type, mcp_plan_install_planned, mcp_plan_install_request, mcp_plan_install_result, mcp_plan_install_source, mcp_plan_install_source_candidate, mcp_plan_install_source_candidate_kind, mcp_plan_install_source_card, mcp_plan_install_source_card_kind, mcp_plan_package_install_method, mcp_plan_package_transport, mcp_plan_policy_decision, mcp_plan_policy_result, mcp_plan_policy_source, mcp_plan_provenance, mcp_plan_remote_install_method, mcp_plan_remote_transport, mcp_plan_required_value, mcp_plan_required_value_enum, mcp_plan_required_value_enum_kind, mcp_plan_required_value_scalar, mcp_plan_required_value_scalar_kind, mcp_plan_resource_identity, mcp_plan_scalar_value_type, mcp_plan_scope, mcp_plan_secret_placeholder, mcp_plan_secret_reference, mcp_plan_target, mcp_plan_transport_choice, mcp_plan_transport_choice_package, mcp_plan_transport_choice_remote, mcp_plan_value_category, mcp_register_external_client_request, mcp_reload_config, 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_safe_for_telemetry, mcp_safe_for_telemetry_fields, mcp_sampling_execution_action, mcp_sampling_execution_result, mcp_serializable_server_config, mcp_server, mcp_server_auth_config, mcp_server_auth_config_redirect_port, mcp_server_card_embedded, mcp_server_card_embedded_kind, mcp_server_card_media_type, mcp_server_card_reference, mcp_server_card_url, mcp_server_card_url_kind, 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_memory, mcp_server_config_memory_type, mcp_server_config_stdio, mcp_server_config_stdio_type, 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_task_metadata, 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_apply_startup_overlay_request, 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_message, model_picker_category, model_picker_persistence_request, model_picker_price_category, model_picker_settings_context, model_policy, model_policy_state, model_set_reasoning_effort_request, model_set_reasoning_effort_result, models_list_request, model_switch_confirmation, model_switch_to_request, model_switch_to_result, model_warning_text, mode_set_request, mode_set_result, move_mcp_loading_to_background_result, 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_env_access, 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_env_access, 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_mode_source, 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_response_capability, permission_rules_set, 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_mode_request, permissions_get_mode_result, 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_env_access, 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_approve_all_request, permissions_set_approve_all_result, permissions_set_approve_all_source, permissions_set_mode_request, permissions_set_mode_result, 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_builtin_set_request, 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, protocol_external_tool_defer, protocol_external_tool_definition, 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_host_status, 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_source, 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, sandbox_enforcement_status, 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_login_request, session_auth_logout_user_request, session_auth_status, session_auth_switch_request, 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_git_hub_auth_get_all_auth_available_result, session_git_hub_auth_logout_result, session_git_hub_auth_logout_user_result, 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_read_persisted_events_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, settable_auth_info, settable_token_auth_info, shell_cancel_user_requested_request, shell_credentials, 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, skill_provider_descriptor, skill_provider_list_request, skill_provider_list_result, skill_provider_read_request, skill_provider_read_result, skills_config_set_disabled_skills_request, skills_config_set_skill_disabled_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_add_timeline_entry_result, 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_model_picker_dialog, slash_command_select_subcommand_option, slash_command_select_subcommand_result, slash_command_set_model_result, slash_command_set_plan_model_result, slash_command_show_dialog_result, slash_command_text_result, slash_command_timeline_entry, subagent_settings_entry, subagent_settings_entry_context_tier, task_agent_info, task_agent_progress, task_complete_data, task_completion_decision, 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, token_provider_auth_info, tool, tool_list, tool_result, tool_result_expanded, tool_result_new_message, tool_result_type, tools_execute_request, tools_get_builtin_descriptors_request, tools_get_builtin_descriptors_result, tools_get_current_metadata_result, tools_initialize_and_validate_result, tools_list_request, tools_set_request, tools_set_result, tools_shell_descriptor_config, tools_task_complete_event_data_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_agent_metric, 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_auth_info_result, session_context_attribution, session_context_info, subagent_settings, task_progress, workspace_summary) def to_dict(self) -> dict: result: dict = {} @@ -30384,13 +37921,21 @@ def to_dict(self) -> dict: 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["AuthIdentity"] = to_class(AuthIdentity, self.auth_identity) result["AuthInfo"] = (self.auth_info).to_dict() result["AuthInfoType"] = to_enum(AuthInfoType, self.auth_info_type) + result["AuthValidationError"] = to_class(AuthValidationError, self.auth_validation_error) + result["AuthValidationErrors"] = from_list(lambda x: to_class(AuthValidationError, x), self.auth_validation_errors) result["BuiltInModelCatalog"] = to_class(BuiltInModelCatalog, self.built_in_model_catalog) result["BuiltInModelCatalogEntry"] = to_class(BuiltInModelCatalogEntry, self.built_in_model_catalog_entry) + result["BuiltinToolDescriptor"] = to_class(BuiltinToolDescriptor, self.builtin_tool_descriptor) + result["BuiltinToolFormat"] = to_class(BuiltinToolFormat, self.builtin_tool_format) + result["BuiltinToolFormatType"] = to_enum(BuiltinToolFormatType, self.builtin_tool_format_type) + result["BuiltinToolInputSchema"] = to_class(BuiltinToolInputSchema, self.builtin_tool_input_schema) + result["BuiltinToolInputSchemaType"] = to_enum(BuiltinToolInputSchemaType, self.builtin_tool_input_schema_type) + result["BuiltinToolSafeForTelemetry"] = from_union([from_bool, lambda x: to_class(BuiltinToolSafeTelemetryFields, x)], self.builtin_tool_safe_for_telemetry) + result["BuiltinToolSafeTelemetryFields"] = to_class(BuiltinToolSafeTelemetryFields, self.builtin_tool_safe_telemetry_fields) 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) @@ -30406,11 +37951,63 @@ def to_dict(self) -> dict: result["CanvasProviderInvokeActionRequest"] = to_class(CanvasProviderInvokeActionRequest, self.canvas_provider_invoke_action_request) result["CanvasProviderOpenRequest"] = to_class(CanvasProviderOpenRequest, self.canvas_provider_open_request) result["CanvasProviderOpenResult"] = to_class(CanvasProviderOpenResult, self.canvas_provider_open_result) + result["CanvasProviderRegisterRequest"] = to_class(CanvasProviderRegisterRequest, self.canvas_provider_register_request) + result["CanvasProviderUnregisterRequest"] = to_class(CanvasProviderUnregisterRequest, self.canvas_provider_unregister_request) result["CanvasSessionContext"] = to_class(CanvasSessionContext, self.canvas_session_context) result["CapiSessionOptions"] = to_class(CapiSessionOptions, self.capi_session_options) + result["CardDigest"] = to_class(CardDigest, self.card_digest) + result["CardDigestAlgorithm"] = to_enum(CardDigestAlgorithm, self.card_digest_algorithm) + result["CardDigestValue"] = from_str(self.card_digest_value) + result["CatalogAiSkillCandidate"] = to_class(CatalogAISkillCandidate, self.catalog_ai_skill_candidate) + result["CatalogAiSkillCandidateProvenance"] = to_class(CatalogAISkillCandidateProvenance, self.catalog_ai_skill_candidate_provenance) + result["CatalogAuthenticationRequiredError"] = to_class(CatalogAuthenticationRequiredError, self.catalog_authentication_required_error) + result["CatalogAuthenticationRequiredReason"] = to_enum(CatalogAuthenticationRequiredReason, self.catalog_authentication_required_reason) + result["CatalogCandidate"] = to_class(CatalogCandidate, self.catalog_candidate) + result["CatalogCandidateKind"] = to_enum(CatalogCandidateKind, self.catalog_candidate_kind) + result["CatalogCandidateSource"] = (self.catalog_candidate_source).to_dict() + result["CatalogCandidateSourceEmbedded"] = to_class(CatalogCandidateSourceEmbedded, self.catalog_candidate_source_embedded) + result["CatalogCandidateSourceUrl"] = to_class(CatalogCandidateSourceURL, self.catalog_candidate_source_url) + result["CatalogCapability"] = to_enum(CatalogCapability, self.catalog_capability) + result["CatalogCapabilityId"] = from_str(self.catalog_capability_id) + result["CatalogClientContract"] = to_class(CatalogClientContract, self.catalog_client_contract) + result["CatalogContractViolationError"] = to_class(CatalogContractViolationError, self.catalog_contract_violation_error) + result["CatalogContractViolationReason"] = to_enum(CatalogContractViolationReason, self.catalog_contract_violation_reason) + result["CatalogHandleRejectedError"] = to_class(CatalogHandleRejectedError, self.catalog_handle_rejected_error) + result["CatalogHandleRejectionReason"] = to_enum(CatalogHandleRejectionReason, self.catalog_handle_rejection_reason) + result["CatalogHandleType"] = to_enum(CatalogHandleType, self.catalog_handle_type) + result["CatalogInvalidRequestError"] = to_class(CatalogInvalidRequestError, self.catalog_invalid_request_error) + result["CatalogInvalidRequestField"] = to_enum(CatalogInvalidRequestField, self.catalog_invalid_request_field) + result["CatalogMalformedCardError"] = to_class(CatalogMalformedCardError, self.catalog_malformed_card_error) + result["CatalogMalformedCardReason"] = to_enum(CatalogMalformedCardReason, self.catalog_malformed_card_reason) + result["CatalogMcpServerCandidate"] = to_class(CatalogMCPServerCandidate, self.catalog_mcp_server_candidate) + result["CatalogMcpServerCandidateProvenance"] = to_class(CatalogMCPServerCandidateProvenance, self.catalog_mcp_server_candidate_provenance) + result["CatalogMcpServerInstallability"] = to_enum(CatalogMCPServerInstallabilityEnum, self.catalog_mcp_server_installability) + result["CatalogMediaType"] = to_enum(CatalogMediaType, self.catalog_media_type) + result["CatalogNegotiatedContract"] = to_class(CatalogNegotiatedContract, self.catalog_negotiated_contract) + result["CatalogNegotiationRefusedError"] = to_class(CatalogNegotiationRefusedError, self.catalog_negotiation_refused_error) + result["CatalogNegotiationRefusedReason"] = to_enum(CatalogNegotiationRefusedReason, self.catalog_negotiation_refused_reason) + result["CatalogNetworkFailureError"] = to_class(CatalogNetworkFailureError, self.catalog_network_failure_error) + result["CatalogNetworkFailureReason"] = to_enum(CatalogNetworkFailureReason, self.catalog_network_failure_reason) + result["CatalogNotInstallableError"] = to_class(CatalogNotInstallableError, self.catalog_not_installable_error) + result["CatalogNotInstallableReason"] = to_enum(CatalogNotInstallableReason, self.catalog_not_installable_reason) + result["CatalogPolicyRejectedError"] = to_class(CatalogPolicyRejectedError, self.catalog_policy_rejected_error) + result["CatalogSearchRequest"] = to_class(CatalogSearchRequest, self.catalog_search_request) + result["CatalogSearchResult"] = (self.catalog_search_result).to_dict() + result["CatalogSearchSucceeded"] = to_class(CatalogSearchSucceeded, self.catalog_search_succeeded) + result["CatalogUnavailableError"] = to_class(CatalogUnavailableError, self.catalog_unavailable_error) + result["CatalogUnavailableReason"] = to_enum(CatalogUnavailableReason, self.catalog_unavailable_reason) + result["CatalogUnavailableTransportError"] = to_class(CatalogUnavailableTransportError, self.catalog_unavailable_transport_error) + result["CatalogUnavailableTransportReason"] = to_enum(CatalogUnavailableTransportReason, self.catalog_unavailable_transport_reason) + result["CatalogUnsafeRetrievalError"] = to_class(CatalogUnsafeRetrievalError, self.catalog_unsafe_retrieval_error) + result["CatalogUnsafeRetrievalReason"] = to_enum(CatalogUnsafeRetrievalReason, self.catalog_unsafe_retrieval_reason) + result["CatalogUnsupportedKindError"] = to_class(CatalogUnsupportedKindError, self.catalog_unsupported_kind_error) result["CommandList"] = to_class(CommandList, self.command_list) + result["CommandsFinalizeInvocationEffectRequest"] = to_class(CommandsFinalizeInvocationEffectRequest, self.commands_finalize_invocation_effect_request) + result["CommandsFinalizeInvocationEffectResult"] = to_class(CommandsFinalizeInvocationEffectResult, self.commands_finalize_invocation_effect_result) result["CommandsHandlePendingCommandRequest"] = to_class(CommandsHandlePendingCommandRequest, self.commands_handle_pending_command_request) result["CommandsHandlePendingCommandResult"] = to_class(CommandsHandlePendingCommandResult, self.commands_handle_pending_command_result) + result["CommandsInvocationEffectOutcome"] = to_enum(CommandsInvocationEffectOutcome, self.commands_invocation_effect_outcome) + result["CommandsInvocationOrigin"] = to_enum(CommandsInvocationOrigin, self.commands_invocation_origin) result["CommandsInvokeRequest"] = to_class(CommandsInvokeRequest, self.commands_invoke_request) result["CommandsListRequest"] = self.commands_list_request result["CommandsRespondToQueuedCommandRequest"] = to_class(CommandsRespondToQueuedCommandRequest, self.commands_respond_to_queued_command_request) @@ -30419,6 +38016,7 @@ def to_dict(self) -> dict: result["CompletionsRequestRequest"] = to_class(CompletionsRequestRequest, self.completions_request_request) result["CompletionsRequestResult"] = to_class(CompletionsRequestResult, self.completions_request_result) result["ConfigureSessionExtensionsParams"] = to_class(_ConfigureSessionExtensionsParams, self.configure_session_extensions_params) + result["ConnectClientInfo"] = to_class(_ConnectClientInfo, self.connect_client_info) result["ConnectedRemoteSessionMetadata"] = to_class(ConnectedRemoteSessionMetadata, self.connected_remote_session_metadata) result["ConnectedRemoteSessionMetadataKind"] = to_enum(ConnectedRemoteSessionMetadataKind, self.connected_remote_session_metadata_kind) result["ConnectedRemoteSessionMetadataRepository"] = to_class(ConnectedRemoteSessionMetadataRepository, self.connected_remote_session_metadata_repository) @@ -30458,6 +38056,7 @@ def to_dict(self) -> dict: 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["DiscoveredHook"] = to_class(DiscoveredHook, self.discovered_hook) 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) @@ -30475,6 +38074,9 @@ def to_dict(self) -> dict: 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) @@ -30533,6 +38135,9 @@ def to_dict(self) -> dict: 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["FactoryToolResumeRequest"] = to_class(_FactoryToolResumeRequest, self.factory_tool_resume_request) + result["FactoryToolRunOptions"] = to_class(_FactoryToolRunOptions, self.factory_tool_run_options) + result["FactoryToolRunRequest"] = to_class(_FactoryToolRunRequest, self.factory_tool_run_request) 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) @@ -30543,6 +38148,9 @@ def to_dict(self) -> dict: result["GitHubTelemetryClientInfo"] = to_class(GitHubTelemetryClientInfo, self.git_hub_telemetry_client_info) result["GitHubTelemetryEvent"] = to_class(GitHubTelemetryEvent, self.git_hub_telemetry_event) result["GitHubTelemetryNotification"] = to_class(GitHubTelemetryNotification, self.git_hub_telemetry_notification) + result["GitHubTokenAcquireReason"] = to_enum(GitHubTokenAcquireReason, self.git_hub_token_acquire_reason) + result["GitHubTokenAcquireRequest"] = to_class(GitHubTokenAcquireRequest, self.git_hub_token_acquire_request) + result["GitHubTokenAcquireResult"] = to_class(GitHubTokenAcquireResult, self.git_hub_token_acquire_result) result["HandlePendingToolCallRequest"] = to_class(HandlePendingToolCallRequest, self.handle_pending_tool_call_request) result["HandlePendingToolCallResult"] = to_class(HandlePendingToolCallResult, self.handle_pending_tool_call_result) result["HistoryAbortManualCompactionResult"] = to_class(HistoryAbortManualCompactionResult, self.history_abort_manual_compaction_result) @@ -30571,7 +38179,10 @@ def to_dict(self) -> dict: result["HMACAuthInfo"] = to_class(HMACAuthInfo, self.hmac_auth_info) result["HookInvokeRequest"] = to_class(_HookInvokeRequest, self.hook_invoke_request) result["HookInvokeResponse"] = to_class(_HookInvokeResponse, self.hook_invoke_response) - result["HookType"] = to_enum(_HookType, self.hook_type) + result["HookOrigin"] = to_enum(HookOrigin, self.hook_origin) + result["HooksDiscoverRequest"] = to_class(HooksDiscoverRequest, self.hooks_discover_request) + result["HooksDiscoverResult"] = to_class(HooksDiscoverResult, self.hooks_discover_result) + result["HookType"] = to_enum(HookType, self.hook_type) result["InstalledPlugin"] = to_class(InstalledPlugin, self.installed_plugin) result["InstalledPluginInfo"] = to_class(InstalledPluginInfo, self.installed_plugin_info) result["InstalledPluginSource"] = from_union([lambda x: to_class(InstalledPluginSource, x), from_str], self.installed_plugin_source) @@ -30606,6 +38217,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) @@ -30650,15 +38262,18 @@ def to_dict(self) -> dict: result["McpDisableRequest"] = to_class(MCPDisableRequest, self.mcp_disable_request) result["McpDiscoverRequest"] = to_class(MCPDiscoverRequest, self.mcp_discover_request) result["McpDiscoverResult"] = to_class(MCPDiscoverResult, self.mcp_discover_result) + result["McpElicitationFormMode"] = to_enum(MCPElicitationFormMode, self.mcp_elicitation_form_mode) result["McpEnableRequest"] = to_class(MCPEnableRequest, self.mcp_enable_request) result["McpExecuteSamplingParams"] = to_class(MCPExecuteSamplingParams, self.mcp_execute_sampling_params) result["McpExecuteSamplingRequest"] = from_dict(lambda x: x, self.mcp_execute_sampling_request) result["McpExecuteSamplingResult"] = from_dict(lambda x: x, self.mcp_execute_sampling_result) + result["McpFailedServer"] = to_class(MCPFailedServer, self.mcp_failed_server) result["McpFilteredServer"] = to_class(MCPFilteredServer, self.mcp_filtered_server) result["McpHeadersHandlePendingHeadersRefreshRequest"] = to_class(MCPHeadersHandlePendingHeadersRefreshRequest, self.mcp_headers_handle_pending_headers_refresh_request) result["McpHeadersHandlePendingHeadersRefreshRequestRequest"] = to_class(MCPHeadersHandlePendingHeadersRefreshRequestRequest, self.mcp_headers_handle_pending_headers_refresh_request_request) result["McpHeadersHandlePendingHeadersRefreshRequestResult"] = to_class(MCPHeadersHandlePendingHeadersRefreshRequestResult, self.mcp_headers_handle_pending_headers_refresh_request_result) result["McpHostState"] = to_class(MCPHostState, self.mcp_host_state) + result["McpInstallPlan"] = to_class(MCPInstallPlan, self.mcp_install_plan) result["McpIsServerRunningRequest"] = to_class(MCPIsServerRunningRequest, self.mcp_is_server_running_request) result["McpIsServerRunningResult"] = to_class(MCPIsServerRunningResult, self.mcp_is_server_running_result) result["McpListToolsRequest"] = to_class(MCPListToolsRequest, self.mcp_list_tools_request) @@ -30670,9 +38285,47 @@ def to_dict(self) -> dict: 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["McpOauthProbeNeedsAuthReason"] = to_enum(MCPOauthProbeNeedsAuthReason, self.mcp_oauth_probe_needs_auth_reason) + result["McpOauthProbeRequest"] = to_class(MCPOauthProbeRequest, self.mcp_oauth_probe_request) + result["McpOauthProbeResult"] = to_class(MCPOauthProbeResult, self.mcp_oauth_probe_result) result["McpOauthRespondRequest"] = to_class(MCPOauthRespondRequest, self.mcp_oauth_respond_request) result["McpOauthRespondResult"] = to_class(MCPOauthRespondResult, self.mcp_oauth_respond_result) + result["McpPlanConfigurationChange"] = to_class(MCPPlanConfigurationChange, self.mcp_plan_configuration_change) + result["McpPlanConfigurationOperation"] = to_enum(MCPPlanConfigurationOperation, self.mcp_plan_configuration_operation) + result["McpPlanEnumValueType"] = to_enum(MCPPlan, self.mcp_plan_enum_value_type) + result["McpPlanInstallPlanned"] = to_class(MCPPlanInstallPlanned, self.mcp_plan_install_planned) + result["McpPlanInstallRequest"] = to_class(MCPPlanInstallRequest, self.mcp_plan_install_request) + result["McpPlanInstallResult"] = (self.mcp_plan_install_result).to_dict() + result["McpPlanInstallSource"] = (self.mcp_plan_install_source).to_dict() + result["McpPlanInstallSourceCandidate"] = to_class(MCPPlanInstallSourceCandidate, self.mcp_plan_install_source_candidate) + result["McpPlanInstallSourceCandidateKind"] = to_enum(MCPPlanInstallSourceCandidateKind, self.mcp_plan_install_source_candidate_kind) + result["McpPlanInstallSourceCard"] = to_class(MCPPlanInstallSourceCard, self.mcp_plan_install_source_card) + result["McpPlanInstallSourceCardKind"] = to_enum(MCPPlanInstallSourceCardKind, self.mcp_plan_install_source_card_kind) + result["McpPlanPackageInstallMethod"] = to_enum(MCPPlanPackageInstallMethod, self.mcp_plan_package_install_method) + result["McpPlanPackageTransport"] = to_enum(MCPPlanPackageTransport, self.mcp_plan_package_transport) + result["McpPlanPolicyDecision"] = to_enum(MCPPlanPolicyDecision, self.mcp_plan_policy_decision) + result["McpPlanPolicyResult"] = to_class(MCPPlanPolicyResult, self.mcp_plan_policy_result) + result["McpPlanPolicySource"] = to_enum(MCPPlanPolicySource, self.mcp_plan_policy_source) + result["McpPlanProvenance"] = to_class(MCPPlanProvenance, self.mcp_plan_provenance) + result["McpPlanRemoteInstallMethod"] = to_enum(MCPPlanRemoteInstallMethod, self.mcp_plan_remote_install_method) + result["McpPlanRemoteTransport"] = to_enum(MCPPlanRemoteTransport, self.mcp_plan_remote_transport) + result["McpPlanRequiredValue"] = (self.mcp_plan_required_value).to_dict() + result["McpPlanRequiredValueEnum"] = to_class(MCPPlanRequiredValueEnum, self.mcp_plan_required_value_enum) + result["McpPlanRequiredValueEnumKind"] = to_enum(MCPPlan, self.mcp_plan_required_value_enum_kind) + result["McpPlanRequiredValueScalar"] = to_class(MCPPlanRequiredValueScalar, self.mcp_plan_required_value_scalar) + result["McpPlanRequiredValueScalarKind"] = to_enum(MCPPlanRequiredValueScalarKind, self.mcp_plan_required_value_scalar_kind) + result["McpPlanResourceIdentity"] = to_class(MCPPlanResourceIdentity, self.mcp_plan_resource_identity) + result["McpPlanScalarValueType"] = to_enum(MCPPlanScalarValueTypeEnum, self.mcp_plan_scalar_value_type) + result["McpPlanScope"] = to_enum(MCPPlanScope, self.mcp_plan_scope) + result["McpPlanSecretPlaceholder"] = to_class(MCPPlanSecretPlaceholder, self.mcp_plan_secret_placeholder) + result["McpPlanSecretReference"] = from_str(self.mcp_plan_secret_reference) + result["McpPlanTarget"] = to_class(MCPPlanTarget, self.mcp_plan_target) + result["McpPlanTransportChoice"] = (self.mcp_plan_transport_choice).to_dict() + result["McpPlanTransportChoicePackage"] = to_class(MCPPlanTransportChoicePackage, self.mcp_plan_transport_choice_package) + result["McpPlanTransportChoiceRemote"] = to_class(MCPPlanTransportChoiceRemote, self.mcp_plan_transport_choice_remote) + result["McpPlanValueCategory"] = to_enum(MCPPlanValueCategory, self.mcp_plan_value_category) result["McpRegisterExternalClientRequest"] = to_class(MCPRegisterExternalClientRequest, self.mcp_register_external_client_request) + result["McpReloadConfig"] = to_class(MCPReloadConfig, self.mcp_reload_config) result["McpReloadWithConfigRequest"] = to_class(MCPReloadWithConfigRequest, self.mcp_reload_with_config_request) result["McpRemoveGitHubResult"] = to_class(MCPRemoveGitHubResult, self.mcp_remove_git_hub_result) result["McpResource"] = to_class(MCPResource, self.mcp_resource) @@ -30687,17 +38340,29 @@ def to_dict(self) -> dict: result["McpResourcesReadResult"] = to_class(MCPResourcesReadResult, self.mcp_resources_read_result) result["McpResourceTemplate"] = to_class(MCPResourceTemplate, self.mcp_resource_template) result["McpRestartServerRequest"] = to_class(MCPRestartServerRequest, self.mcp_restart_server_request) + result["McpSafeForTelemetry"] = from_union([from_bool, lambda x: to_class(MCPSafeForTelemetryFields, x)], self.mcp_safe_for_telemetry) + result["McpSafeForTelemetryFields"] = to_class(MCPSafeForTelemetryFields, self.mcp_safe_for_telemetry_fields) result["McpSamplingExecutionAction"] = to_enum(MCPSamplingExecutionAction, self.mcp_sampling_execution_action) result["McpSamplingExecutionResult"] = to_class(MCPSamplingExecutionResult, self.mcp_sampling_execution_result) + result["McpSerializableServerConfig"] = to_class(MCPSerializableServerConfig, self.mcp_serializable_server_config) result["McpServer"] = to_class(MCPServer, self.mcp_server) result["McpServerAuthConfig"] = from_union([from_bool, lambda x: to_class(MCPServerAuthConfigRedirectPort, x)], self.mcp_server_auth_config) result["McpServerAuthConfigRedirectPort"] = to_class(MCPServerAuthConfigRedirectPort, self.mcp_server_auth_config_redirect_port) + result["McpServerCardEmbedded"] = to_class(MCPServerCardEmbedded, self.mcp_server_card_embedded) + result["McpServerCardEmbeddedKind"] = to_enum(MCPServerCardEmbeddedKind, self.mcp_server_card_embedded_kind) + result["McpServerCardMediaType"] = to_enum(MCPServerCardMediaType, self.mcp_server_card_media_type) + result["McpServerCardReference"] = (self.mcp_server_card_reference).to_dict() + result["McpServerCardUrl"] = to_class(MCPServerCardURL, self.mcp_server_card_url) + result["McpServerCardUrlKind"] = to_enum(MCPServerCardURLKind, self.mcp_server_card_url_kind) result["McpServerConfig"] = to_class(MCPServerConfig, self.mcp_server_config) result["McpServerConfigDeferTools"] = to_enum(MCPServerConfigDeferTools, self.mcp_server_config_defer_tools) result["McpServerConfigHttp"] = to_class(MCPServerConfigHTTP, self.mcp_server_config_http) result["McpServerConfigHttpOauthGrantType"] = to_enum(MCPGrantType, self.mcp_server_config_http_oauth_grant_type) result["McpServerConfigHttpType"] = to_enum(MCPServerConfigHTTPType, self.mcp_server_config_http_type) + result["McpServerConfigMemory"] = to_class(MCPServerConfigMemory, self.mcp_server_config_memory) + result["McpServerConfigMemoryType"] = to_enum(MCPServerConfigMemoryType, self.mcp_server_config_memory_type) result["McpServerConfigStdio"] = to_class(MCPServerConfigStdio, self.mcp_server_config_stdio) + result["McpServerConfigStdioType"] = to_enum(MCPServerConfigStdioType, self.mcp_server_config_stdio_type) result["McpServerFailureInfo"] = to_class(MCPServerFailureInfo, self.mcp_server_failure_info) result["McpServerList"] = to_class(MCPServerList, self.mcp_server_list) result["McpServerNeedsAuthInfo"] = to_class(MCPServerNeedsAuthInfo, self.mcp_server_needs_auth_info) @@ -30707,6 +38372,7 @@ def to_dict(self) -> dict: result["McpStartServerRequest"] = to_class(MCPStartServerRequest, self.mcp_start_server_request) result["McpStartServersResult"] = to_class(MCPStartServersResult, self.mcp_start_servers_result) result["McpStopServerRequest"] = to_class(MCPStopServerRequest, self.mcp_stop_server_request) + result["McpTaskMetadata"] = to_class(MCPTaskMetadata, self.mcp_task_metadata) result["McpTools"] = to_class(MCPTools, self.mcp_tools) result["McpToolUi"] = to_class(MCPToolUI, self.mcp_tool_ui) result["McpToolUiVisibility"] = to_enum(MCPToolUIVisibility, self.mcp_tool_ui_visibility) @@ -30729,6 +38395,7 @@ def to_dict(self) -> dict: result["MetadataSnapshotRemoteMetadataRepository"] = to_class(MetadataSnapshotRemoteMetadataRepository, self.metadata_snapshot_remote_metadata_repository) result["MetadataSnapshotRemoteMetadataTaskType"] = to_enum(TaskType, self.metadata_snapshot_remote_metadata_task_type) result["Model"] = to_class(Model, self.model) + result["ModelApplyStartupOverlayRequest"] = to_class(ModelApplyStartupOverlayRequest, self.model_apply_startup_overlay_request) result["ModelBilling"] = to_class(ModelBilling, self.model_billing) result["ModelBillingPromo"] = to_class(ModelBillingPromo, self.model_billing_promo) result["ModelBillingTokenPrices"] = to_class(ModelBillingTokenPrices, self.model_billing_token_prices) @@ -30743,16 +38410,23 @@ def to_dict(self) -> dict: result["ModelCapabilitiesSupports"] = to_class(ModelCapabilitiesSupports, self.model_capabilities_supports) result["ModelList"] = to_class(ModelList, self.model_list) result["ModelListRequest"] = self.model_list_request + result["ModelMessage"] = to_class(ModelMessage, self.model_message) result["ModelPickerCategory"] = to_enum(ModelPickerCategory, self.model_picker_category) + result["ModelPickerPersistenceRequest"] = to_class(ModelPickerPersistenceRequest, self.model_picker_persistence_request) result["ModelPickerPriceCategory"] = to_enum(ModelPickerPriceCategory, self.model_picker_price_category) + result["ModelPickerSettingsContext"] = to_class(ModelPickerSettingsContext, self.model_picker_settings_context) result["ModelPolicy"] = to_class(ModelPolicy, self.model_policy) result["ModelPolicyState"] = to_enum(ModelPolicyState, self.model_policy_state) result["ModelSetReasoningEffortRequest"] = to_class(ModelSetReasoningEffortRequest, self.model_set_reasoning_effort_request) result["ModelSetReasoningEffortResult"] = to_class(ModelSetReasoningEffortResult, self.model_set_reasoning_effort_result) result["ModelsListRequest"] = to_class(ModelsListRequest, self.models_list_request) + result["ModelSwitchConfirmation"] = to_class(ModelSwitchConfirmation, self.model_switch_confirmation) result["ModelSwitchToRequest"] = to_class(ModelSwitchToRequest, self.model_switch_to_request) result["ModelSwitchToResult"] = to_class(ModelSwitchToResult, self.model_switch_to_result) + result["ModelWarningText"] = to_class(ModelWarningText, self.model_warning_text) result["ModeSetRequest"] = to_class(ModeSetRequest, self.mode_set_request) + result["ModeSetResult"] = to_class(ModeSetResult, self.mode_set_result) + result["MoveMcpLoadingToBackgroundResult"] = to_class(MoveMCPLoadingToBackgroundResult, self.move_mcp_loading_to_background_result) result["NamedProviderConfig"] = to_class(NamedProviderConfig, self.named_provider_config) result["NameGetResult"] = to_class(NameGetResult, self.name_get_result) result["NameSetAutoRequest"] = to_class(NameSetAutoRequest, self.name_set_auto_request) @@ -30777,6 +38451,7 @@ def to_dict(self) -> dict: result["PermissionDecisionApproveForLocationApproval"] = (self.permission_decision_approve_for_location_approval).to_dict() result["PermissionDecisionApproveForLocationApprovalCommands"] = to_class(PermissionDecisionApproveForLocationApprovalCommands, self.permission_decision_approve_for_location_approval_commands) result["PermissionDecisionApproveForLocationApprovalCustomTool"] = to_class(PermissionDecisionApproveForLocationApprovalCustomTool, self.permission_decision_approve_for_location_approval_custom_tool) + result["PermissionDecisionApproveForLocationApprovalExtensionEnvAccess"] = to_class(PermissionDecisionApproveForLocationApprovalExtensionEnvAccess, self.permission_decision_approve_for_location_approval_extension_env_access) 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) @@ -30789,6 +38464,7 @@ def to_dict(self) -> dict: result["PermissionDecisionApproveForSessionApproval"] = (self.permission_decision_approve_for_session_approval).to_dict() result["PermissionDecisionApproveForSessionApprovalCommands"] = to_class(PermissionDecisionApproveForSessionApprovalCommands, self.permission_decision_approve_for_session_approval_commands) result["PermissionDecisionApproveForSessionApprovalCustomTool"] = to_class(PermissionDecisionApproveForSessionApprovalCustomTool, self.permission_decision_approve_for_session_approval_custom_tool) + result["PermissionDecisionApproveForSessionApprovalExtensionEnvAccess"] = to_class(PermissionDecisionApproveForSessionApprovalExtensionEnvAccess, self.permission_decision_approve_for_session_approval_extension_env_access) 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) @@ -30800,13 +38476,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) @@ -30814,6 +38494,7 @@ def to_dict(self) -> dict: result["PermissionLocationResolveParams"] = to_class(PermissionLocationResolveParams, self.permission_location_resolve_params) result["PermissionLocationResolveResult"] = to_class(PermissionLocationResolveResult, self.permission_location_resolve_result) result["PermissionLocationType"] = to_enum(PermissionLocationType, self.permission_location_type) + result["PermissionModeSource"] = to_enum(PermissionSource, self.permission_mode_source) result["PermissionPathsAddParams"] = to_class(PermissionPathsAddParams, self.permission_paths_add_params) result["PermissionPathsAllowedCheckParams"] = to_class(PermissionPathsAllowedCheckParams, self.permission_paths_allowed_check_params) result["PermissionPathsAllowedCheckResult"] = to_class(PermissionPathsAllowedCheckResult, self.permission_paths_allowed_check_result) @@ -30824,8 +38505,8 @@ def to_dict(self) -> dict: result["PermissionPathsWorkspaceCheckResult"] = to_class(PermissionPathsWorkspaceCheckResult, self.permission_paths_workspace_check_result) result["PermissionPromptShownNotification"] = to_class(PermissionPromptShownNotification, self.permission_prompt_shown_notification) result["PermissionRequestResult"] = to_class(PermissionRequestResult, self.permission_request_result) + result["PermissionResponseCapability"] = to_enum(PermissionResponseCapability, self.permission_response_capability) result["PermissionRulesSet"] = to_class(PermissionRulesSet, self.permission_rules_set) - result["PermissionsAllowAllMode"] = to_enum(PermissionsAllowAllMode, self.permissions_allow_all_mode) result["PermissionsConfigureAdditionalContentExclusionPolicy"] = to_class(PermissionsConfigureAdditionalContentExclusionPolicy, self.permissions_configure_additional_content_exclusion_policy) result["PermissionsConfigureAdditionalContentExclusionPolicyRule"] = to_class(PermissionsConfigureAdditionalContentExclusionPolicyRule, self.permissions_configure_additional_content_exclusion_policy_rule) result["PermissionsConfigureAdditionalContentExclusionPolicyRuleSource"] = to_class(PermissionsConfigureAdditionalContentExclusionPolicyRuleSource, self.permissions_configure_additional_content_exclusion_policy_rule_source) @@ -30833,10 +38514,12 @@ def to_dict(self) -> dict: result["PermissionsConfigureParams"] = to_class(PermissionsConfigureParams, self.permissions_configure_params) result["PermissionsConfigureResult"] = to_class(PermissionsConfigureResult, self.permissions_configure_result) result["PermissionsFolderTrustAddTrustedResult"] = to_class(PermissionsFolderTrustAddTrustedResult, self.permissions_folder_trust_add_trusted_result) - result["PermissionsGetAllowAllRequest"] = to_class(PermissionsGetAllowAllRequest, self.permissions_get_allow_all_request) + result["PermissionsGetModeRequest"] = to_class(PermissionsGetModeRequest, self.permissions_get_mode_request) + result["PermissionsGetModeResult"] = to_class(PermissionsGetModeResult, self.permissions_get_mode_result) result["PermissionsLocationsAddToolApprovalDetails"] = (self.permissions_locations_add_tool_approval_details).to_dict() result["PermissionsLocationsAddToolApprovalDetailsCommands"] = to_class(PermissionsLocationsAddToolApprovalDetailsCommands, self.permissions_locations_add_tool_approval_details_commands) result["PermissionsLocationsAddToolApprovalDetailsCustomTool"] = to_class(PermissionsLocationsAddToolApprovalDetailsCustomTool, self.permissions_locations_add_tool_approval_details_custom_tool) + result["PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess"] = to_class(PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess, self.permissions_locations_add_tool_approval_details_extension_env_access) 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) @@ -30856,11 +38539,11 @@ def to_dict(self) -> dict: result["PermissionsPendingRequestsRequest"] = to_class(PermissionsPendingRequestsRequest, self.permissions_pending_requests_request) result["PermissionsResetSessionApprovalsRequest"] = to_class(PermissionsResetSessionApprovalsRequest, self.permissions_reset_session_approvals_request) result["PermissionsResetSessionApprovalsResult"] = to_class(PermissionsResetSessionApprovalsResult, self.permissions_reset_session_approvals_result) - result["PermissionsSetAllowAllRequest"] = to_class(PermissionsSetAllowAllRequest, self.permissions_set_allow_all_request) - result["PermissionsSetAllowAllSource"] = to_enum(PermissionsSetAAllSource, self.permissions_set_allow_all_source) result["PermissionsSetApproveAllRequest"] = to_class(PermissionsSetApproveAllRequest, self.permissions_set_approve_all_request) result["PermissionsSetApproveAllResult"] = to_class(PermissionsSetApproveAllResult, self.permissions_set_approve_all_result) - result["PermissionsSetApproveAllSource"] = to_enum(PermissionsSetAAllSource, self.permissions_set_approve_all_source) + result["PermissionsSetApproveAllSource"] = to_enum(PermissionSource, self.permissions_set_approve_all_source) + result["PermissionsSetModeRequest"] = to_class(PermissionsSetModeRequest, self.permissions_set_mode_request) + result["PermissionsSetModeResult"] = to_class(PermissionsSetModeResult, self.permissions_set_mode_result) result["PermissionsSetRequiredRequest"] = to_class(PermissionsSetRequiredRequest, self.permissions_set_required_request) result["PermissionsSetRequiredResult"] = to_class(PermissionsSetRequiredResult, self.permissions_set_required_result) result["PermissionsUrlsSetUnrestrictedModeResult"] = to_class(PermissionsUrlsSetUnrestrictedModeResult, self.permissions_urls_set_unrestricted_mode_result) @@ -30878,6 +38561,7 @@ def to_dict(self) -> dict: result["PluginInstallResult"] = to_class(PluginInstallResult, self.plugin_install_result) result["PluginList"] = to_class(PluginList, self.plugin_list) result["PluginListResult"] = to_class(PluginListResult, self.plugin_list_result) + result["PluginsBuiltinSetRequest"] = to_class(PluginsBuiltinSetRequest, self.plugins_builtin_set_request) result["PluginsDisableRequest"] = to_class(PluginsDisableRequest, self.plugins_disable_request) result["PluginsEnableRequest"] = to_class(PluginsEnableRequest, self.plugins_enable_request) result["PluginsInstallRequest"] = to_class(PluginsInstallRequest, self.plugins_install_request) @@ -30891,6 +38575,8 @@ def to_dict(self) -> dict: result["PluginUpdateAllEntry"] = to_class(PluginUpdateAllEntry, self.plugin_update_all_entry) result["PluginUpdateAllResult"] = to_class(PluginUpdateAllResult, self.plugin_update_all_result) result["PluginUpdateResult"] = to_class(PluginUpdateResult, self.plugin_update_result) + result["ProtocolExternalToolDefer"] = to_enum(MCPServerConfigDeferTools, self.protocol_external_tool_defer) + result["ProtocolExternalToolDefinition"] = to_class(ProtocolExternalToolDefinition, self.protocol_external_tool_definition) result["ProviderAddRequest"] = to_class(ProviderAddRequest, self.provider_add_request) result["ProviderAddResult"] = to_class(ProviderAddResult, self.provider_add_result) result["ProviderConfig"] = to_class(ProviderConfig, self.provider_config) @@ -30980,6 +38666,7 @@ def to_dict(self) -> dict: result["RemoteNotifySteerableChangedRequest"] = to_class(RemoteNotifySteerableChangedRequest, self.remote_notify_steerable_changed_request) result["RemoteNotifySteerableChangedResult"] = to_class(RemoteNotifySteerableChangedResult, self.remote_notify_steerable_changed_result) result["RemoteSessionConnectionResult"] = to_class(RemoteSessionConnectionResult, self.remote_session_connection_result) + result["RemoteSessionHostStatus"] = to_enum(RemoteSessionHostStatus, self.remote_session_host_status) result["RemoteSessionMetadataRepository"] = to_class(RemoteSessionMetadataRepository, self.remote_session_metadata_repository) result["RemoteSessionMetadataTaskType"] = to_enum(TaskType, self.remote_session_metadata_task_type) result["RemoteSessionMetadataValue"] = to_class(RemoteSessionMetadataValue, self.remote_session_metadata_value) @@ -30987,6 +38674,8 @@ 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["SandboxConfigSource"] = to_enum(_SandboxConfigSource, self.sandbox_config_source) 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) @@ -30994,6 +38683,7 @@ def to_dict(self) -> dict: 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["SandboxEnforcementStatus"] = to_class(SandboxEnforcementStatus, self.sandbox_enforcement_status) 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) @@ -31022,7 +38712,10 @@ def to_dict(self) -> dict: 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["SessionAuthLoginRequest"] = to_class(SessionAuthLoginRequest, self.session_auth_login_request) + result["SessionAuthLogoutUserRequest"] = to_class(SessionAuthLogoutUserRequest, self.session_auth_logout_user_request) result["SessionAuthStatus"] = to_class(SessionAuthStatus, self.session_auth_status) + result["SessionAuthSwitchRequest"] = to_class(SessionAuthSwitchRequest, self.session_auth_switch_request) 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) @@ -31064,6 +38757,9 @@ def to_dict(self) -> dict: 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["SessionGitHubAuthGetAllAuthAvailableResult"] = from_list(lambda x: to_class(SessionAuthStatus, x), self.session_git_hub_auth_get_all_auth_available_result) + result["SessionGitHubAuthLogoutResult"] = from_bool(self.session_git_hub_auth_logout_result) + result["SessionGitHubAuthLogoutUserResult"] = from_bool(self.session_git_hub_auth_logout_user_result) 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) @@ -31085,6 +38781,8 @@ def to_dict(self) -> 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) @@ -31157,6 +38855,7 @@ def to_dict(self) -> dict: result["SessionsOpenStatus"] = to_enum(SessionsOpenStatus, self.sessions_open_status) result["SessionSource"] = to_enum(SessionSource, self.session_source) result["SessionsPruneOldRequest"] = to_class(SessionsPruneOldRequest, self.sessions_prune_old_request) + result["SessionsReadPersistedEventsRequest"] = to_class(SessionsReadPersistedEventsRequest, self.sessions_read_persisted_events_request) result["SessionsRegisterExtensionToolsOnSessionOptions"] = to_class(SessionsRegisterExtensionToolsOnSessionOptions, self.sessions_register_extension_tools_on_session_options) result["SessionsReleaseLockRequest"] = to_class(SessionsReleaseLockRequest, self.sessions_release_lock_request) result["SessionsReleaseLockResult"] = to_class(SessionsReleaseLockResult, self.sessions_release_lock_result) @@ -31176,7 +38875,10 @@ def to_dict(self) -> dict: result["SessionVisibilityStatus"] = to_enum(SessionVisibilityStatus, self.session_visibility_status) result["SessionWorkingDirectoryContext"] = to_class(SessionWorkingDirectoryContext, self.session_working_directory_context) result["SessionWorkingDirectoryContextHostType"] = to_enum(HostType, self.session_working_directory_context_host_type) + result["SettableAuthInfo"] = (self.settable_auth_info).to_dict() + result["SettableTokenAuthInfo"] = to_class(SettableTokenAuthInfo, self.settable_token_auth_info) result["ShellCancelUserRequestedRequest"] = to_class(ShellCancelUserRequestedRequest, self.shell_cancel_user_requested_request) + result["ShellCredentials"] = to_class(ShellCredentials, self.shell_credentials) 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) @@ -31193,7 +38895,13 @@ def to_dict(self) -> dict: result["SkillDiscoveryPathList"] = to_class(SkillDiscoveryPathList, self.skill_discovery_path_list) result["SkillDiscoveryScope"] = to_enum(SkillDiscoveryScope, self.skill_discovery_scope) result["SkillList"] = to_class(SkillList, self.skill_list) + result["SkillProviderDescriptor"] = to_class(SkillProviderDescriptor, self.skill_provider_descriptor) + result["SkillProviderListRequest"] = to_class(SkillProviderListRequest, self.skill_provider_list_request) + result["SkillProviderListResult"] = to_class(_SkillProviderListResult, self.skill_provider_list_result) + result["SkillProviderReadRequest"] = to_class(_SkillProviderReadRequest, self.skill_provider_read_request) + result["SkillProviderReadResult"] = to_class(_SkillProviderReadResult, self.skill_provider_read_result) result["SkillsConfigSetDisabledSkillsRequest"] = to_class(SkillsConfigSetDisabledSkillsRequest, self.skills_config_set_disabled_skills_request) + result["SkillsConfigSetSkillDisabledRequest"] = to_class(SkillsConfigSetSkillDisabledRequest, self.skills_config_set_skill_disabled_request) result["SkillsDisableRequest"] = to_class(SkillsDisableRequest, self.skills_disable_request) result["SkillsDiscoverRequest"] = to_class(SkillsDiscoverRequest, self.skills_discover_request) result["SkillsEnableRequest"] = to_class(SkillsEnableRequest, self.skills_enable_request) @@ -31201,6 +38909,7 @@ def to_dict(self) -> dict: result["SkillsGetInvokedResult"] = to_class(SkillsGetInvokedResult, self.skills_get_invoked_result) result["SkillsInvokedSkill"] = to_class(SkillsInvokedSkill, self.skills_invoked_skill) result["SkillsLoadDiagnostics"] = to_class(SkillsLoadDiagnostics, self.skills_load_diagnostics) + result["SlashCommandAddTimelineEntryResult"] = to_class(SlashCommandAddTimelineEntryResult, self.slash_command_add_timeline_entry_result) result["SlashCommandAgentPromptResult"] = to_class(SlashCommandAgentPromptResult, self.slash_command_agent_prompt_result) result["SlashCommandCompletedResult"] = to_class(SlashCommandCompletedResult, self.slash_command_completed_result) result["SlashCommandInfo"] = to_class(SlashCommandInfo, self.slash_command_info) @@ -31209,13 +38918,20 @@ def to_dict(self) -> dict: result["SlashCommandInputCompletion"] = to_enum(SlashCommandInputCompletion, self.slash_command_input_completion) result["SlashCommandInvocationResult"] = (self.slash_command_invocation_result).to_dict() result["SlashCommandKind"] = to_enum(SlashCommandKind, self.slash_command_kind) + result["SlashCommandModelPickerDialog"] = to_class(SlashCommandModelPickerDialog, self.slash_command_model_picker_dialog) result["SlashCommandSelectSubcommandOption"] = to_class(SlashCommandSelectSubcommandOption, self.slash_command_select_subcommand_option) result["SlashCommandSelectSubcommandResult"] = to_class(SlashCommandSelectSubcommandResult, self.slash_command_select_subcommand_result) + result["SlashCommandSetModelResult"] = to_class(SlashCommandSetModelResult, self.slash_command_set_model_result) + result["SlashCommandSetPlanModelResult"] = to_class(SlashCommandSetPlanModelResult, self.slash_command_set_plan_model_result) + result["SlashCommandShowDialogResult"] = to_class(SlashCommandShowDialogResult, self.slash_command_show_dialog_result) result["SlashCommandTextResult"] = to_class(SlashCommandTextResult, self.slash_command_text_result) + result["SlashCommandTimelineEntry"] = to_class(SlashCommandTimelineEntry, self.slash_command_timeline_entry) result["SubagentSettingsEntry"] = to_class(SubagentSettingsEntry, self.subagent_settings_entry) result["SubagentSettingsEntryContextTier"] = to_enum(SubagentSettingsEntryContextTier, self.subagent_settings_entry_context_tier) result["TaskAgentInfo"] = to_class(TaskAgentInfo, self.task_agent_info) result["TaskAgentProgress"] = to_class(TaskAgentProgress, self.task_agent_progress) + result["TaskCompleteData"] = to_class(TaskCompleteData, self.task_complete_data) + result["TaskCompletionDecision"] = to_class(TaskCompletionDecision, self.task_completion_decision) result["TaskExecutionMode"] = to_enum(TaskExecutionMode, self.task_execution_mode) result["TaskInfo"] = (self.task_info).to_dict() result["TaskList"] = to_class(TaskList, self.task_list) @@ -31242,11 +38958,23 @@ def to_dict(self) -> dict: result["TasksWaitForPendingResult"] = to_class(TasksWaitForPendingResult, self.tasks_wait_for_pending_result) result["TelemetrySetFeatureOverridesRequest"] = to_class(TelemetrySetFeatureOverridesRequest, self.telemetry_set_feature_overrides_request) result["TokenAuthInfo"] = to_class(TokenAuthInfo, self.token_auth_info) + result["TokenProviderAuthInfo"] = to_class(TokenProviderAuthInfo, self.token_provider_auth_info) result["Tool"] = to_class(Tool, self.tool) result["ToolList"] = to_class(ToolList, self.tool_list) + result["ToolResult"] = from_union([lambda x: to_class(ToolResultExpanded, x), from_str], self.tool_result) + result["ToolResultExpanded"] = to_class(ToolResultExpanded, self.tool_result_expanded) + result["ToolResultNewMessage"] = to_class(ToolResultNewMessage, self.tool_result_new_message) + result["ToolResultType"] = to_enum(ToolResultType, self.tool_result_type) + result["ToolsExecuteRequest"] = to_class(ToolsExecuteRequest, self.tools_execute_request) + result["ToolsGetBuiltinDescriptorsRequest"] = to_class(ToolsGetBuiltinDescriptorsRequest, self.tools_get_builtin_descriptors_request) + result["ToolsGetBuiltinDescriptorsResult"] = to_class(ToolsGetBuiltinDescriptorsResult, self.tools_get_builtin_descriptors_result) result["ToolsGetCurrentMetadataResult"] = to_class(ToolsGetCurrentMetadataResult, self.tools_get_current_metadata_result) result["ToolsInitializeAndValidateResult"] = to_class(ToolsInitializeAndValidateResult, self.tools_initialize_and_validate_result) result["ToolsListRequest"] = to_class(ToolsListRequest, self.tools_list_request) + result["ToolsSetRequest"] = to_class(ToolsSetRequest, self.tools_set_request) + result["ToolsSetResult"] = to_class(ToolsSetResult, self.tools_set_result) + result["ToolsShellDescriptorConfig"] = to_class(ToolsShellDescriptorConfig, self.tools_shell_descriptor_config) + result["ToolsTaskCompleteEventDataRequest"] = to_class(ToolsTaskCompleteEventDataRequest, self.tools_task_complete_event_data_request) result["ToolsUpdateSubagentSettingsResult"] = to_class(ToolsUpdateSubagentSettingsResult, self.tools_update_subagent_settings_result) result["UIAutoModeSwitchResponse"] = to_enum(UIAutoModeSwitchResponse, self.ui_auto_mode_switch_response) result["UIElicitationArrayAnyOfField"] = to_class(UIElicitationArrayAnyOfField, self.ui_elicitation_array_any_of_field) @@ -31290,6 +39018,7 @@ def to_dict(self) -> dict: result["UIUserInputResponse"] = to_class(UIUserInputResponse, self.ui_user_input_response) result["UpdateSubagentSettingsRequest"] = to_class(UpdateSubagentSettingsRequest, self.update_subagent_settings_request) result["UsageGetMetricsResult"] = to_class(UsageGetMetricsResult, self.usage_get_metrics_result) + result["UsageMetricsAgentMetric"] = to_class(UsageMetricsAgentMetric, self.usage_metrics_agent_metric) result["UsageMetricsCodeChanges"] = to_class(UsageMetricsCodeChanges, self.usage_metrics_code_changes) result["UsageMetricsModelMetric"] = to_class(UsageMetricsModelMetric, self.usage_metrics_model_metric) result["UsageMetricsModelMetricRequests"] = to_class(UsageMetricsModelMetricRequests, self.usage_metrics_model_metric_requests) @@ -31333,6 +39062,7 @@ def to_dict(self) -> dict: 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["SessionAuthInfoResult"] = from_union([lambda x: to_class(AuthIdentity, x), from_none], self.session_auth_info_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) @@ -31359,8 +39089,8 @@ def _load_AgentRegistrySpawnResult(obj: Any) -> "AgentRegistrySpawnResult": case "validation-error": return AgentRegistrySpawnValidationError.from_dict(obj) case _: raise ValueError(f"Unknown AgentRegistrySpawnResult kind: {kind!r}") -# Initial authentication info for the session. -AuthInfo = HMACAuthInfo | EnvAuthInfo | TokenAuthInfo | CopilotAPITokenAuthInfo | UserAuthInfo | GhCLIAuthInfo | APIKeyAuthInfo +# Authentication credentials accepted only at native protocol ingress. Runtime outputs use credential-free `AuthIdentity` metadata. +AuthInfo = HMACAuthInfo | EnvAuthInfo | TokenAuthInfo | TokenProviderAuthInfo | CopilotAPITokenAuthInfo | UserAuthInfo | GhCLIAuthInfo | APIKeyAuthInfo def _load_AuthInfo(obj: Any) -> "AuthInfo": assert isinstance(obj, dict) @@ -31369,12 +39099,44 @@ def _load_AuthInfo(obj: Any) -> "AuthInfo": case "hmac": return HMACAuthInfo.from_dict(obj) case "env": return EnvAuthInfo.from_dict(obj) case "token": return TokenAuthInfo.from_dict(obj) + case "token-provider": return TokenProviderAuthInfo.from_dict(obj) case "copilot-api-token": return CopilotAPITokenAuthInfo.from_dict(obj) case "user": return UserAuthInfo.from_dict(obj) case "gh-cli": return GhCLIAuthInfo.from_dict(obj) case "api-key": return APIKeyAuthInfo.from_dict(obj) case _: raise ValueError(f"Unknown AuthInfo type: {kind!r}") +# Where a candidate's card came from. Exactly one of a URL or embedded data: the union has no variant carrying both, and no variant carrying neither, so the rule holds structurally rather than by validation. +CatalogCandidateSource = CatalogCandidateSourceURL | CatalogCandidateSourceEmbedded + +def _load_CatalogCandidateSource(obj: Any) -> "CatalogCandidateSource": + assert isinstance(obj, dict) + kind = obj.get("kind") + match kind: + case "url": return CatalogCandidateSourceURL.from_dict(obj) + case "embedded": return CatalogCandidateSourceEmbedded.from_dict(obj) + case _: raise ValueError(f"Unknown CatalogCandidateSource kind: {kind!r}") + +# Outcome of a catalog.search call: either bounded inert candidates, or one typed refusal. Never a partial success. +CatalogSearchResult = CatalogSearchSucceeded | CatalogNegotiationRefusedError | CatalogUnsupportedKindError | CatalogInvalidRequestError | CatalogAuthenticationRequiredError | CatalogPolicyRejectedError | CatalogNetworkFailureError | CatalogUnsafeRetrievalError | CatalogMalformedCardError | CatalogContractViolationError | CatalogUnavailableError + +def _load_CatalogSearchResult(obj: Any) -> "CatalogSearchResult": + assert isinstance(obj, dict) + kind = obj.get("kind") + match kind: + case "succeeded": return CatalogSearchSucceeded.from_dict(obj) + case "negotiation-refused": return CatalogNegotiationRefusedError.from_dict(obj) + case "unsupported-kind": return CatalogUnsupportedKindError.from_dict(obj) + case "invalid-request": return CatalogInvalidRequestError.from_dict(obj) + case "authentication-required": return CatalogAuthenticationRequiredError.from_dict(obj) + case "policy-rejected": return CatalogPolicyRejectedError.from_dict(obj) + case "network-failure": return CatalogNetworkFailureError.from_dict(obj) + case "unsafe-retrieval": return CatalogUnsafeRetrievalError.from_dict(obj) + case "malformed-card": return CatalogMalformedCardError.from_dict(obj) + case "contract-violation": return CatalogContractViolationError.from_dict(obj) + case "unavailable": return CatalogUnavailableError.from_dict(obj) + case _: raise ValueError(f"Unknown CatalogSearchResult kind: {kind!r}") + # A content block within a tool result, which may be text, terminal output, image, audio, or a resource ExternalToolTextResultForLlmContent = ExternalToolTextResultForLlmContentText | ExternalToolTextResultForLlmContentTerminal | ExternalToolTextResultForLlmContentShellExit | ExternalToolTextResultForLlmContentImage | ExternalToolTextResultForLlmContentAudio | ExternalToolTextResultForLlmContentResourceLink | ExternalToolTextResultForLlmContentResource @@ -31391,6 +39153,72 @@ def _load_ExternalToolTextResultForLlmContent(obj: Any) -> "ExternalToolTextResu case "resource": return ExternalToolTextResultForLlmContentResource.from_dict(obj) case _: raise ValueError(f"Unknown ExternalToolTextResultForLlmContent type: {kind!r}") +# Outcome of an mcp.planInstall call: either a normalised plan, or one typed refusal. Nothing is written in either case. +MCPPlanInstallResult = MCPPlanInstallPlanned | CatalogNegotiationRefusedError | CatalogHandleRejectedError | CatalogInvalidRequestError | CatalogAuthenticationRequiredError | CatalogPolicyRejectedError | CatalogNetworkFailureError | CatalogUnsafeRetrievalError | CatalogMalformedCardError | CatalogContractViolationError | CatalogUnavailableTransportError | CatalogNotInstallableError | CatalogUnavailableError + +def _load_MCPPlanInstallResult(obj: Any) -> "MCPPlanInstallResult": + assert isinstance(obj, dict) + kind = obj.get("kind") + match kind: + case "planned": return MCPPlanInstallPlanned.from_dict(obj) + case "negotiation-refused": return CatalogNegotiationRefusedError.from_dict(obj) + case "handle-rejected": return CatalogHandleRejectedError.from_dict(obj) + case "invalid-request": return CatalogInvalidRequestError.from_dict(obj) + case "authentication-required": return CatalogAuthenticationRequiredError.from_dict(obj) + case "policy-rejected": return CatalogPolicyRejectedError.from_dict(obj) + case "network-failure": return CatalogNetworkFailureError.from_dict(obj) + case "unsafe-retrieval": return CatalogUnsafeRetrievalError.from_dict(obj) + case "malformed-card": return CatalogMalformedCardError.from_dict(obj) + case "contract-violation": return CatalogContractViolationError.from_dict(obj) + case "unavailable-transport": return CatalogUnavailableTransportError.from_dict(obj) + case "not-installable": return CatalogNotInstallableError.from_dict(obj) + case "unavailable": return CatalogUnavailableError.from_dict(obj) + case _: raise ValueError(f"Unknown MCPPlanInstallResult kind: {kind!r}") + +# What an install plan is computed from: a candidate handle from a previous search, or a card supplied directly. +MCPPlanInstallSource = MCPPlanInstallSourceCandidate | MCPPlanInstallSourceCard + +def _load_MCPPlanInstallSource(obj: Any) -> "MCPPlanInstallSource": + assert isinstance(obj, dict) + kind = obj.get("kind") + match kind: + case "candidate": return MCPPlanInstallSourceCandidate.from_dict(obj) + case "card": return MCPPlanInstallSourceCard.from_dict(obj) + case _: raise ValueError(f"Unknown MCPPlanInstallSource kind: {kind!r}") + +# One non-secret value a transport choice needs, represented as a scalar or enumerated variant so enum values cannot be missing or attached to another type. +MCPPlanRequiredValue = MCPPlanRequiredValueScalar | MCPPlanRequiredValueEnum + +def _load_MCPPlanRequiredValue(obj: Any) -> "MCPPlanRequiredValue": + assert isinstance(obj, dict) + kind = obj.get("kind") + match kind: + case "scalar": return MCPPlanRequiredValueScalar.from_dict(obj) + case "enum": return MCPPlanRequiredValueEnum.from_dict(obj) + case _: raise ValueError(f"Unknown MCPPlanRequiredValue kind: {kind!r}") + +# One eligible way to run the server, represented as a tagged package or remote variant so package identity and endpoint states cannot contradict the install method. +MCPPlanTransportChoice = MCPPlanTransportChoicePackage | MCPPlanTransportChoiceRemote + +def _load_MCPPlanTransportChoice(obj: Any) -> "MCPPlanTransportChoice": + assert isinstance(obj, dict) + kind = obj.get("installMethod") + match kind: + case "package": return MCPPlanTransportChoicePackage.from_dict(obj) + case "remote": return MCPPlanTransportChoiceRemote.from_dict(obj) + case _: raise ValueError(f"Unknown MCPPlanTransportChoice installMethod: {kind!r}") + +# A card supplied directly by the caller. Exactly one of a URL or embedded data, encoded structurally so neither both nor neither can be expressed. +MCPServerCardReference = MCPServerCardURL | MCPServerCardEmbedded + +def _load_MCPServerCardReference(obj: Any) -> "MCPServerCardReference": + assert isinstance(obj, dict) + kind = obj.get("kind") + match kind: + case "url": return MCPServerCardURL.from_dict(obj) + case "embedded": return MCPServerCardEmbedded.from_dict(obj) + case _: raise ValueError(f"Unknown MCPServerCardReference kind: {kind!r}") + # The client's response to the pending permission prompt PermissionDecision = PermissionDecisionApproveOnce | PermissionDecisionApproveForSession | PermissionDecisionApproveForLocation | PermissionDecisionApprovePermanently | PermissionDecisionReject | PermissionDecisionUserNotAvailable | PermissionDecisionApproved | PermissionDecisionApprovedForSession | PermissionDecisionApprovedForLocation | PermissionDecisionCancelled | PermissionDecisionDeniedByRules | PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser | PermissionDecisionDeniedInteractivelyByUser | PermissionDecisionDeniedByContentExclusionPolicy | PermissionDecisionDeniedByPermissionRequestHook @@ -31416,7 +39244,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 | PermissionDecisionApproveForLocationApprovalFactory | PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess +PermissionDecisionApproveForLocationApproval = PermissionDecisionApproveForLocationApprovalCommands | PermissionDecisionApproveForLocationApprovalRead | PermissionDecisionApproveForLocationApprovalWrite | PermissionDecisionApproveForLocationApprovalMCP | PermissionDecisionApproveForLocationApprovalMCPSampling | PermissionDecisionApproveForLocationApprovalMemory | PermissionDecisionApproveForLocationApprovalCustomTool | PermissionDecisionApproveForLocationApprovalExtensionManagement | PermissionDecisionApproveForLocationApprovalFactory | PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess | PermissionDecisionApproveForLocationApprovalExtensionEnvAccess def _load_PermissionDecisionApproveForLocationApproval(obj: Any) -> "PermissionDecisionApproveForLocationApproval": assert isinstance(obj, dict) @@ -31432,10 +39260,11 @@ def _load_PermissionDecisionApproveForLocationApproval(obj: Any) -> "PermissionD 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 "extension-env-access": return PermissionDecisionApproveForLocationApprovalExtensionEnvAccess.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 | PermissionDecisionApproveForSessionApprovalFactory | PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess +PermissionDecisionApproveForSessionApproval = PermissionDecisionApproveForSessionApprovalCommands | PermissionDecisionApproveForSessionApprovalRead | PermissionDecisionApproveForSessionApprovalWrite | PermissionDecisionApproveForSessionApprovalMCP | PermissionDecisionApproveForSessionApprovalMCPSampling | PermissionDecisionApproveForSessionApprovalMemory | PermissionDecisionApproveForSessionApprovalCustomTool | PermissionDecisionApproveForSessionApprovalExtensionManagement | PermissionDecisionApproveForSessionApprovalFactory | PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess | PermissionDecisionApproveForSessionApprovalExtensionEnvAccess def _load_PermissionDecisionApproveForSessionApproval(obj: Any) -> "PermissionDecisionApproveForSessionApproval": assert isinstance(obj, dict) @@ -31451,10 +39280,11 @@ def _load_PermissionDecisionApproveForSessionApproval(obj: Any) -> "PermissionDe 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 "extension-env-access": return PermissionDecisionApproveForSessionApprovalExtensionEnvAccess.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 | PermissionsLocationsAddToolApprovalDetailsFactory | PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess +PermissionsLocationsAddToolApprovalDetails = PermissionsLocationsAddToolApprovalDetailsCommands | PermissionsLocationsAddToolApprovalDetailsRead | PermissionsLocationsAddToolApprovalDetailsWrite | PermissionsLocationsAddToolApprovalDetailsMCP | PermissionsLocationsAddToolApprovalDetailsMCPSampling | PermissionsLocationsAddToolApprovalDetailsMemory | PermissionsLocationsAddToolApprovalDetailsCustomTool | PermissionsLocationsAddToolApprovalDetailsExtensionManagement | PermissionsLocationsAddToolApprovalDetailsFactory | PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess | PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess def _load_PermissionsLocationsAddToolApprovalDetails(obj: Any) -> "PermissionsLocationsAddToolApprovalDetails": assert isinstance(obj, dict) @@ -31470,6 +39300,7 @@ def _load_PermissionsLocationsAddToolApprovalDetails(obj: Any) -> "PermissionsLo 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 "extension-env-access": return PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess.from_dict(obj) case _: raise ValueError(f"Unknown PermissionsLocationsAddToolApprovalDetails kind: {kind!r}") # Attachment union accepted by push input, covering files, directories, GitHub objects, blobs, snippets, and extension context. @@ -31547,8 +39378,24 @@ def _load_SessionOpenParams(obj: Any) -> "SessionOpenParams": case "handoff": return SessionsOpenHandoff.from_dict(obj) case _: raise ValueError(f"Unknown SessionOpenParams kind: {kind!r}") +# Authentication credentials accepted by session.gitHubAuth.setCredentials. Session-owned token-provider identities cannot be installed through this method. +SettableAuthInfo = HMACAuthInfo | EnvAuthInfo | SettableTokenAuthInfo | CopilotAPITokenAuthInfo | UserAuthInfo | GhCLIAuthInfo | APIKeyAuthInfo + +def _load_SettableAuthInfo(obj: Any) -> "SettableAuthInfo": + assert isinstance(obj, dict) + kind = obj.get("type") + match kind: + case "hmac": return HMACAuthInfo.from_dict(obj) + case "env": return EnvAuthInfo.from_dict(obj) + case "token": return SettableTokenAuthInfo.from_dict(obj) + case "copilot-api-token": return CopilotAPITokenAuthInfo.from_dict(obj) + case "user": return UserAuthInfo.from_dict(obj) + case "gh-cli": return GhCLIAuthInfo.from_dict(obj) + case "api-key": return APIKeyAuthInfo.from_dict(obj) + case _: raise ValueError(f"Unknown SettableAuthInfo type: {kind!r}") + # Result of invoking the slash command (text output, prompt to send to the agent, completion, or subcommand selection). -SlashCommandInvocationResult = SlashCommandTextResult | SlashCommandAgentPromptResult | SlashCommandCompletedResult | SlashCommandSelectSubcommandResult +SlashCommandInvocationResult = SlashCommandTextResult | SlashCommandAgentPromptResult | SlashCommandCompletedResult | SlashCommandSelectSubcommandResult | SlashCommandAddTimelineEntryResult | SlashCommandShowDialogResult | SlashCommandSetModelResult | SlashCommandSetPlanModelResult def _load_SlashCommandInvocationResult(obj: Any) -> "SlashCommandInvocationResult": assert isinstance(obj, dict) @@ -31558,6 +39405,10 @@ def _load_SlashCommandInvocationResult(obj: Any) -> "SlashCommandInvocationResul case "agent-prompt": return SlashCommandAgentPromptResult.from_dict(obj) case "completed": return SlashCommandCompletedResult.from_dict(obj) case "select-subcommand": return SlashCommandSelectSubcommandResult.from_dict(obj) + case "add-timeline-entry": return SlashCommandAddTimelineEntryResult.from_dict(obj) + case "show-dialog": return SlashCommandShowDialogResult.from_dict(obj) + case "set-model": return SlashCommandSetModelResult.from_dict(obj) + case "set-plan-model": return SlashCommandSetPlanModelResult.from_dict(obj) case _: raise ValueError(f"Unknown SlashCommandInvocationResult kind: {kind!r}") # Tracked task union returned by task APIs, containing either an agent task or a shell task. @@ -31574,8 +39425,13 @@ def _load_TaskInfo(obj: Any) -> "TaskInfo": AccountGetAllUsersResult = list AgentListRequest = Any +AuthValidationErrors = list +BuiltinToolSafeForTelemetry = bool CanvasActionInvokeResult = Any CanvasJsonSchema = Any +CardDigestValue = str +CatalogCapabilityId = str +CatalogMcpServerInstallability = CatalogMCPServerInstallabilityEnum CommandsListRequest = Any ExternalToolResult = ExternalToolTextResultForLlm ExternalToolTextResultForLlmContentResourceLinkIconTheme = Theme @@ -31595,17 +39451,28 @@ def _load_TaskInfo(obj: Any) -> "TaskInfo": McpExecuteSamplingRequest = dict McpExecuteSamplingResult = dict McpOauthLoginGrantType = MCPGrantType +McpPlanEnumValueType = MCPPlan +McpPlanInstallResult = MCPPlanInstallResult +McpPlanInstallSource = MCPPlanInstallSource +McpPlanRequiredValue = MCPPlanRequiredValue +McpPlanRequiredValueEnumKind = MCPPlan +McpPlanScalarValueType = MCPPlanScalarValueTypeEnum +McpPlanSecretReference = str +McpPlanTransportChoice = MCPPlanTransportChoice +McpSafeForTelemetry = bool McpServerAuthConfig = bool +McpServerCardReference = MCPServerCardReference McpServerConfigHttpOauthGrantType = MCPGrantType MetadataSnapshotRemoteMetadataTaskType = TaskType ModelListRequest = Any OptionsUpdateAdditionalContentExclusionPolicyScope = AdditionalContentExclusionPolicyScope OptionsUpdateEnvValueMode = MCPSetEnvValueModeDetails OptionsUpdateReasoningSummary = ReasoningSummary +PermissionModeSource = PermissionSource PermissionsConfigureAdditionalContentExclusionPolicyScope = AdditionalContentExclusionPolicyScope -PermissionsSetAllowAllSource = PermissionsSetAAllSource -PermissionsSetApproveAllSource = PermissionsSetAAllSource +PermissionsSetApproveAllSource = PermissionSource PluginsReloadRequest = Any +ProtocolExternalToolDefer = MCPServerConfigDeferTools ProviderConfigTransport = ProviderTransport ProviderConfigType = ProviderType ProviderConfigWireApi = ProviderWireAPI @@ -31614,9 +39481,13 @@ def _load_TaskInfo(obj: Any) -> "TaskInfo": ProviderEndpointWireApi = ProviderWireAPI ProviderGetEndpointRequest = Any RemoteSessionMetadataTaskType = TaskType +SessionAuthInfoResult = AuthIdentity SessionCancelAllBackgroundAgentsResult = int SessionContextHostType = HostType SessionFsReaddirWithTypesEntryType = DebugCollectLogsEntryKind +SessionGitHubAuthGetAllAuthAvailableResult = list +SessionGitHubAuthLogoutResult = bool +SessionGitHubAuthLogoutUserResult = bool SessionLimitPredictionRequest = Any SessionMcpAppsCallToolResult = dict SessionOpenOptionsAdditionalContentExclusionPolicyScope = AdditionalContentExclusionPolicyScope @@ -31626,6 +39497,7 @@ def _load_TaskInfo(obj: Any) -> "TaskInfo": SessionWorkingDirectoryContextHostType = HostType TaskInfoExecutionMode = TaskExecutionMode TaskInfoStatus = TaskStatus +ToolResult = ToolResultExpanded WorkspaceSummaryHostType = HostType WorkspacesWorkspaceDetailsHostType = HostType @@ -31657,13 +39529,24 @@ def _patch_model_capabilities(data: dict) -> dict: return data +# Experimental: this API group is experimental and may change or be removed. +class ServerHooksApi: + def __init__(self, client: "JsonRpcClient"): + self._client = client + + async def discover(self, params: HooksDiscoverRequest, *, timeout: float | None = None) -> HooksDiscoverResult: + "Discovers hook actions enabled under server-side discovery settings from user, repository, plugin, and managed-policy sources.\n\nArgs:\n params: Optional project paths and host-exclusion behavior for server-scoped hook discovery.\n\nReturns:\n Server-discovered hook actions and partial-load diagnostics from user, repository, plugin, and managed-policy sources. Concrete sessions may include additional session-specific hook sources." + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + return HooksDiscoverResult.from_dict(await self._client.request("hooks.discover", params_dict, **_timeout_kwargs(timeout))) + + # Experimental: this API group is experimental and may change or be removed. class ServerModelsApi: def __init__(self, client: "JsonRpcClient"): self._client = client async def list(self, params: ModelsListRequest, *, timeout: float | None = None) -> ModelList: - "Lists Copilot models available to the authenticated user.\n\nArgs:\n params: Optional GitHub token used to list models for a specific user instead of the global auth context.\n\nReturns:\n List of Copilot models available to the resolved user, including capabilities and billing metadata." + "Lists Copilot models available to the authenticated user.\n\nArgs:\n params: Optional opaque account selection or compatibility GitHub token used to list models.\n\nReturns:\n List of Copilot models available to the resolved user, including capabilities and billing metadata." 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)))) @@ -31689,7 +39572,7 @@ def __init__(self, client: "JsonRpcClient"): self._client = client async def get_quota(self, params: AccountGetQuotaRequest, *, timeout: float | None = None) -> AccountGetQuotaResult: - "Gets Copilot quota usage for the authenticated user or supplied GitHub token.\n\nArgs:\n params: Optional GitHub token used to look up quota for a specific user instead of the global auth context.\n\nReturns:\n Quota usage snapshots for the resolved user, keyed by quota type." + "Gets Copilot quota usage for the current or opaquely selected authenticated user.\n\nArgs:\n params: Optional opaque account selection or compatibility GitHub token used to look up quota.\n\nReturns:\n Quota usage snapshots for the resolved user, keyed by quota type." params_dict = {k: v for k, v in params.to_dict().items() if v is not None} return AccountGetQuotaResult.from_dict(await self._client.request("account.getQuota", params_dict, **_timeout_kwargs(timeout))) @@ -31702,7 +39585,7 @@ async def get_all_users(self, *, timeout: float | None = None) -> list: return list(await self._client.request("account.getAllUsers", {}, **_timeout_kwargs(timeout))) async def login(self, params: AccountLoginRequest, *, timeout: float | None = None) -> AccountLoginResult: - "Stores authentication credentials after successful login (e.g., device code flow).\n\nArgs:\n params: Credentials to store after successful authentication\n\nReturns:\n Result of a successful login; throws on failure" + "Validates and stores authentication credentials. When login is omitted, resolves the authenticated user from the token before persistence.\n\nArgs:\n params: Credentials to validate and store. Omit login to resolve the authenticated user from the token.\n\nReturns:\n Result of a successful login; throws on failure" params_dict = {k: v for k, v in params.to_dict().items() if v is not None} return AccountLoginResult.from_dict(await self._client.request("account.login", params_dict, **_timeout_kwargs(timeout))) @@ -31773,6 +39656,11 @@ async def discover(self, params: MCPDiscoverRequest, *, timeout: float | None = params_dict = {k: v for k, v in params.to_dict().items() if v is not None} return MCPDiscoverResult.from_dict(await self._client.request("mcp.discover", params_dict, **_timeout_kwargs(timeout))) + async def plan_install(self, params: MCPPlanInstallRequest, *, timeout: float | None = None) -> MCPPlanInstallResult: + "Requests a side-effect-free MCP install plan from a catalog candidate handle or a caller-supplied card. This host-implemented server method is available through SDK/TUI hosts; standalone and C-ABI runtimes whose host does not implement server-method dispatch return JSON-RPC MethodNotFound. A runtime with planning available returns a normalised plan and opaque single-use plan handle; a runtime without it returns the typed planning-unavailable result. A completed plan reports resource identity, provenance, eligible transport choices, the user-scope target, required typed values and secret placeholders, the policy result, the configuration changes installing would make, and whether a reload would be needed. Planning never writes configuration, stores a secret, or reloads MCP servers, so abandoning a plan needs no call and leaves nothing behind.\n\nArgs:\n params: A side-effect-free request for an MCP install plan. Computing a plan never writes configuration, stores a secret, or reloads MCP servers.\n\nReturns:\n Outcome of an mcp.planInstall call: either a normalised plan, or one typed refusal. Nothing is written in either case." + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + return _load_MCPPlanInstallResult(await self._client.request("mcp.planInstall", params_dict, **_timeout_kwargs(timeout))) + # Experimental: this API group is experimental and may change or be removed. class ServerExtensionsApi: @@ -31794,6 +39682,28 @@ async def disable(self, params: DiscoveredExtensionsDisableRequest, *, timeout: await self._client.request("extensions.disable", params_dict, **_timeout_kwargs(timeout)) +# Experimental: this API group is experimental and may change or be removed. +class ServerCatalogApi: + def __init__(self, client: "JsonRpcClient"): + self._client = client + + async def search(self, params: CatalogSearchRequest, *, timeout: float | None = None) -> CatalogSearchResult: + "Requests a bounded catalog search. This host-implemented server method is available through SDK/TUI hosts; standalone and C-ABI runtimes whose host does not implement server-method dispatch return JSON-RPC MethodNotFound. A runtime with search available returns inert candidate summaries, each with an opaque single-use handle scoped to this runtime instance; a runtime without it returns the typed search-unavailable result. Public authorities may be searched anonymously, while an authority that requires credentials yields the typed authentication-required result. All returned text, URLs, and package metadata are untrusted external data and can never trigger instructions, tools, or installation. Read-only: nothing is installed, configured, or persisted.\n\nArgs:\n params: A bounded catalog search. Both the query length and the result count are capped by the schema so a caller cannot request an unbounded scan.\n\nReturns:\n Outcome of a catalog.search call: either bounded inert candidates, or one typed refusal. Never a partial success." + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + return _load_CatalogSearchResult(await self._client.request("catalog.search", params_dict, **_timeout_kwargs(timeout))) + + +# Experimental: this API group is experimental and may change or be removed. +class ServerPluginsBuiltinApi: + def __init__(self, client: "JsonRpcClient"): + self._client = client + + async def set(self, params: PluginsBuiltinSetRequest, *, timeout: float | None = None) -> None: + "Replaces this server's trusted built-in plugin directories while no sessions are active.\n\nArgs:\n params: Trusted built-in plugin directories to use for this runtime process." + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + await self._client.request("plugins.builtin.set", params_dict, **_timeout_kwargs(timeout)) + + # Experimental: this API group is experimental and may change or be removed. class ServerPluginsMarketplacesApi: def __init__(self, client: "JsonRpcClient"): @@ -31828,6 +39738,7 @@ async def refresh(self, params: PluginsMarketplacesRefreshRequest, *, timeout: f class ServerPluginsApi: def __init__(self, client: "JsonRpcClient"): self._client = client + self.builtin = ServerPluginsBuiltinApi(client) self.marketplaces = ServerPluginsMarketplacesApi(client) async def list(self, *, timeout: float | None = None) -> PluginListResult: @@ -31874,6 +39785,11 @@ async def set_disabled_skills(self, params: SkillsConfigSetDisabledSkillsRequest params_dict = {k: v for k, v in params.to_dict().items() if v is not None} await self._client.request("skills.config.setDisabledSkills", params_dict, **_timeout_kwargs(timeout)) + async def set_skill_disabled(self, params: SkillsConfigSetSkillDisabledRequest, *, timeout: float | None = None) -> None: + "Atomically adds or removes one skill from the disabled list.\n\nArgs:\n params: Adds or removes a single skill from the global disabled list, leaving every other entry untouched." + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + await self._client.request("skills.config.setSkillDisabled", params_dict, **_timeout_kwargs(timeout)) + # Experimental: this API group is experimental and may change or be removed. class ServerSkillsApi: @@ -31960,6 +39876,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"): @@ -32026,6 +39952,11 @@ async def list(self, params: SessionsListRequest, *, timeout: float | None = Non params_dict = {k: v for k, v in params.to_dict().items() if v is not None} return SessionList.from_dict(await self._client.request("sessions.list", params_dict, **_timeout_kwargs(timeout))) + async def read_persisted_events(self, params: SessionsReadPersistedEventsRequest, *, timeout: float | None = None) -> EventsReadResult: + "Reads a page of durable events directly from a local session's persisted journal without creating, resuming, or activating the session. The initial backward read uses a bounded tail scan for fast first paint; cursor continuations preserve the session event-log paging semantics. Persisted events may omit payloads that are reconstructed only for an active session.\n\nArgs:\n params: Pagination options for reading an inactive or active local session's persisted event journal.\n\nReturns:\n Batch of session events returned by a read, with cursor and continuation metadata." + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + return EventsReadResult.from_dict(await self._client.request("sessions.readPersistedEvents", params_dict, **_timeout_kwargs(timeout))) + async def find_by_task_id(self, params: SessionsFindByTaskIDRequest, *, timeout: float | None = None) -> SessionsFindByTaskIDResult: "Finds the local session bound to a GitHub task ID, if any.\n\nArgs:\n params: GitHub task ID to look up.\n\nReturns:\n ID of the local session bound to the given GitHub task, or omitted when none." params_dict = {k: v for k, v in params.to_dict().items() if v is not None} @@ -32135,18 +40066,21 @@ class ServerRpc: """Typed server-scoped RPC methods.""" def __init__(self, client: "JsonRpcClient"): self._client = client + self.hooks = ServerHooksApi(client) self.models = ServerModelsApi(client) self.tools = ServerToolsApi(client) self.account = ServerAccountApi(client) self.secrets = ServerSecretsApi(client) self.mcp = ServerMcpApi(client) self.extensions = ServerExtensionsApi(client) + self.catalog = ServerCatalogApi(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) @@ -32158,6 +40092,10 @@ 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 uses its built-in extension launcher.\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: @@ -32212,11 +40150,22 @@ def __init__(self, client: "JsonRpcClient"): self.sessions = _InternalServerSessionsApi(client) async def _connect(self, params: _ConnectRequest, *, timeout: float | None = None) -> _ConnectResult: - "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.\n\nArgs:\n params: Parameters for the `server.connect` handshake: an optional connection token and optional connection-level opt-ins (e.g. GitHub telemetry forwarding).\n\nReturns:\n Handshake result reporting the server's protocol version and package version on success.\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." + "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.\n\nArgs:\n params: Connection-level opt-ins for the `server.connect` handshake. Transport authentication is consumed by the native protocol boundary before dispatch.\n\nReturns:\n Handshake result reporting the server's protocol version and package version on success.\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 = {k: v for k, v in params.to_dict().items() if v is not None} return _ConnectResult.from_dict(await self._client.request("connect", params_dict, **_timeout_kwargs(timeout))) +# Experimental: this API group is experimental and may change or be removed. +class SandboxApi: + def __init__(self, client: "JsonRpcClient", session_id: str): + self._client = client + self._session_id = session_id + + async def get_enforcement_status(self, *, timeout: float | None = None) -> SandboxEnforcementStatus: + "Returns whether managed policy requires sandbox enforcement and whether an enforcement failure has permanently blocked the session.\n\nReturns:\n Managed sandbox enforcement state for a session." + return SandboxEnforcementStatus.from_dict(await self._client.request("session.sandbox.getEnforcementStatus", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + + # Experimental: this API group is experimental and may change or be removed. class GitHubAuthApi: def __init__(self, client: "JsonRpcClient", session_id: str): @@ -32332,9 +40281,11 @@ async def get_run(self, params: FactoryGetRunRequest, *, timeout: float | 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, *, timeout: float | None = None) -> FactoryListRunsResult: - "Lists durable factory runs for this session in creation order.\n\nReturns:\n Factory runs in durable creation order." - return FactoryListRunsResult.from_dict(await self._client.request("session.factory.listRuns", {"sessionId": self._session_id}, **_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." @@ -32406,11 +40357,11 @@ async def get(self, *, timeout: float | None = None) -> SessionMode: "Gets the current agent interaction mode.\n\nReturns:\n The session mode the agent is operating in" return SessionMode(await self._client.request("session.mode.get", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) - async def set(self, params: ModeSetRequest, *, timeout: float | None = None) -> None: - "Sets the current agent interaction mode.\n\nArgs:\n params: Agent interaction mode to apply to the session." + async def set(self, params: ModeSetRequest, *, timeout: float | None = None) -> ModeSetResult: + "Sets the current agent interaction mode.\n\nArgs:\n params: Agent interaction mode to apply to the session.\n\nReturns:\n Outcome of a session mode change, including any model switch it triggered and follow-up the host must perform." 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.mode.set", params_dict, **_timeout_kwargs(timeout)) + return ModeSetResult.from_dict(await self._client.request("session.mode.set", params_dict, **_timeout_kwargs(timeout))) # Experimental: this API group is experimental and may change or be removed. @@ -32756,6 +40707,12 @@ async def login(self, params: MCPOauthLoginRequest, *, timeout: float | None = N 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 probe(self, params: MCPOauthProbeRequest, *, timeout: float | None = None) -> MCPOauthProbeResult: + "Passively probes a configured remote MCP server to classify whether OAuth is required or a cached/override token is accepted. Does not start OAuth, emit pending OAuth requests, or mutate MCP connection state.\n\nArgs:\n params: Remote MCP server name for a passive OAuth status probe.\n\nReturns:\n Passive MCP OAuth probe result. `authenticated` means the server accepted the probe request while an OAuth-origin access token was attached; it does not prove the server required or independently validated that token. The probe does not make a second unauthenticated request. Failed is an expected probe-domain outcome; JSON-RPC errors are reserved for API-call failures." + 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 MCPOauthProbeResult.from_dict(await self._client.request("session.mcp.oauth.probe", 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} @@ -32878,6 +40835,10 @@ async def reload(self, *, timeout: float | None = None) -> None: "Reloads MCP server connections for the session." await self._client.request("session.mcp.reload", {"sessionId": self._session_id}, **_timeout_kwargs(timeout)) + async def move_loading_to_background(self, *, timeout: float | None = None) -> MoveMCPLoadingToBackgroundResult: + "Releases any turns waiting on an in-flight MCP load without cancelling the load, letting the agent proceed while MCP servers finish connecting in the background. No-op when no MCP load is in flight or waiting turns were already released.\n\nReturns:\n Result of moving in-flight MCP loading to the background." + return MoveMCPLoadingToBackgroundResult.from_dict(await self._client.request("session.mcp.moveLoadingToBackground", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + async def execute_sampling(self, params: MCPExecuteSamplingParams, *, timeout: float | None = None) -> MCPSamplingExecutionResult: "Runs an MCP sampling inference on behalf of an MCP server.\n\nArgs:\n params: Identifiers and raw MCP CreateMessageRequest params used to run a sampling inference.\n\nReturns:\n Outcome of an MCP sampling execution: success result, failure error, or cancellation." params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} @@ -33026,6 +40987,24 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id + async def execute(self, params: ToolsExecuteRequest, *, timeout: float | None = None) -> ToolResultExpanded: + "Executes one tool from the session's currently offered tool set through the native invocation pipeline.\n\nArgs:\n params: A tool name and arguments to execute through the session's native invocation pipeline.\n\nReturns:\n Canonical result returned by a session tool." + 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 await self._client.request("session.tools.execute", params_dict, **_timeout_kwargs(timeout)) + + async def get_builtin_descriptors(self, params: ToolsGetBuiltinDescriptorsRequest, *, timeout: float | None = None) -> ToolsGetBuiltinDescriptorsResult: + "Returns the Rust-owned built-in tool descriptors used to construct the session's offered tool set.\n\nArgs:\n params: Options controlling how Rust-owned built-in tool descriptors are materialized.\n\nReturns:\n Rust-owned built-in tool descriptors for the session." + 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 ToolsGetBuiltinDescriptorsResult.from_dict(await self._client.request("session.tools.getBuiltinDescriptors", params_dict, **_timeout_kwargs(timeout))) + + async def task_complete_event_data(self, params: ToolsTaskCompleteEventDataRequest, *, timeout: float | None = None) -> TaskCompleteData: + "Projects a completed task_complete tool call into its label-safe session event payload.\n\nArgs:\n params: Task-completion tool arguments and final result used to build a label-safe session event payload.\n\nReturns:\n Task completion notification with summary from the 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 + return TaskCompleteData.from_dict(await self._client.request("session.tools.taskCompleteEventData", params_dict, **_timeout_kwargs(timeout))) + async def handle_pending_tool_call(self, params: HandlePendingToolCallRequest, *, timeout: float | None = None) -> HandlePendingToolCallResult: "Provides the result for a pending external tool call.\n\nArgs:\n params: Pending external tool call request ID, with the tool result or an error describing why it failed.\n\nReturns:\n Indicates whether the external tool call result was handled successfully." params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} @@ -33040,6 +41019,12 @@ async def get_current_metadata(self, *, timeout: float | None = None) -> ToolsGe "Returns lightweight metadata for the session's currently initialized tools.\n\nReturns:\n Current lightweight tool metadata snapshot for the session." return ToolsGetCurrentMetadataResult.from_dict(await self._client.request("session.tools.getCurrentMetadata", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + async def set(self, params: ToolsSetRequest, *, timeout: float | None = None) -> ToolsSetResult: + "Atomically replaces the complete externally implemented tool list supplied by the calling connection. Built-in, MCP/plugin, extension-discovered, subagent, and tools supplied by other connections remain unchanged.\n\nArgs:\n params: Complete externally implemented tool list for the calling connection. An empty list removes every tool previously supplied by that connection.\n\nReturns:\n Empty result after replacing the calling connection's externally implemented tools." + 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 ToolsSetResult.from_dict(await self._client.request("session.tools.set", params_dict, **_timeout_kwargs(timeout))) + async def update_subagent_settings(self, params: UpdateSubagentSettingsRequest, *, timeout: float | None = None) -> ToolsUpdateSubagentSettingsResult: "Updates the current session's live subagent settings after user settings change. The persisted user settings remain the source of truth for future sessions.\n\nArgs:\n params: Subagent settings to apply to the current session\n\nReturns:\n Empty result after applying subagent settings" params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} @@ -33114,7 +41099,7 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._session_id = session_id async def ephemeral_query(self, params: UIEphemeralQueryRequest, *, timeout: float | None = None) -> UIEphemeralQueryResult: - "Runs a transient no-tools model query against the current conversation context.\n\nArgs:\n params: Transient question to answer without adding it to conversation history.\n\nReturns:\n Transient answer generated from current conversation context." + "Runs a transient no-tools model query against the current conversation context.\n\nArgs:\n params: Transient question to answer without adding it to conversation history.\n\nReturns:\n Completed transient query. Ordered chunks and the terminal outcome are also delivered through `ui.ephemeral_query` session events while it runs." 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 UIEphemeralQueryResult.from_dict(await self._client.request("session.ui.ephemeralQuery", params_dict, **_timeout_kwargs(timeout))) @@ -33183,7 +41168,7 @@ async def list(self, *, timeout: float | None = None) -> PermissionPathsList: return PermissionPathsList.from_dict(await self._client.request("session.permissions.paths.list", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) async def add(self, params: PermissionPathsAddParams, *, timeout: float | None = None) -> PermissionsPathsAddResult: - "Adds a directory to the session's allow-list.\n\nArgs:\n params: Directory path to add to the session's allowed directories.\n\nReturns:\n Indicates whether the operation succeeded." + "Adds a directory to the session's allow-list and activates conventional skill and agent definitions under it.\n\nArgs:\n params: Directory path to add to the session's allowed directories.\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 PermissionsPathsAddResult.from_dict(await self._client.request("session.permissions.paths.add", params_dict, **_timeout_kwargs(timeout))) @@ -33296,15 +41281,15 @@ async def set_approve_all(self, params: PermissionsSetApproveAllRequest, *, time params_dict["sessionId"] = self._session_id return PermissionsSetApproveAllResult.from_dict(await self._client.request("session.permissions.setApproveAll", params_dict, **_timeout_kwargs(timeout))) - async def set_allow_all(self, params: PermissionsSetAllowAllRequest, *, timeout: float | None = None) -> AllowAllPermissionSetResult: - "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.\n\nArgs:\n params: Allow-all mode to apply for the session.\n\nReturns:\n Indicates whether the operation succeeded and reports the post-mutation state." + async def set_mode(self, params: PermissionsSetModeRequest, *, timeout: float | None = None) -> PermissionsSetModeResult: + "Sets the permission mode for the session. `manual` follows the normal approval flow, `assisted` attaches LLM safety recommendations, and `allow-all` automatically approves permission requests. The result returns the authoritative post-mutation mode so callers can update local state without racing the `session.permissions_changed` notification.\n\nArgs:\n params: Permission mode to apply for the session.\n\nReturns:\n Indicates whether the requested permission mode was applied and reports the authoritative post-mutation 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 AllowAllPermissionSetResult.from_dict(await self._client.request("session.permissions.setAllowAll", params_dict, **_timeout_kwargs(timeout))) + return PermissionsSetModeResult.from_dict(await self._client.request("session.permissions.setMode", params_dict, **_timeout_kwargs(timeout))) - async def get_allow_all(self, *, timeout: float | None = None) -> AllowAllPermissionState: - "Returns the current allow-all permission mode for the session.\n\nReturns:\n Current allow-all permission mode." - return AllowAllPermissionState.from_dict(await self._client.request("session.permissions.getAllowAll", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + async def get_mode(self, *, timeout: float | None = None) -> PermissionsGetModeResult: + "Returns the current permission mode for the session.\n\nReturns:\n Current permission mode." + return PermissionsGetModeResult.from_dict(await self._client.request("session.permissions.getMode", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) async def modify_rules(self, params: PermissionsModifyRulesParams, *, timeout: float | None = None) -> PermissionsModifyRulesResult: "Adds or removes session-scoped or location-scoped permission rules.\n\nArgs:\n params: Scope and add/remove instructions for modifying session- or location-scoped permission rules.\n\nReturns:\n Indicates whether the operation succeeded." @@ -33657,6 +41642,7 @@ class SessionRpc: def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id + self.sandbox = SandboxApi(client, session_id) self.git_hub_auth = GitHubAuthApi(client, session_id) self.debug = DebugApi(client, session_id) self.canvas = CanvasApi(client, session_id) @@ -33740,6 +41726,112 @@ async def log(self, params: LogRequest, *, timeout: float | None = None) -> LogR return LogResult.from_dict(await self._client.request("session.log", params_dict, **_timeout_kwargs(timeout))) +# Experimental: this API group is experimental and may change or be removed. +class _InternalGitHubAuthApi: + def __init__(self, client: "JsonRpcClient", session_id: str): + self._client = client + self._session_id = session_id + + async def _get_current_auth_info(self, *, timeout: float | None = None) -> AuthIdentity | None: + "Gets the current authentication information for internal session hosts.\n\nReturns:\n Current authentication information, or null when no authentication is active.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + _result = await self._client.request("session.gitHubAuth.getCurrentAuthInfo", {"sessionId": self._session_id}, **_timeout_kwargs(timeout)) + return AuthIdentity(_result) if _result is not None else None + + async def _get_all_auth_available(self, *, timeout: float | None = None) -> list: + "Gets all authentication accounts available to the internal session host.\n\nReturns:\n Authentication accounts available to the internal session host.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + return list(await self._client.request("session.gitHubAuth.getAllAuthAvailable", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + + async def _refresh_copilot_user(self, *, timeout: float | None = None) -> AuthIdentity | None: + "Refreshes Copilot account metadata for the current authentication.\n\nReturns:\n Current authentication information, or null when no authentication is active.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + _result = await self._client.request("session.gitHubAuth.refreshCopilotUser", {"sessionId": self._session_id}, **_timeout_kwargs(timeout)) + return AuthIdentity(_result) if _result is not None else None + + async def _login(self, params: SessionAuthLoginRequest, *, timeout: float | None = None) -> AuthInfo: + "Logs in a GitHub user through the internal session host.\n\nArgs:\n params: Internal GitHub login parameters.\n\nReturns:\n Authentication credentials accepted only at native protocol ingress. Runtime outputs use credential-free `AuthIdentity` metadata.\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 _load_AuthInfo(await self._client.request("session.gitHubAuth.login", params_dict, **_timeout_kwargs(timeout))) + + async def _switch_to_auth(self, params: SessionAuthSwitchRequest, *, timeout: float | None = None) -> None: + "Switches the session to another available authentication.\n\nArgs:\n params: Parameters for switching the session's active authentication.\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.gitHubAuth.switchToAuth", params_dict, **_timeout_kwargs(timeout)) + + async def _logout(self, *, timeout: float | None = None) -> bool: + "Logs out the session's current GitHub authentication.\n\nReturns:\n Whether the current authentication was logged out.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + return bool(await self._client.request("session.gitHubAuth.logout", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + + async def _logout_user(self, params: SessionAuthLogoutUserRequest, *, timeout: float | None = None) -> bool: + "Logs out a specific GitHub authentication.\n\nArgs:\n params: Parameters identifying a GitHub authentication to log out.\n\nReturns:\n Whether the requested authentication was logged out.\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 bool(await self._client.request("session.gitHubAuth.logoutUser", params_dict, **_timeout_kwargs(timeout))) + + async def _last_auth_errors(self, *, timeout: float | None = None) -> list: + "Gets validation errors from the most recent authentication attempt.\n\nReturns:\n Validation errors from the most recent authentication attempt.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + return list(await self._client.request("session.gitHubAuth.lastAuthErrors", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + + +# Experimental: this API group is experimental and may change or be removed. +class _InternalCanvasProviderApi: + def __init__(self, client: "JsonRpcClient", session_id: str): + self._client = client + self._session_id = session_id + + async def _register(self, params: CanvasProviderRegisterRequest, *, timeout: float | None = None) -> None: + "Registers an internal canvas provider connection and its contributions.\n\nArgs:\n params: Internal canvas provider registration parameters.\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.canvas.provider.register", params_dict, **_timeout_kwargs(timeout)) + + async def _unregister(self, params: CanvasProviderUnregisterRequest, *, timeout: float | None = None) -> None: + "Unregisters an internal canvas provider connection.\n\nArgs:\n params: Internal canvas provider unregistration parameters.\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.canvas.provider.unregister", params_dict, **_timeout_kwargs(timeout)) + + +# Experimental: this API group is experimental and may change or be removed. +class _InternalCanvasApi: + def __init__(self, client: "JsonRpcClient", session_id: str): + self._client = client + self._session_id = session_id + self.provider = _InternalCanvasProviderApi(client, session_id) + + +# Experimental: this API group is experimental and may change or be removed. +class _InternalFactoryApi: + def __init__(self, client: "JsonRpcClient", session_id: str): + self._client = client + self._session_id = session_id + + async def _run_from_tool(self, params: _FactoryToolRunRequest, *, timeout: float | None = None) -> FactoryRunResult: + "Internal tool-originated factory invocation.\n\nArgs:\n params: Internal parameters for invoking a registered factory from a tool.\n\nReturns:\n Complete current or terminal factory run envelope.\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 FactoryRunResult.from_dict(await self._client.request("session.factory.runFromTool", params_dict, **_timeout_kwargs(timeout))) + + async def _resume_from_tool(self, params: _FactoryToolResumeRequest, *, timeout: float | None = None) -> FactoryResumeResult: + "Internal tool-originated factory resume.\n\nArgs:\n params: Internal parameters for resuming a factory run from a tool.\n\nReturns:\n Resolved persisted factory identity and resumed run envelope.\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 FactoryResumeResult.from_dict(await self._client.request("session.factory.resumeFromTool", params_dict, **_timeout_kwargs(timeout))) + + +# Experimental: this API group is experimental and may change or be removed. +class _InternalModelApi: + def __init__(self, client: "JsonRpcClient", session_id: str): + self._client = client + self._session_id = session_id + + async def _apply_startup_overlay(self, params: ModelApplyStartupOverlayRequest, *, timeout: float | None = None) -> ModelSwitchToResult: + "Resolves and applies organization-managed and repository model overlays.\n\nArgs:\n params: Managed, repository, and CLI model overrides to overlay onto the session at startup.\n\nReturns:\n The model identifier active on the session after the switch.\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 ModelSwitchToResult.from_dict(await self._client.request("session.model.applyStartupOverlay", params_dict, **_timeout_kwargs(timeout))) + + # Experimental: this API group is experimental and may change or be removed. class _InternalMcpApi: def __init__(self, client: "JsonRpcClient", session_id: str): @@ -33753,7 +41845,7 @@ async def _reload_with_config(self, params: MCPReloadWithConfigRequest, *, timeo return MCPStartServersResult.from_dict(await self._client.request("session.mcp.reloadWithConfig", params_dict, **_timeout_kwargs(timeout))) async def _configure_git_hub(self, params: MCPConfigureGitHubRequest, *, timeout: float | None = None) -> MCPConfigureGitHubResult: - "Configures the built-in GitHub MCP server for the session's current auth context.\n\nArgs:\n params: Opaque auth info used to configure GitHub MCP.\n\nReturns:\n Result of configuring GitHub MCP.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + "Configures the built-in GitHub MCP server for the session's current auth context.\n\nArgs:\n params: Credential-free authentication identity used to configure GitHub MCP.\n\nReturns:\n Result of configuring GitHub MCP.\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 MCPConfigureGitHubResult.from_dict(await self._client.request("session.mcp.configureGitHub", params_dict, **_timeout_kwargs(timeout))) @@ -33771,6 +41863,19 @@ async def _unregister_external_client(self, params: MCPUnregisterExternalClientR await self._client.request("session.mcp.unregisterExternalClient", params_dict, **_timeout_kwargs(timeout)) +# Experimental: this API group is experimental and may change or be removed. +class _InternalCommandsApi: + def __init__(self, client: "JsonRpcClient", session_id: str): + self._client = client + self._session_id = session_id + + async def _finalize_invocation_effect(self, params: CommandsFinalizeInvocationEffectRequest, *, timeout: float | None = None) -> CommandsFinalizeInvocationEffectResult: + "Finalizes persistence associated with a client-applied slash-command effect.\n\nArgs:\n params: The pending slash-command invocation effect to finalize, plus whether the host applied or cancelled it.\n\nReturns:\n Whether finalizing the invocation effect succeeded, and the failure reason when it did not.\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 CommandsFinalizeInvocationEffectResult.from_dict(await self._client.request("session.commands.finalizeInvocationEffect", params_dict, **_timeout_kwargs(timeout))) + + # Experimental: this API group is experimental and may change or be removed. class _InternalSettingsApi: def __init__(self, client: "JsonRpcClient", session_id: str): @@ -33885,7 +41990,12 @@ class _InternalSessionRpc: def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id + self.git_hub_auth = _InternalGitHubAuthApi(client, session_id) + self.canvas = _InternalCanvasApi(client, session_id) + self.factory = _InternalFactoryApi(client, session_id) + self.model = _InternalModelApi(client, session_id) self.mcp = _InternalMcpApi(client, session_id) + self.commands = _InternalCommandsApi(client, session_id) self.settings = _InternalSettingsApi(client, session_id) self.queue = _InternalQueueApi(client, session_id) self.schedule = _InternalScheduleApi(client, session_id) @@ -34118,6 +42228,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: @@ -34133,11 +42249,19 @@ async def event(self, params: GitHubTelemetryNotification) -> None: "Forwards a single GitHub telemetry event to a host connection that opted into telemetry forwarding during the `server.connect` handshake. Opted-in connections receive every event the runtime emits after the handshake — across all sessions, plus sessionless events (for example, `server.sendTelemetry` calls with no session id).\n\nArgs:\n params: 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." pass +# Experimental: this API group is experimental and may change or be removed. +class GitHubTokenHandler(Protocol): + async def get_token(self, params: GitHubTokenAcquireRequest) -> GitHubTokenAcquireResult: + "Asks the SDK client to mint a GitHub access token for a session whose configuration supplied a GitHub token provider. The runtime acquires the initial token during bootstrap and refreshes it during expiry preflight when one hour or less remains.\n\nArgs:\n params: Asks the SDK client to acquire a GitHub access token from an opaque callback registration.\n\nReturns:\n SDK host response to a GitHub credential request." + pass + @dataclass class ClientGlobalApiHandlers: hooks: HooksHandler | None = None + extension_launch_provider: ExtensionLaunchProviderHandler | None = None llm_inference: LlmInferenceHandler | None = None git_hub_telemetry: GitHubTelemetryHandler | None = None + git_hub_token: GitHubTokenHandler | None = None def register_client_global_api_handlers( client: "JsonRpcClient", @@ -34156,6 +42280,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 @@ -34177,6 +42308,13 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: await handler.event(request) return None client.set_notification_method_handler("gitHubTelemetry.event", handle_git_hub_telemetry_event) + async def handle_git_hub_token_get_token(params: dict) -> dict | None: + request = GitHubTokenAcquireRequest.from_dict(params) + handler = handlers.git_hub_token + if handler is None: raise RuntimeError("No git_hub_token client-global handler registered") + result = await handler.get_token(request) + return result.value if hasattr(result, 'value') else result + client.set_request_handler("gitHubToken.getToken", handle_git_hub_token_get_token) __all__ = [ "APIKeyAuthInfo", @@ -34231,13 +42369,21 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "AgentSetPromptRequest", "AgentsDiscoverRequest", "AgentsGetDiscoveryPathsRequest", - "AllowAllPermissionSetResult", - "AllowAllPermissionState", "ApprovalKind", + "AuthIdentity", "AuthInfo", "AuthInfoType", + "AuthValidationError", + "AuthValidationErrors", "BuiltInModelCatalog", "BuiltInModelCatalogEntry", + "BuiltinToolDescriptor", + "BuiltinToolFormat", + "BuiltinToolFormatType", + "BuiltinToolInputSchema", + "BuiltinToolInputSchemaType", + "BuiltinToolSafeForTelemetry", + "BuiltinToolSafeTelemetryFields", "CancelUserRequestedShellCommandResult", "CanvasAction", "CanvasActionApi", @@ -34256,14 +42402,89 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "CanvasProviderInvokeActionRequest", "CanvasProviderOpenRequest", "CanvasProviderOpenResult", + "CanvasProviderRegisterRequest", + "CanvasProviderUnregisterRequest", "CanvasSessionContext", "CapiSessionOptions", + "CardDigest", + "CardDigestAlgorithm", + "CardDigestValue", + "CatalogAISkillCandidate", + "CatalogAISkillCandidateKind", + "CatalogAISkillCandidateProvenance", + "CatalogAuthenticationRequiredError", + "CatalogAuthenticationRequiredErrorKind", + "CatalogAuthenticationRequiredReason", + "CatalogCandidate", + "CatalogCandidateInstallability", + "CatalogCandidateKind", + "CatalogCandidateProvenance", + "CatalogCandidateSource", + "CatalogCandidateSourceEmbedded", + "CatalogCandidateSourceKind", + "CatalogCandidateSourceURL", + "CatalogCapability", + "CatalogCapabilityId", + "CatalogClientContract", + "CatalogContractViolationError", + "CatalogContractViolationErrorKind", + "CatalogContractViolationReason", + "CatalogHandleRejectedError", + "CatalogHandleRejectedErrorKind", + "CatalogHandleRejectionReason", + "CatalogHandleType", + "CatalogInvalidRequestError", + "CatalogInvalidRequestErrorKind", + "CatalogInvalidRequestField", + "CatalogMCPServerCandidate", + "CatalogMCPServerCandidateKind", + "CatalogMCPServerCandidateProvenance", + "CatalogMCPServerInstallabilityEnum", + "CatalogMalformedCardError", + "CatalogMalformedCardErrorKind", + "CatalogMalformedCardReason", + "CatalogMcpServerInstallability", + "CatalogMediaType", + "CatalogNegotiatedContract", + "CatalogNegotiationRefusedError", + "CatalogNegotiationRefusedErrorKind", + "CatalogNegotiationRefusedReason", + "CatalogNetworkFailureError", + "CatalogNetworkFailureErrorKind", + "CatalogNetworkFailureReason", + "CatalogNotInstallableError", + "CatalogNotInstallableErrorKind", + "CatalogNotInstallableReason", + "CatalogPolicyRejectedError", + "CatalogPolicyRejectedErrorKind", + "CatalogSearchRequest", + "CatalogSearchResult", + "CatalogSearchResultKind", + "CatalogSearchResultReason", + "CatalogSearchSucceeded", + "CatalogSearchSucceededKind", + "CatalogUnavailableError", + "CatalogUnavailableErrorKind", + "CatalogUnavailableReason", + "CatalogUnavailableTransportError", + "CatalogUnavailableTransportErrorKind", + "CatalogUnavailableTransportReason", + "CatalogUnsafeRetrievalError", + "CatalogUnsafeRetrievalErrorKind", + "CatalogUnsafeRetrievalReason", + "CatalogUnsupportedKindError", + "CatalogUnsupportedKindErrorKind", + "Categories", "ClientGlobalApiHandlers", "ClientSessionApiHandlers", "CommandList", "CommandsApi", + "CommandsFinalizeInvocationEffectRequest", + "CommandsFinalizeInvocationEffectResult", "CommandsHandlePendingCommandRequest", "CommandsHandlePendingCommandResult", + "CommandsInvocationEffectOutcome", + "CommandsInvocationOrigin", "CommandsInvokeRequest", "CommandsListRequest", "CommandsRespondToQueuedCommandRequest", @@ -34313,6 +42534,7 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "DiscoveredExtensions", "DiscoveredExtensionsDisableRequest", "DiscoveredExtensionsEnableRequest", + "DiscoveredHook", "DiscoveredMCPServer", "DiscoveredMCPServerType", "EnqueueCommandParams", @@ -34334,6 +42556,10 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "Extension", "ExtensionContextPushInput", "ExtensionContextPushInputType", + "ExtensionLaunchProfile", + "ExtensionLaunchProviderHandler", + "ExtensionLaunchProviderResolveRequest", + "ExtensionLaunchProviderResolveResult", "ExtensionList", "ExtensionSource", "ExtensionStatus", @@ -34419,6 +42645,11 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "GitHubTelemetryEvent", "GitHubTelemetryHandler", "GitHubTelemetryNotification", + "GitHubTokenAcquireReason", + "GitHubTokenAcquireRequest", + "GitHubTokenAcquireResult", + "GitHubTokenAcquireResultKind", + "GitHubTokenHandler", "HMACAuthInfo", "HMACAuthInfoType", "HandlePendingToolCallRequest", @@ -34447,15 +42678,22 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "HistorySummarizeForHandoffResult", "HistoryTruncateRequest", "HistoryTruncateResult", + "HookOrigin", + "HookType", + "HooksDiscoverRequest", + "HooksDiscoverResult", "HooksHandler", "Host", "HostType", + "InstallMethod", + "Installability", "InstalledPlugin", "InstalledPluginInfo", "InstalledPluginSource", "InstalledPluginSourceGitHub", "InstalledPluginSourceLocal", "InstalledPluginSourceURL", + "InstalledPluginSourceURLSource", "InstructionDiscoveryPath", "InstructionDiscoveryPathKind", "InstructionDiscoveryPathList", @@ -34520,8 +42758,10 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "MCPDisableRequest", "MCPDiscoverRequest", "MCPDiscoverResult", + "MCPElicitationFormMode", "MCPEnableRequest", "MCPExecuteSamplingParams", + "MCPFailedServer", "MCPFilteredServer", "MCPGrantType", "MCPHeadersHandlePendingHeadersRefreshRequest", @@ -34529,6 +42769,7 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "MCPHeadersHandlePendingHeadersRefreshRequestRequest", "MCPHeadersHandlePendingHeadersRefreshRequestResult", "MCPHostState", + "MCPInstallPlan", "MCPIsServerRunningRequest", "MCPIsServerRunningResult", "MCPListToolsRequest", @@ -34539,10 +42780,52 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "MCPOauthLoginRequest", "MCPOauthLoginResult", "MCPOauthPendingRequestResponse", - "MCPOauthPendingRequestResponseKind", + "MCPOauthProbeNeedsAuthReason", + "MCPOauthProbeRequest", + "MCPOauthProbeResult", "MCPOauthRespondRequest", "MCPOauthRespondResult", + "MCPPlan", + "MCPPlanConfigurationChange", + "MCPPlanConfigurationOperation", + "MCPPlanETransport", + "MCPPlanInstallPlanned", + "MCPPlanInstallPlannedKind", + "MCPPlanInstallRequest", + "MCPPlanInstallResult", + "MCPPlanInstallResultKind", + "MCPPlanInstallResultReason", + "MCPPlanInstallSource", + "MCPPlanInstallSourceCandidate", + "MCPPlanInstallSourceCandidateKind", + "MCPPlanInstallSourceCard", + "MCPPlanInstallSourceCardKind", + "MCPPlanInstallSourceKind", + "MCPPlanPackageInstallMethod", + "MCPPlanPackageTransport", + "MCPPlanPolicyDecision", + "MCPPlanPolicyResult", + "MCPPlanPolicySource", + "MCPPlanProvenance", + "MCPPlanRemoteInstallMethod", + "MCPPlanRemoteTransport", + "MCPPlanRequiredValue", + "MCPPlanRequiredValueEnum", + "MCPPlanRequiredValueKind", + "MCPPlanRequiredValueScalar", + "MCPPlanRequiredValueScalarKind", + "MCPPlanRequiredValueValueType", + "MCPPlanResourceIdentity", + "MCPPlanScalarValueTypeEnum", + "MCPPlanScope", + "MCPPlanSecretPlaceholder", + "MCPPlanTarget", + "MCPPlanTransportChoice", + "MCPPlanTransportChoicePackage", + "MCPPlanTransportChoiceRemote", + "MCPPlanValueCategory", "MCPRegisterExternalClientRequest", + "MCPReloadConfig", "MCPReloadWithConfigRequest", "MCPRemoveGitHubResult", "MCPResource", @@ -34557,15 +42840,28 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "MCPResourcesReadRequest", "MCPResourcesReadResult", "MCPRestartServerRequest", + "MCPSafeForTelemetryFields", "MCPSamplingExecutionAction", "MCPSamplingExecutionResult", + "MCPSerializableServerConfig", + "MCPSerializableServerConfigType", "MCPServer", "MCPServerAuthConfigRedirectPort", + "MCPServerCardEmbedded", + "MCPServerCardEmbeddedKind", + "MCPServerCardMediaType", + "MCPServerCardReference", + "MCPServerCardURL", + "MCPServerCardURLKind", "MCPServerConfig", "MCPServerConfigDeferTools", "MCPServerConfigHTTP", "MCPServerConfigHTTPType", + "MCPServerConfigMemory", + "MCPServerConfigMemoryType", "MCPServerConfigStdio", + "MCPServerConfigStdioType", + "MCPServerConfigType", "MCPServerFailureInfo", "MCPServerList", "MCPServerNeedsAuthInfo", @@ -34575,10 +42871,12 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "MCPStartServerRequest", "MCPStartServersResult", "MCPStopServerRequest", + "MCPTaskMetadata", "MCPToolUI", "MCPToolUIVisibility", "MCPTools", "MCPUnregisterExternalClientRequest", + "ManagedSettingsReadResult", "MarketplaceAddResult", "MarketplaceBrowseResult", "MarketplaceInfo", @@ -34601,9 +42899,20 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "McpHeadersApi", "McpOauthApi", "McpOauthLoginGrantType", + "McpPlanEnumValueType", + "McpPlanInstallResult", + "McpPlanInstallSource", + "McpPlanRequiredValue", + "McpPlanRequiredValueEnumKind", + "McpPlanScalarValueType", + "McpPlanSecretReference", + "McpPlanTransportChoice", "McpResourcesApi", + "McpSafeForTelemetry", "McpServerAuthConfig", + "McpServerCardReference", "McpServerConfigHttpOauthGrantType", + "MediaType", "MemoryConfiguration", "MetadataApi", "MetadataContextAttributionResult", @@ -34624,8 +42933,10 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "MetadataSnapshotRemoteMetadataTaskType", "ModeApi", "ModeSetRequest", + "ModeSetResult", "Model", "ModelApi", + "ModelApplyStartupOverlayRequest", "ModelBilling", "ModelBillingPromo", "ModelBillingTokenPrices", @@ -34640,15 +42951,21 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "ModelCapabilitiesSupports", "ModelList", "ModelListRequest", + "ModelMessage", "ModelPickerCategory", + "ModelPickerPersistenceRequest", "ModelPickerPriceCategory", + "ModelPickerSettingsContext", "ModelPolicy", "ModelPolicyState", "ModelSetReasoningEffortRequest", "ModelSetReasoningEffortResult", + "ModelSwitchConfirmation", "ModelSwitchToRequest", "ModelSwitchToResult", + "ModelWarningText", "ModelsListRequest", + "MoveMCPLoadingToBackgroundResult", "NameApi", "NameGetResult", "NameSetAutoRequest", @@ -34674,6 +42991,8 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "PermissionDecisionApproveForLocationApprovalCommandsKind", "PermissionDecisionApproveForLocationApprovalCustomTool", "PermissionDecisionApproveForLocationApprovalCustomToolKind", + "PermissionDecisionApproveForLocationApprovalExtensionEnvAccess", + "PermissionDecisionApproveForLocationApprovalExtensionEnvAccessKind", "PermissionDecisionApproveForLocationApprovalExtensionManagement", "PermissionDecisionApproveForLocationApprovalExtensionManagementKind", "PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess", @@ -34685,7 +43004,6 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "PermissionDecisionApproveForLocationApprovalMCPSampling", "PermissionDecisionApproveForLocationApprovalMCPSamplingKind", "PermissionDecisionApproveForLocationApprovalMemory", - "PermissionDecisionApproveForLocationApprovalMemoryKind", "PermissionDecisionApproveForLocationApprovalRead", "PermissionDecisionApproveForLocationApprovalReadKind", "PermissionDecisionApproveForLocationApprovalWrite", @@ -34695,6 +43013,7 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "PermissionDecisionApproveForSessionApproval", "PermissionDecisionApproveForSessionApprovalCommands", "PermissionDecisionApproveForSessionApprovalCustomTool", + "PermissionDecisionApproveForSessionApprovalExtensionEnvAccess", "PermissionDecisionApproveForSessionApprovalExtensionManagement", "PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess", "PermissionDecisionApproveForSessionApprovalFactory", @@ -34716,6 +43035,7 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "PermissionDecisionApprovedKind", "PermissionDecisionCancelled", "PermissionDecisionCancelledKind", + "PermissionDecisionContext", "PermissionDecisionDeniedByContentExclusionPolicy", "PermissionDecisionDeniedByContentExclusionPolicyKind", "PermissionDecisionDeniedByPermissionRequestHook", @@ -34727,9 +43047,12 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser", "PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind", "PermissionDecisionKind", + "PermissionDecisionOutcome", "PermissionDecisionReject", "PermissionDecisionRejectKind", "PermissionDecisionRequest", + "PermissionDecisionSource", + "PermissionDecisionSurface", "PermissionDecisionUserNotAvailable", "PermissionDecisionUserNotAvailableKind", "PermissionLocationAddToolApprovalParams", @@ -34738,6 +43061,7 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "PermissionLocationResolveParams", "PermissionLocationResolveResult", "PermissionLocationType", + "PermissionModeSource", "PermissionPathsAddParams", "PermissionPathsAllowedCheckParams", "PermissionPathsAllowedCheckResult", @@ -34748,10 +43072,11 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "PermissionPathsWorkspaceCheckResult", "PermissionPromptShownNotification", "PermissionRequestResult", + "PermissionResponseCapability", "PermissionRulesSet", + "PermissionSource", "PermissionUrlsConfig", "PermissionUrlsSetUnrestrictedModeParams", - "PermissionsAllowAllMode", "PermissionsApi", "PermissionsConfigureAdditionalContentExclusionPolicy", "PermissionsConfigureAdditionalContentExclusionPolicyRule", @@ -34761,10 +43086,12 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "PermissionsConfigureResult", "PermissionsFolderTrustAddTrustedResult", "PermissionsFolderTrustApi", - "PermissionsGetAllowAllRequest", + "PermissionsGetModeRequest", + "PermissionsGetModeResult", "PermissionsLocationsAddToolApprovalDetails", "PermissionsLocationsAddToolApprovalDetailsCommands", "PermissionsLocationsAddToolApprovalDetailsCustomTool", + "PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess", "PermissionsLocationsAddToolApprovalDetailsExtensionManagement", "PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess", "PermissionsLocationsAddToolApprovalDetailsFactory", @@ -34786,12 +43113,11 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "PermissionsPendingRequestsRequest", "PermissionsResetSessionApprovalsRequest", "PermissionsResetSessionApprovalsResult", - "PermissionsSetAAllSource", - "PermissionsSetAllowAllRequest", - "PermissionsSetAllowAllSource", "PermissionsSetApproveAllRequest", "PermissionsSetApproveAllResult", "PermissionsSetApproveAllSource", + "PermissionsSetModeRequest", + "PermissionsSetModeResult", "PermissionsSetRequiredRequest", "PermissionsSetRequiredResult", "PermissionsUrlsApi", @@ -34813,6 +43139,7 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "PluginUpdateAllResult", "PluginUpdateResult", "PluginsApi", + "PluginsBuiltinSetRequest", "PluginsDisableRequest", "PluginsEnableRequest", "PluginsInstallRequest", @@ -34823,6 +43150,8 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "PluginsReloadRequest", "PluginsUninstallRequest", "PluginsUpdateRequest", + "ProtocolExternalToolDefer", + "ProtocolExternalToolDefinition", "ProviderAddRequest", "ProviderAddResult", "ProviderApi", @@ -34938,13 +43267,16 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "RemoteNotifySteerableChangedRequest", "RemoteNotifySteerableChangedResult", "RemoteSessionConnectionResult", + "RemoteSessionHostStatus", "RemoteSessionMetadataRepository", "RemoteSessionMetadataTaskType", "RemoteSessionMetadataValue", "RemoteSessionMode", "RemoteSessionRepository", "RunOptions", + "SandboxApi", "SandboxConfig", + "SandboxConfigAuth", "SandboxConfigUserPolicy", "SandboxConfigUserPolicyExperimental", "SandboxConfigUserPolicyExperimentalSeatbelt", @@ -34952,6 +43284,7 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "SandboxConfigUserPolicyNetwork", "SandboxConfigUserPolicyNetworkProxy", "SandboxConfigUserPolicySeatbelt", + "SandboxEnforcementStatus", "Saved", "ScheduleAddAtRequest", "ScheduleAddCronRequest", @@ -34980,15 +43313,19 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "ServerAgentList", "ServerAgentRegistryApi", "ServerAgentsApi", + "ServerCatalogApi", "ServerCommandsApi", "ServerExtensionsApi", + "ServerHooksApi", "ServerInstructionSourceList", "ServerInstructionsApi", "ServerLlmInferenceApi", + "ServerManagedSettingsApi", "ServerMcpApi", "ServerMcpConfigApi", "ServerModelsApi", "ServerPluginsApi", + "ServerPluginsBuiltinApi", "ServerPluginsMarketplacesApi", "ServerRpc", "ServerRuntimeApi", @@ -35004,7 +43341,11 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "ServerUserSettingsApi", "SessionActivity", "SessionAgentListRequest", + "SessionAuthInfoResult", + "SessionAuthLoginRequest", + "SessionAuthLogoutUserRequest", "SessionAuthStatus", + "SessionAuthSwitchRequest", "SessionBulkDeleteResult", "SessionCancelAllBackgroundAgentsResult", "SessionCapability", @@ -35049,6 +43390,9 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "SessionFSWriteFileRequest", "SessionFsHandler", "SessionFsReaddirWithTypesEntryType", + "SessionGitHubAuthGetAllAuthAvailableResult", + "SessionGitHubAuthLogoutResult", + "SessionGitHubAuthLogoutUserResult", "SessionHistoryCompactRequest", "SessionInstalledPlugin", "SessionInstalledPluginSource", @@ -35071,6 +43415,8 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "SessionListFilter", "SessionLoadDeferredRepoHooksResult", "SessionLogLevel", + "SessionManagedPermissions", + "SessionManagedSettings", "SessionMcpAppsCallToolResult", "SessionMetadataSnapshot", "SessionModelList", @@ -35150,13 +43496,13 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "SessionsOpenProgressStatus", "SessionsOpenProgressStep", "SessionsOpenRemote", - "SessionsOpenRemoteKind", "SessionsOpenResume", "SessionsOpenResumeKind", "SessionsOpenResumeLast", "SessionsOpenResumeLastKind", "SessionsOpenStatus", "SessionsPruneOldRequest", + "SessionsReadPersistedEventsRequest", "SessionsRegisterExtensionToolsOnSessionOptions", "SessionsReleaseLockRequest", "SessionsReleaseLockResult", @@ -35170,8 +43516,13 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "SessionsStartRemoteControlRequest", "SessionsStopRemoteControlRequest", "SessionsTransferRemoteControlRequest", + "SettableAuthInfo", + "SettableAuthInfoType", + "SettableTokenAuthInfo", + "SettableTokenAuthInfoType", "ShellApi", "ShellCancelUserRequestedRequest", + "ShellCredentials", "ShellExecRequest", "ShellExecResult", "ShellExecuteUserRequestedRequest", @@ -35188,8 +43539,11 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "SkillDiscoveryPathList", "SkillDiscoveryScope", "SkillList", + "SkillProviderDescriptor", + "SkillProviderListRequest", "SkillsApi", "SkillsConfigSetDisabledSkillsRequest", + "SkillsConfigSetSkillDisabledRequest", "SkillsDisableRequest", "SkillsDiscoverRequest", "SkillsEnableRequest", @@ -35197,6 +43551,8 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "SkillsGetInvokedResult", "SkillsInvokedSkill", "SkillsLoadDiagnostics", + "SlashCommandAddTimelineEntryResult", + "SlashCommandAddTimelineEntryResultKind", "SlashCommandAgentPromptResult", "SlashCommandAgentPromptResultKind", "SlashCommandCompletedResult", @@ -35208,17 +43564,28 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "SlashCommandInvocationResult", "SlashCommandInvocationResultKind", "SlashCommandKind", + "SlashCommandModelPickerDialog", + "SlashCommandModelPickerDialogKind", "SlashCommandSelectSubcommandOption", "SlashCommandSelectSubcommandResult", "SlashCommandSelectSubcommandResultKind", + "SlashCommandSetModelResult", + "SlashCommandSetModelResultKind", + "SlashCommandSetPlanModelResult", + "SlashCommandSetPlanModelResultKind", + "SlashCommandShowDialogResult", + "SlashCommandShowDialogResultKind", "SlashCommandTextResult", - "StickySource", + "SlashCommandTimelineEntry", + "Status", "SubagentSettings", "SubagentSettingsEntry", "SubagentSettingsEntryContextTier", "TaskAgentInfo", "TaskAgentInfoType", "TaskAgentProgress", + "TaskCompleteData", + "TaskCompletionDecision", "TaskExecutionMode", "TaskInfo", "TaskInfoExecutionMode", @@ -35252,16 +43619,27 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "TasksWaitForPendingResult", "TelemetryApi", "TelemetrySetFeatureOverridesRequest", - "TentacledSource", "Theme", "TokenAuthInfo", - "TokenAuthInfoType", + "TokenProviderAuthInfo", + "TokenProviderAuthInfoType", "Tool", "ToolList", + "ToolResult", + "ToolResultExpanded", + "ToolResultNewMessage", + "ToolResultType", "ToolsApi", + "ToolsExecuteRequest", + "ToolsGetBuiltinDescriptorsRequest", + "ToolsGetBuiltinDescriptorsResult", "ToolsGetCurrentMetadataResult", "ToolsInitializeAndValidateResult", "ToolsListRequest", + "ToolsSetRequest", + "ToolsSetResult", + "ToolsShellDescriptorConfig", + "ToolsTaskCompleteEventDataRequest", "ToolsUpdateSubagentSettingsResult", "Trigger", "UIAutoModeSwitchResponse", @@ -35286,7 +43664,6 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "UIElicitationSchemaPropertyString", "UIElicitationSchemaPropertyStringFormat", "UIElicitationSchemaPropertyType", - "UIElicitationSchemaType", "UIElicitationStringEnumField", "UIElicitationStringOneOfField", "UIElicitationStringOneOfFieldOneOf", @@ -35311,6 +43688,7 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "UpdateSubagentSettingsRequest", "UsageApi", "UsageGetMetricsResult", + "UsageMetricsAgentMetric", "UsageMetricsCodeChanges", "UsageMetricsModelMetric", "UsageMetricsModelMetricRequests", @@ -35318,7 +43696,6 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "UsageMetricsModelMetricUsage", "UsageMetricsTokenDetail", "UserAuthInfo", - "UserAuthInfoType", "UserRequestedShellCommandResult", "UserSettingMetadata", "UserSettingsGetResult", diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py index 6978a5a6d6..51e719bcf9 100644 --- a/python/copilot/generated/session_events.py +++ b/python/copilot/generated/session_events.py @@ -24,8 +24,9 @@ def from_str(x: Any) -> str: def from_int(x: Any) -> int: - assert isinstance(x, int) and not isinstance(x, bool) - return x + assert isinstance(x, (int, float)) and not isinstance(x, bool) + assert not isinstance(x, float) or x.is_integer() + return int(x) def to_int(x: Any) -> int: @@ -136,7 +137,9 @@ class SessionEventType(Enum): SESSION_WARNING = "session.warning" SESSION_MODEL_CHANGE = "session.model_change" SESSION_MODE_CHANGED = "session.mode_changed" + SESSION_MODE_NOTICE_DELIVERED = "session.mode_notice_delivered" SESSION_SESSION_LIMITS_CHANGED = "session.session_limits_changed" + # Experimental: this event is part of an experimental API and may change or be removed. SESSION_PERMISSIONS_CHANGED = "session.permissions_changed" SESSION_PLAN_CHANGED = "session.plan_changed" SESSION_TODOS_CHANGED = "session.todos_changed" @@ -152,11 +155,30 @@ class SessionEventType(Enum): SESSION_COMPACTION_START = "session.compaction_start" SESSION_COMPACTION_COMPLETE = "session.compaction_complete" SESSION_TASK_COMPLETE = "session.task_complete" + # Experimental: this event is part of an experimental API and may change or be removed. + SESSION_COMPLETION_RECEIPT = "session.completion_receipt" + # Experimental: this event is part of an experimental API and may change or be removed. + SESSION_FUSION_ROUTE_STARTED = "session.fusion_route_started" + # Experimental: this event is part of an experimental API and may change or be removed. + SESSION_FUSION_ROUTE_FAILED = "session.fusion_route_failed" + # Experimental: this event is part of an experimental API and may change or be removed. + SESSION_FUSION_RESOLVED = "session.fusion_resolved" + # Experimental: this event is part of an experimental API and may change or be removed. + SESSION_FUSION_COMPLETED = "session.fusion_completed" USER_MESSAGE = "user.message" PENDING_MESSAGES_MODIFIED = "pending_messages.modified" ASSISTANT_TURN_START = "assistant.turn_start" ASSISTANT_TURN_RETRY = "assistant.turn_retry" + AGENT_INTERRUPTED = "agent.interrupted" ASSISTANT_INTENT = "assistant.intent" + # Experimental: this event is part of an experimental API and may change or be removed. + ASSISTANT_FUSION_PHASE_STARTED = "assistant.fusion_phase_started" + # Experimental: this event is part of an experimental API and may change or be removed. + ASSISTANT_FUSION_PHASE_ACTIVITY = "assistant.fusion_phase_activity" + # Experimental: this event is part of an experimental API and may change or be removed. + ASSISTANT_FUSION_PHASE_COMPLETED = "assistant.fusion_phase_completed" + # Experimental: this event is part of an experimental API and may change or be removed. + ASSISTANT_FUSION_PHASE_FAILED = "assistant.fusion_phase_failed" ASSISTANT_SERVER_TOOL_PROGRESS = "assistant.server_tool_progress" ASSISTANT_REASONING = "assistant.reasoning" ASSISTANT_REASONING_DELTA = "assistant.reasoning_delta" @@ -168,7 +190,9 @@ class SessionEventType(Enum): ASSISTANT_TURN_END = "assistant.turn_end" ASSISTANT_IDLE = "assistant.idle" ASSISTANT_USAGE = "assistant.usage" + PROMPT_CACHE_BREAK = "prompt_cache_break" MODEL_CALL_FAILURE = "model.call_failure" + MODEL_CALL_FINISHED = "model.call_finished" MODEL_CALL_START = "model.call_start" ABORT = "abort" TOOL_USER_REQUESTED = "tool.user_requested" @@ -178,7 +202,9 @@ class SessionEventType(Enum): TOOL_EXECUTION_COMPLETE = "tool.execution_complete" TOOL_SEARCH_ACTIVATED = "tool_search.activated" SKILL_INVOKED = "skill.invoked" + SANDBOX_DECISION = "sandbox.decision" SUBAGENT_STARTED = "subagent.started" + SUBAGENT_CONFIGURED = "subagent.configured" SUBAGENT_COMPLETED = "subagent.completed" SUBAGENT_FAILED = "subagent.failed" SUBAGENT_SELECTED = "subagent.selected" @@ -203,6 +229,8 @@ class SessionEventType(Enum): MCP_HEADERS_REFRESH_REQUIRED = "mcp.headers_refresh_required" MCP_HEADERS_REFRESH_COMPLETED = "mcp.headers_refresh_completed" SESSION_CUSTOM_NOTIFICATION = "session.custom_notification" + # Experimental: this event is part of an experimental API and may change or be removed. + UI_EPHEMERAL_QUERY = "ui.ephemeral_query" EXTERNAL_TOOL_REQUESTED = "external_tool.requested" EXTERNAL_TOOL_COMPLETED = "external_tool.completed" COMMAND_QUEUED = "command.queued" @@ -226,6 +254,10 @@ class SessionEventType(Enum): 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" + # Experimental: this event is part of an experimental API and may change or be removed. + FACTORY_RUN_STARTED = "factory.run_started" + # Experimental: this event is part of an experimental API and may change or be removed. + FACTORY_RUN_SETTLED = "factory.run_settled" SESSION_SKILLS_LOADED = "session.skills_loaded" SESSION_CUSTOM_AGENTS_UPDATED = "session.custom_agents_updated" SESSION_MCP_SERVERS_LOADED = "session.mcp_servers_loaded" @@ -370,6 +402,273 @@ def to_dict(self) -> dict: return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class AssistantFusionPhaseActivityData: + "Experimental content-safe activity signal for a running HydraFusion phase." + activity: FusionPhaseActivityKind + conversation_scope: FusionConversationScope + fusion_id: str + pattern: FusionPattern + phase_id: str + phase_kind: FusionPhaseKind + role: str + tool_call_id: str | None = None + total_response_size_bytes: int | None = None + + @staticmethod + def from_dict(obj: Any) -> "AssistantFusionPhaseActivityData": + assert isinstance(obj, dict) + activity = parse_enum(FusionPhaseActivityKind, obj.get("activity")) + conversation_scope = parse_enum(FusionConversationScope, obj.get("conversationScope")) + fusion_id = from_str(obj.get("fusionId")) + pattern = parse_enum(FusionPattern, obj.get("pattern")) + phase_id = from_str(obj.get("phaseId")) + phase_kind = parse_enum(FusionPhaseKind, obj.get("phaseKind")) + role = from_str(obj.get("role")) + tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) + total_response_size_bytes = from_union([from_none, from_int], obj.get("totalResponseSizeBytes")) + return AssistantFusionPhaseActivityData( + activity=activity, + conversation_scope=conversation_scope, + fusion_id=fusion_id, + pattern=pattern, + phase_id=phase_id, + phase_kind=phase_kind, + role=role, + tool_call_id=tool_call_id, + total_response_size_bytes=total_response_size_bytes, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["activity"] = to_enum(FusionPhaseActivityKind, self.activity) + result["conversationScope"] = to_enum(FusionConversationScope, self.conversation_scope) + result["fusionId"] = from_str(self.fusion_id) + result["pattern"] = to_enum(FusionPattern, self.pattern) + result["phaseId"] = from_str(self.phase_id) + result["phaseKind"] = to_enum(FusionPhaseKind, self.phase_kind) + result["role"] = from_str(self.role) + if self.tool_call_id is not None: + result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) + if self.total_response_size_bytes is not None: + result["totalResponseSizeBytes"] = from_union([from_none, to_int], self.total_response_size_bytes) + return result + + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class AssistantFusionPhaseCompletedData: + "Experimental durable HydraFusion phase output and lossless replay checkpoint." + content: str + conversation_scope: FusionConversationScope + duration_ms: float + fusion_id: str + model: str + phase_id: str + phase_kind: FusionPhaseKind + role: str + status: FusionPhaseStatus + usage: FusionPhaseUsage + verdict: str | None + # Internal: this field is an internal SDK API and is not part of the public surface. + _projection_message: Any = None + # Internal: this field is an internal SDK API and is not part of the public surface. + _projection_mode: _FusionProjectionMode | None = None + # Internal: this field is an internal SDK API and is not part of the public surface. + _staged_terminal: _FusionStagedTerminal | None = None + + @staticmethod + def from_dict(obj: Any) -> "AssistantFusionPhaseCompletedData": + assert isinstance(obj, dict) + content = from_str(obj.get("content")) + conversation_scope = parse_enum(FusionConversationScope, obj.get("conversationScope")) + duration_ms = from_float(obj.get("durationMs")) + fusion_id = from_str(obj.get("fusionId")) + model = from_str(obj.get("model")) + phase_id = from_str(obj.get("phaseId")) + phase_kind = parse_enum(FusionPhaseKind, obj.get("phaseKind")) + role = from_str(obj.get("role")) + status = parse_enum(FusionPhaseStatus, obj.get("status")) + usage = FusionPhaseUsage.from_dict(obj.get("usage")) + verdict = from_union([from_none, from_str], obj.get("verdict")) + _projection_message = obj.get("projectionMessage") + _projection_mode = from_union([from_none, lambda x: parse_enum(_FusionProjectionMode, x)], obj.get("projectionMode")) + _staged_terminal = from_union([from_none, _FusionStagedTerminal.from_dict], obj.get("stagedTerminal")) + return AssistantFusionPhaseCompletedData( + content=content, + conversation_scope=conversation_scope, + duration_ms=duration_ms, + fusion_id=fusion_id, + model=model, + phase_id=phase_id, + phase_kind=phase_kind, + role=role, + status=status, + usage=usage, + verdict=verdict, + _projection_message=_projection_message, + _projection_mode=_projection_mode, + _staged_terminal=_staged_terminal, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["content"] = from_str(self.content) + result["conversationScope"] = to_enum(FusionConversationScope, self.conversation_scope) + result["durationMs"] = to_float(self.duration_ms) + result["fusionId"] = from_str(self.fusion_id) + result["model"] = from_str(self.model) + result["phaseId"] = from_str(self.phase_id) + result["phaseKind"] = to_enum(FusionPhaseKind, self.phase_kind) + result["role"] = from_str(self.role) + result["status"] = to_enum(FusionPhaseStatus, self.status) + result["usage"] = to_class(FusionPhaseUsage, self.usage) + result["verdict"] = from_union([from_none, from_str], self.verdict) + if self._projection_message is not None: + result["projectionMessage"] = self._projection_message + if self._projection_mode is not None: + result["projectionMode"] = from_union([from_none, lambda x: to_enum(_FusionProjectionMode, x)], self._projection_mode) + if self._staged_terminal is not None: + result["stagedTerminal"] = from_union([from_none, lambda x: to_class(_FusionStagedTerminal, x)], self._staged_terminal) + return result + + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class AssistantFusionPhaseFailedData: + "Experimental durable typed HydraFusion phase failure and degradation transition." + conversation_scope: FusionConversationScope + duration_ms: float + fusion_id: str + model: str + phase_id: str + phase_kind: FusionPhaseKind + reason: str + role: str + status: FusionPhaseStatus + usage: FusionPhaseUsage + degraded_to_phase_id: str | None = None + error_message: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "AssistantFusionPhaseFailedData": + assert isinstance(obj, dict) + conversation_scope = parse_enum(FusionConversationScope, obj.get("conversationScope")) + duration_ms = from_float(obj.get("durationMs")) + fusion_id = from_str(obj.get("fusionId")) + model = from_str(obj.get("model")) + phase_id = from_str(obj.get("phaseId")) + phase_kind = parse_enum(FusionPhaseKind, obj.get("phaseKind")) + reason = from_str(obj.get("reason")) + role = from_str(obj.get("role")) + status = parse_enum(FusionPhaseStatus, obj.get("status")) + usage = FusionPhaseUsage.from_dict(obj.get("usage")) + degraded_to_phase_id = from_union([from_none, from_str], obj.get("degradedToPhaseId")) + error_message = from_union([from_none, from_str], obj.get("errorMessage")) + return AssistantFusionPhaseFailedData( + conversation_scope=conversation_scope, + duration_ms=duration_ms, + fusion_id=fusion_id, + model=model, + phase_id=phase_id, + phase_kind=phase_kind, + reason=reason, + role=role, + status=status, + usage=usage, + degraded_to_phase_id=degraded_to_phase_id, + error_message=error_message, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["conversationScope"] = to_enum(FusionConversationScope, self.conversation_scope) + result["durationMs"] = to_float(self.duration_ms) + result["fusionId"] = from_str(self.fusion_id) + result["model"] = from_str(self.model) + result["phaseId"] = from_str(self.phase_id) + result["phaseKind"] = to_enum(FusionPhaseKind, self.phase_kind) + result["reason"] = from_str(self.reason) + result["role"] = from_str(self.role) + result["status"] = to_enum(FusionPhaseStatus, self.status) + result["usage"] = to_class(FusionPhaseUsage, self.usage) + if self.degraded_to_phase_id is not None: + result["degradedToPhaseId"] = from_union([from_none, from_str], self.degraded_to_phase_id) + if self.error_message is not None: + result["errorMessage"] = from_union([from_none, from_str], self.error_message) + return result + + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class AssistantFusionPhaseStartedData: + "Experimental transient HydraFusion phase/model/role signal." + conversation_scope: FusionConversationScope + fusion_id: str + model: str + pattern: FusionPattern + phase_id: str + phase_kind: FusionPhaseKind + role: str + + @staticmethod + def from_dict(obj: Any) -> "AssistantFusionPhaseStartedData": + assert isinstance(obj, dict) + conversation_scope = parse_enum(FusionConversationScope, obj.get("conversationScope")) + fusion_id = from_str(obj.get("fusionId")) + model = from_str(obj.get("model")) + pattern = parse_enum(FusionPattern, obj.get("pattern")) + phase_id = from_str(obj.get("phaseId")) + phase_kind = parse_enum(FusionPhaseKind, obj.get("phaseKind")) + role = from_str(obj.get("role")) + return AssistantFusionPhaseStartedData( + conversation_scope=conversation_scope, + fusion_id=fusion_id, + model=model, + pattern=pattern, + phase_id=phase_id, + phase_kind=phase_kind, + role=role, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["conversationScope"] = to_enum(FusionConversationScope, self.conversation_scope) + result["fusionId"] = from_str(self.fusion_id) + result["model"] = from_str(self.model) + result["pattern"] = to_enum(FusionPattern, self.pattern) + result["phaseId"] = from_str(self.phase_id) + result["phaseKind"] = to_enum(FusionPhaseKind, self.phase_kind) + result["role"] = from_str(self.role) + return result + + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class AssistantMessageReasoningBlocks: + "Neutral provider-tagged reasoning content blocks preserved verbatim for round-tripping" + provider: str + blocks: list[Any] | None = None + + @staticmethod + def from_dict(obj: Any) -> "AssistantMessageReasoningBlocks": + assert isinstance(obj, dict) + provider = from_str(obj.get("provider")) + blocks = from_union([from_none, lambda x: from_list(lambda x: x, x)], obj.get("blocks")) + return AssistantMessageReasoningBlocks( + provider=provider, + blocks=blocks, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["provider"] = from_str(self.provider) + if self.blocks is not None: + result["blocks"] = from_union([from_none, lambda x: from_list(lambda x: x, x)], self.blocks) + return result + + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class AssistantMessageServerTools: @@ -777,6 +1076,75 @@ def to_dict(self) -> dict: return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryRunSettledData: + "Ephemeral signal that a factory run reached a terminal status." + consumed_nano_aiu: int + consumed_subagents: int + elapsed_ms: int + run_id: str + status: FactoryRunSettledStatus + failure_type: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "FactoryRunSettledData": + assert isinstance(obj, dict) + consumed_nano_aiu = from_int(obj.get("consumedNanoAiu")) + consumed_subagents = from_int(obj.get("consumedSubagents")) + elapsed_ms = from_int(obj.get("elapsedMs")) + run_id = from_str(obj.get("runId")) + status = parse_enum(FactoryRunSettledStatus, obj.get("status")) + failure_type = from_union([from_none, from_str], obj.get("failureType")) + return FactoryRunSettledData( + consumed_nano_aiu=consumed_nano_aiu, + consumed_subagents=consumed_subagents, + elapsed_ms=elapsed_ms, + run_id=run_id, + status=status, + failure_type=failure_type, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["consumedNanoAiu"] = to_int(self.consumed_nano_aiu) + result["consumedSubagents"] = to_int(self.consumed_subagents) + result["elapsedMs"] = to_int(self.elapsed_ms) + result["runId"] = from_str(self.run_id) + result["status"] = to_enum(FactoryRunSettledStatus, self.status) + if self.failure_type is not None: + result["failureType"] = from_union([from_none, from_str], self.failure_type) + return result + + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryRunStartedData: + "Ephemeral signal that a factory run attempt began executing." + attempt: int + factory_name: str + run_id: str + + @staticmethod + def from_dict(obj: Any) -> "FactoryRunStartedData": + assert isinstance(obj, dict) + attempt = from_int(obj.get("attempt")) + factory_name = from_str(obj.get("factoryName")) + run_id = from_str(obj.get("runId")) + return FactoryRunStartedData( + attempt=attempt, + factory_name=factory_name, + run_id=run_id, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["attempt"] = to_int(self.attempt) + result["factoryName"] = from_str(self.factory_name) + 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 FactoryRunUpdatedData: @@ -801,6 +1169,238 @@ def to_dict(self) -> dict: return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FusionAttribution: + "Experimental attribution linking an ordinary event to the HydraFusion turn, phase, and concrete source that produced it." + fusion_id: str + pattern: str + policy: str + synthetic_model: str + commit_id: str | None = None + conversation_scope: str | None = None + phase_id: str | None = None + phase_kind: str | None = None + role: str | None = None + source_model: str | None = None + source_phase_id: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "FusionAttribution": + assert isinstance(obj, dict) + fusion_id = from_str(obj.get("fusionId")) + pattern = from_str(obj.get("pattern")) + policy = from_str(obj.get("policy")) + synthetic_model = from_str(obj.get("syntheticModel")) + commit_id = from_union([from_none, from_str], obj.get("commitId")) + conversation_scope = from_union([from_none, from_str], obj.get("conversationScope")) + phase_id = from_union([from_none, from_str], obj.get("phaseId")) + phase_kind = from_union([from_none, from_str], obj.get("phaseKind")) + role = from_union([from_none, from_str], obj.get("role")) + source_model = from_union([from_none, from_str], obj.get("sourceModel")) + source_phase_id = from_union([from_none, from_str], obj.get("sourcePhaseId")) + return FusionAttribution( + fusion_id=fusion_id, + pattern=pattern, + policy=policy, + synthetic_model=synthetic_model, + commit_id=commit_id, + conversation_scope=conversation_scope, + phase_id=phase_id, + phase_kind=phase_kind, + role=role, + source_model=source_model, + source_phase_id=source_phase_id, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["fusionId"] = from_str(self.fusion_id) + result["pattern"] = from_str(self.pattern) + result["policy"] = from_str(self.policy) + result["syntheticModel"] = from_str(self.synthetic_model) + if self.commit_id is not None: + result["commitId"] = from_union([from_none, from_str], self.commit_id) + if self.conversation_scope is not None: + result["conversationScope"] = from_union([from_none, from_str], self.conversation_scope) + if self.phase_id is not None: + result["phaseId"] = from_union([from_none, from_str], self.phase_id) + if self.phase_kind is not None: + result["phaseKind"] = from_union([from_none, from_str], self.phase_kind) + if self.role is not None: + result["role"] = from_union([from_none, from_str], self.role) + if self.source_model is not None: + result["sourceModel"] = from_union([from_none, from_str], self.source_model) + if self.source_phase_id is not None: + result["sourcePhaseId"] = from_union([from_none, from_str], self.source_phase_id) + return result + + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FusionFollowUpRecommendation: + "Durable server recommendation for subsequent HydraFusion turns." + compaction_turn: FusionFollowUpAction + user_turn: FusionFollowUpAction + + @staticmethod + def from_dict(obj: Any) -> "FusionFollowUpRecommendation": + assert isinstance(obj, dict) + compaction_turn = parse_enum(FusionFollowUpAction, obj.get("compactionTurn")) + user_turn = parse_enum(FusionFollowUpAction, obj.get("userTurn")) + return FusionFollowUpRecommendation( + compaction_turn=compaction_turn, + user_turn=user_turn, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["compactionTurn"] = to_enum(FusionFollowUpAction, self.compaction_turn) + result["userTurn"] = to_enum(FusionFollowUpAction, self.user_turn) + return result + + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FusionPhasePlanStep: + "Presentation-neutral phase planned for a HydraFusion turn." + conditional: bool + kind: FusionPhaseKind + role: str + scope: FusionConversationScope + + @staticmethod + def from_dict(obj: Any) -> "FusionPhasePlanStep": + assert isinstance(obj, dict) + conditional = from_bool(obj.get("conditional")) + kind = parse_enum(FusionPhaseKind, obj.get("kind")) + role = from_str(obj.get("role")) + scope = parse_enum(FusionConversationScope, obj.get("scope")) + return FusionPhasePlanStep( + conditional=conditional, + kind=kind, + role=role, + scope=scope, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["conditional"] = from_bool(self.conditional) + result["kind"] = to_enum(FusionPhaseKind, self.kind) + result["role"] = from_str(self.role) + result["scope"] = to_enum(FusionConversationScope, self.scope) + return result + + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FusionPhaseUsage: + "Aggregate concrete-model usage for one HydraFusion phase." + cached_tokens: int + input_tokens: int + output_tokens: int + request_count: int + total_nano_aiu: float + cache_write_tokens: int | None = None + + @staticmethod + def from_dict(obj: Any) -> "FusionPhaseUsage": + assert isinstance(obj, dict) + cached_tokens = from_int(obj.get("cachedTokens")) + input_tokens = from_int(obj.get("inputTokens")) + output_tokens = from_int(obj.get("outputTokens")) + request_count = from_int(obj.get("requestCount")) + total_nano_aiu = from_float(obj.get("totalNanoAiu")) + cache_write_tokens = from_union([from_none, from_int], obj.get("cacheWriteTokens")) + return FusionPhaseUsage( + cached_tokens=cached_tokens, + input_tokens=input_tokens, + output_tokens=output_tokens, + request_count=request_count, + total_nano_aiu=total_nano_aiu, + cache_write_tokens=cache_write_tokens, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["cachedTokens"] = to_int(self.cached_tokens) + result["inputTokens"] = to_int(self.input_tokens) + result["outputTokens"] = to_int(self.output_tokens) + result["requestCount"] = to_int(self.request_count) + result["totalNanoAiu"] = to_float(self.total_nano_aiu) + if self.cache_write_tokens is not None: + result["cacheWriteTokens"] = from_union([from_none, to_int], self.cache_write_tokens) + return result + + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FusionScores: + "Validated HydraFusion routing capability scores." + code_gen: float + debugging: float + reasoning: float + tool_use: float + + @staticmethod + def from_dict(obj: Any) -> "FusionScores": + assert isinstance(obj, dict) + code_gen = from_float(obj.get("codeGen")) + debugging = from_float(obj.get("debugging")) + reasoning = from_float(obj.get("reasoning")) + tool_use = from_float(obj.get("toolUse")) + return FusionScores( + code_gen=code_gen, + debugging=debugging, + reasoning=reasoning, + tool_use=tool_use, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["codeGen"] = to_float(self.code_gen) + result["debugging"] = to_float(self.debugging) + result["reasoning"] = to_float(self.reasoning) + result["toolUse"] = to_float(self.tool_use) + return result + + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class _FusionStagedTerminal: + "Internal durable terminal request staged by a HydraFusion phase until an idempotent final commit selects it." + arguments: str + assistant_message: Any + phase_id: str + tool_call_id: str + tool_name: str + + @staticmethod + def from_dict(obj: Any) -> "_FusionStagedTerminal": + assert isinstance(obj, dict) + arguments = from_str(obj.get("arguments")) + assistant_message = obj.get("assistantMessage") + phase_id = from_str(obj.get("phaseId")) + tool_call_id = from_str(obj.get("toolCallId")) + tool_name = from_str(obj.get("toolName")) + return _FusionStagedTerminal( + arguments=arguments, + assistant_message=assistant_message, + phase_id=phase_id, + tool_call_id=tool_call_id, + tool_name=tool_name, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["arguments"] = from_str(self.arguments) + result["assistantMessage"] = self.assistant_message + result["phaseId"] = from_str(self.phase_id) + result["toolCallId"] = from_str(self.tool_call_id) + result["toolName"] = from_str(self.tool_name) + return result + + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class OmittedBinaryResult: @@ -845,21 +1445,21 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -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 +class PermissionAssistedApproval: + "Assisted-approval judge information attached to a permission request. Present only in assisted mode; its absence means the judge did not evaluate the request. The `recommendation` conveys the judge's disposition for this request." + recommendation: AssistedApprovalRecommendation + failure_reason: AssistedApprovalJudgeFailureReason | None = None model: str | None = None reason: str | None = None @staticmethod - def from_dict(obj: Any) -> "PermissionAutoApproval": + def from_dict(obj: Any) -> "PermissionAssistedApproval": 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")) + recommendation = parse_enum(AssistedApprovalRecommendation, obj.get("recommendation")) + failure_reason = from_union([from_none, lambda x: parse_enum(AssistedApprovalJudgeFailureReason, 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( + return PermissionAssistedApproval( recommendation=recommendation, failure_reason=failure_reason, model=model, @@ -868,9 +1468,9 @@ def from_dict(obj: Any) -> "PermissionAutoApproval": def to_dict(self) -> dict: result: dict = {} - result["recommendation"] = to_enum(AutoApprovalRecommendation, self.recommendation) + result["recommendation"] = to_enum(AssistedApprovalRecommendation, self.recommendation) if self.failure_reason is not None: - result["failureReason"] = from_union([from_none, lambda x: to_enum(AutoApprovalJudgeFailureReason, x)], self.failure_reason) + result["failureReason"] = from_union([from_none, lambda x: to_enum(AssistedApprovalJudgeFailureReason, 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: @@ -1121,50 +1721,380 @@ class SessionCanvasRemovedData: instance_id: str @staticmethod - def from_dict(obj: Any) -> "SessionCanvasRemovedData": + def from_dict(obj: Any) -> "SessionCanvasRemovedData": + assert isinstance(obj, dict) + canvas_id = from_str(obj.get("canvasId")) + extension_id = from_str(obj.get("extensionId")) + instance_id = from_str(obj.get("instanceId")) + return SessionCanvasRemovedData( + canvas_id=canvas_id, + extension_id=extension_id, + instance_id=instance_id, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["canvasId"] = from_str(self.canvas_id) + result["extensionId"] = from_str(self.extension_id) + result["instanceId"] = from_str(self.instance_id) + return result + + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionCanvasUnavailableData: + "Transient signal that an open canvas instance's provider has dropped (for example the extension is reloading mid-session). The host should keep the panel mounted and surface a reconnecting affordance rather than tearing it down; a subsequent `session.canvas.opened` for the same instanceId clears the affordance once the provider reconnects with a fresh url. Ephemeral and never persisted, so it is never replayed on cold resume." + canvas_id: str + extension_id: str + instance_id: str + + @staticmethod + def from_dict(obj: Any) -> "SessionCanvasUnavailableData": + assert isinstance(obj, dict) + canvas_id = from_str(obj.get("canvasId")) + extension_id = from_str(obj.get("extensionId")) + instance_id = from_str(obj.get("instanceId")) + return SessionCanvasUnavailableData( + canvas_id=canvas_id, + extension_id=extension_id, + instance_id=instance_id, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["canvasId"] = from_str(self.canvas_id) + result["extensionId"] = from_str(self.extension_id) + result["instanceId"] = from_str(self.instance_id) + return result + + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionCompletionReceiptData: + "Behavior-neutral record of structured runtime facts present when an agent completion decision is accepted." + attempt: int + event_range: CompletionReceiptEventRange + failed_tool_count: int + schema_version: int + source_event_id: str + stop_reason: CompletionReceiptStopReason + successful_tool_count: int + final_tool: CompletionReceiptFinalTool | None = None + + @staticmethod + def from_dict(obj: Any) -> "SessionCompletionReceiptData": + assert isinstance(obj, dict) + attempt = from_int(obj.get("attempt")) + event_range = CompletionReceiptEventRange.from_dict(obj.get("eventRange")) + failed_tool_count = from_int(obj.get("failedToolCount")) + schema_version = from_int(obj.get("schemaVersion")) + source_event_id = from_str(obj.get("sourceEventId")) + stop_reason = parse_enum(CompletionReceiptStopReason, obj.get("stopReason")) + successful_tool_count = from_int(obj.get("successfulToolCount")) + final_tool = from_union([from_none, CompletionReceiptFinalTool.from_dict], obj.get("finalTool")) + return SessionCompletionReceiptData( + attempt=attempt, + event_range=event_range, + failed_tool_count=failed_tool_count, + schema_version=schema_version, + source_event_id=source_event_id, + stop_reason=stop_reason, + successful_tool_count=successful_tool_count, + final_tool=final_tool, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["attempt"] = to_int(self.attempt) + result["eventRange"] = to_class(CompletionReceiptEventRange, self.event_range) + result["failedToolCount"] = to_int(self.failed_tool_count) + result["schemaVersion"] = to_int(self.schema_version) + result["sourceEventId"] = from_str(self.source_event_id) + result["stopReason"] = to_enum(CompletionReceiptStopReason, self.stop_reason) + result["successfulToolCount"] = to_int(self.successful_tool_count) + if self.final_tool is not None: + result["finalTool"] = from_union([from_none, lambda x: to_class(CompletionReceiptFinalTool, x)], self.final_tool) + return result + + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionFusionCompletedData: + "Experimental durable aggregate outcome of a HydraFusion turn." + cached_tokens: int + commit_id: str + degraded_reason: str | None + duration_ms: float + final_source_model: str | None + final_source_phase_id: str | None + follow_up_model: str + fusion_id: str + input_tokens: int + outcome: str + output_tokens: int + pattern: FusionPattern + phase_count: int + request_count: int + synthetic_model: str + total_nano_aiu: float + turn_id: str + cache_write_tokens: int | None = None + + @staticmethod + def from_dict(obj: Any) -> "SessionFusionCompletedData": + assert isinstance(obj, dict) + cached_tokens = from_int(obj.get("cachedTokens")) + commit_id = from_str(obj.get("commitId")) + degraded_reason = from_union([from_none, from_str], obj.get("degradedReason")) + duration_ms = from_float(obj.get("durationMs")) + final_source_model = from_union([from_none, from_str], obj.get("finalSourceModel")) + final_source_phase_id = from_union([from_none, from_str], obj.get("finalSourcePhaseId")) + follow_up_model = from_str(obj.get("followUpModel")) + fusion_id = from_str(obj.get("fusionId")) + input_tokens = from_int(obj.get("inputTokens")) + outcome = from_str(obj.get("outcome")) + output_tokens = from_int(obj.get("outputTokens")) + pattern = parse_enum(FusionPattern, obj.get("pattern")) + phase_count = from_int(obj.get("phaseCount")) + request_count = from_int(obj.get("requestCount")) + synthetic_model = from_str(obj.get("syntheticModel")) + total_nano_aiu = from_float(obj.get("totalNanoAiu")) + turn_id = from_str(obj.get("turnId")) + cache_write_tokens = from_union([from_none, from_int], obj.get("cacheWriteTokens")) + return SessionFusionCompletedData( + cached_tokens=cached_tokens, + commit_id=commit_id, + degraded_reason=degraded_reason, + duration_ms=duration_ms, + final_source_model=final_source_model, + final_source_phase_id=final_source_phase_id, + follow_up_model=follow_up_model, + fusion_id=fusion_id, + input_tokens=input_tokens, + outcome=outcome, + output_tokens=output_tokens, + pattern=pattern, + phase_count=phase_count, + request_count=request_count, + synthetic_model=synthetic_model, + total_nano_aiu=total_nano_aiu, + turn_id=turn_id, + cache_write_tokens=cache_write_tokens, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["cachedTokens"] = to_int(self.cached_tokens) + result["commitId"] = from_str(self.commit_id) + result["degradedReason"] = from_union([from_none, from_str], self.degraded_reason) + result["durationMs"] = to_float(self.duration_ms) + result["finalSourceModel"] = from_union([from_none, from_str], self.final_source_model) + result["finalSourcePhaseId"] = from_union([from_none, from_str], self.final_source_phase_id) + result["followUpModel"] = from_str(self.follow_up_model) + result["fusionId"] = from_str(self.fusion_id) + result["inputTokens"] = to_int(self.input_tokens) + result["outcome"] = from_str(self.outcome) + result["outputTokens"] = to_int(self.output_tokens) + result["pattern"] = to_enum(FusionPattern, self.pattern) + result["phaseCount"] = to_int(self.phase_count) + result["requestCount"] = to_int(self.request_count) + result["syntheticModel"] = from_str(self.synthetic_model) + result["totalNanoAiu"] = to_float(self.total_nano_aiu) + result["turnId"] = from_str(self.turn_id) + if self.cache_write_tokens is not None: + result["cacheWriteTokens"] = from_union([from_none, to_int], self.cache_write_tokens) + return result + + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionFusionResolvedData: + "Experimental durable validated HydraFusion route and turn policy." + contract_version: int + fallback_model: str + follow_up_model: str + fusion_id: str + pattern: FusionPattern + policy: str + primary_model: str + secondary_model: str | None + synthetic_model: str + turn_id: str + follow_up: FusionFollowUpRecommendation | None = None + model_universe_version: str | None = None + # Experimental: this field is part of an experimental API and may change or be removed. + phase_plan: list[FusionPhasePlanStep] | None = None + plan_version: str | None = None + policy_version: str | None = None + route_source: str | None = None + routing_latency_ms: float | None = None + rule_id: str | None = None + rule_index: int | None = None + rule_name: str | None = None + scores: FusionScores | None = None + + @staticmethod + def from_dict(obj: Any) -> "SessionFusionResolvedData": + assert isinstance(obj, dict) + contract_version = from_int(obj.get("contractVersion")) + fallback_model = from_str(obj.get("fallbackModel")) + follow_up_model = from_str(obj.get("followUpModel")) + fusion_id = from_str(obj.get("fusionId")) + pattern = parse_enum(FusionPattern, obj.get("pattern")) + policy = from_str(obj.get("policy")) + primary_model = from_str(obj.get("primaryModel")) + secondary_model = from_union([from_none, from_str], obj.get("secondaryModel")) + synthetic_model = from_str(obj.get("syntheticModel")) + turn_id = from_str(obj.get("turnId")) + follow_up = from_union([from_none, FusionFollowUpRecommendation.from_dict], obj.get("followUp")) + model_universe_version = from_union([from_none, from_str], obj.get("modelUniverseVersion")) + phase_plan = from_union([from_none, lambda x: from_list(FusionPhasePlanStep.from_dict, x)], obj.get("phasePlan")) + plan_version = from_union([from_none, from_str], obj.get("planVersion")) + policy_version = from_union([from_none, from_str], obj.get("policyVersion")) + route_source = from_union([from_none, from_str], obj.get("routeSource")) + routing_latency_ms = from_union([from_none, from_float], obj.get("routingLatencyMs")) + rule_id = from_union([from_none, from_str], obj.get("ruleId")) + rule_index = from_union([from_none, from_int], obj.get("ruleIndex")) + rule_name = from_union([from_none, from_str], obj.get("ruleName")) + scores = from_union([from_none, FusionScores.from_dict], obj.get("scores")) + return SessionFusionResolvedData( + contract_version=contract_version, + fallback_model=fallback_model, + follow_up_model=follow_up_model, + fusion_id=fusion_id, + pattern=pattern, + policy=policy, + primary_model=primary_model, + secondary_model=secondary_model, + synthetic_model=synthetic_model, + turn_id=turn_id, + follow_up=follow_up, + model_universe_version=model_universe_version, + phase_plan=phase_plan, + plan_version=plan_version, + policy_version=policy_version, + route_source=route_source, + routing_latency_ms=routing_latency_ms, + rule_id=rule_id, + rule_index=rule_index, + rule_name=rule_name, + scores=scores, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["contractVersion"] = to_int(self.contract_version) + result["fallbackModel"] = from_str(self.fallback_model) + result["followUpModel"] = from_str(self.follow_up_model) + result["fusionId"] = from_str(self.fusion_id) + result["pattern"] = to_enum(FusionPattern, self.pattern) + result["policy"] = from_str(self.policy) + result["primaryModel"] = from_str(self.primary_model) + result["secondaryModel"] = from_union([from_none, from_str], self.secondary_model) + result["syntheticModel"] = from_str(self.synthetic_model) + result["turnId"] = from_str(self.turn_id) + if self.follow_up is not None: + result["followUp"] = from_union([from_none, lambda x: to_class(FusionFollowUpRecommendation, x)], self.follow_up) + if self.model_universe_version is not None: + result["modelUniverseVersion"] = from_union([from_none, from_str], self.model_universe_version) + if self.phase_plan is not None: + result["phasePlan"] = from_union([from_none, lambda x: from_list(lambda x: to_class(FusionPhasePlanStep, x), x)], self.phase_plan) + if self.plan_version is not None: + result["planVersion"] = from_union([from_none, from_str], self.plan_version) + if self.policy_version is not None: + result["policyVersion"] = from_union([from_none, from_str], self.policy_version) + if self.route_source is not None: + result["routeSource"] = from_union([from_none, from_str], self.route_source) + if self.routing_latency_ms is not None: + result["routingLatencyMs"] = from_union([from_none, to_float], self.routing_latency_ms) + if self.rule_id is not None: + result["ruleId"] = from_union([from_none, from_str], self.rule_id) + if self.rule_index is not None: + result["ruleIndex"] = from_union([from_none, to_int], self.rule_index) + if self.rule_name is not None: + result["ruleName"] = from_union([from_none, from_str], self.rule_name) + if self.scores is not None: + result["scores"] = from_union([from_none, lambda x: to_class(FusionScores, x)], self.scores) + return result + + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionFusionRouteFailedData: + "Experimental durable HydraFusion routing failure and the deterministic concrete fallback selected for the turn." + attempt_id: str + fallback_model: str + policy: str + reason: str + synthetic_model: str + error_message: str | None = None + routing_latency_ms: float | None = None + + @staticmethod + def from_dict(obj: Any) -> "SessionFusionRouteFailedData": assert isinstance(obj, dict) - canvas_id = from_str(obj.get("canvasId")) - extension_id = from_str(obj.get("extensionId")) - instance_id = from_str(obj.get("instanceId")) - return SessionCanvasRemovedData( - canvas_id=canvas_id, - extension_id=extension_id, - instance_id=instance_id, + attempt_id = from_str(obj.get("attemptId")) + fallback_model = from_str(obj.get("fallbackModel")) + policy = from_str(obj.get("policy")) + reason = from_str(obj.get("reason")) + synthetic_model = from_str(obj.get("syntheticModel")) + error_message = from_union([from_none, from_str], obj.get("errorMessage")) + routing_latency_ms = from_union([from_none, from_float], obj.get("routingLatencyMs")) + return SessionFusionRouteFailedData( + attempt_id=attempt_id, + fallback_model=fallback_model, + policy=policy, + reason=reason, + synthetic_model=synthetic_model, + error_message=error_message, + routing_latency_ms=routing_latency_ms, ) def to_dict(self) -> dict: result: dict = {} - result["canvasId"] = from_str(self.canvas_id) - result["extensionId"] = from_str(self.extension_id) - result["instanceId"] = from_str(self.instance_id) + result["attemptId"] = from_str(self.attempt_id) + result["fallbackModel"] = from_str(self.fallback_model) + result["policy"] = from_str(self.policy) + result["reason"] = from_str(self.reason) + result["syntheticModel"] = from_str(self.synthetic_model) + if self.error_message is not None: + result["errorMessage"] = from_union([from_none, from_str], self.error_message) + if self.routing_latency_ms is not None: + result["routingLatencyMs"] = from_union([from_none, to_float], self.routing_latency_ms) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SessionCanvasUnavailableData: - "Transient signal that an open canvas instance's provider has dropped (for example the extension is reloading mid-session). The host should keep the panel mounted and surface a reconnecting affordance rather than tearing it down; a subsequent `session.canvas.opened` for the same instanceId clears the affordance once the provider reconnects with a fresh url. Ephemeral and never persisted, so it is never replayed on cold resume." - canvas_id: str - extension_id: str - instance_id: str +class SessionFusionRouteStartedData: + "Experimental transient signal that HydraFusion routing has started for an eligible turn." + attempt_id: str + turn_kind: FusionTurnKind + policy: str | None = None + synthetic_model: str | None = None @staticmethod - def from_dict(obj: Any) -> "SessionCanvasUnavailableData": + def from_dict(obj: Any) -> "SessionFusionRouteStartedData": assert isinstance(obj, dict) - canvas_id = from_str(obj.get("canvasId")) - extension_id = from_str(obj.get("extensionId")) - instance_id = from_str(obj.get("instanceId")) - return SessionCanvasUnavailableData( - canvas_id=canvas_id, - extension_id=extension_id, - instance_id=instance_id, + attempt_id = from_str(obj.get("attemptId")) + turn_kind = parse_enum(FusionTurnKind, obj.get("turnKind")) + policy = from_union([from_none, from_str], obj.get("policy")) + synthetic_model = from_union([from_none, from_str], obj.get("syntheticModel")) + return SessionFusionRouteStartedData( + attempt_id=attempt_id, + turn_kind=turn_kind, + policy=policy, + synthetic_model=synthetic_model, ) def to_dict(self) -> dict: result: dict = {} - result["canvasId"] = from_str(self.canvas_id) - result["extensionId"] = from_str(self.extension_id) - result["instanceId"] = from_str(self.instance_id) + result["attemptId"] = from_str(self.attempt_id) + result["turnKind"] = to_enum(FusionTurnKind, self.turn_kind) + if self.policy is not None: + result["policy"] = from_union([from_none, from_str], self.policy) + if self.synthetic_model is not None: + result["syntheticModel"] = from_union([from_none, from_str], self.synthetic_model) return result @@ -1208,14 +2138,17 @@ 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, then the policy helper, per ordinary key, while permissions compose restrictively across device, server, policy-helper, 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 + policy_helper_managed: bool | None = None + sandbox_enabled_by_undetermined_policy: bool | None = None settings: Any = None @staticmethod @@ -1227,7 +2160,10 @@ 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")) + policy_helper_managed = from_union([from_none, from_bool], obj.get("policyHelperManaged")) + sandbox_enabled_by_undetermined_policy = from_union([from_none, from_bool], obj.get("sandboxEnabledByUndeterminedPolicy")) settings = obj.get("settings") return SessionManagedSettingsResolvedData( bypass_permissions_disabled=bypass_permissions_disabled, @@ -1236,7 +2172,10 @@ 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, + policy_helper_managed=policy_helper_managed, + sandbox_enabled_by_undetermined_policy=sandbox_enabled_by_undetermined_policy, settings=settings, ) @@ -1248,13 +2187,90 @@ 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.policy_helper_managed is not None: + result["policyHelperManaged"] = from_union([from_none, from_bool], self.policy_helper_managed) + if self.sandbox_enabled_by_undetermined_policy is not None: + result["sandboxEnabledByUndeterminedPolicy"] = from_union([from_none, from_bool], self.sandbox_enabled_by_undetermined_policy) if self.settings is not None: result["settings"] = self.settings return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionPermissionsChangedData: + "Permission-mode transition details." + # Experimental: this field is part of an experimental API and may change or be removed. + mode: PermissionMode + # Experimental: this field is part of an experimental API and may change or be removed. + previous_mode: PermissionMode + # Experimental: this field is part of an experimental API and may change or be removed. + assisted_approval_model: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "SessionPermissionsChangedData": + assert isinstance(obj, dict) + mode = parse_enum(PermissionMode, obj.get("mode")) + previous_mode = parse_enum(PermissionMode, obj.get("previousMode")) + assisted_approval_model = from_union([from_none, from_str], obj.get("assistedApprovalModel")) + return SessionPermissionsChangedData( + mode=mode, + previous_mode=previous_mode, + assisted_approval_model=assisted_approval_model, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["mode"] = to_enum(PermissionMode, self.mode) + result["previousMode"] = to_enum(PermissionMode, self.previous_mode) + if self.assisted_approval_model is not None: + result["assistedApprovalModel"] = from_union([from_none, from_str], self.assisted_approval_model) + return result + + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class UiEphemeralQueryData: + "Ordered output and terminal state for a transient query that does not modify conversation history." + phase: UIEphemeralQueryPhase + request_id: str + answer: str | None = None + chunk: str | None = None + error: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "UiEphemeralQueryData": + assert isinstance(obj, dict) + phase = parse_enum(UIEphemeralQueryPhase, obj.get("phase")) + request_id = from_str(obj.get("requestId")) + answer = from_union([from_none, from_str], obj.get("answer")) + chunk = from_union([from_none, from_str], obj.get("chunk")) + error = from_union([from_none, from_str], obj.get("error")) + return UiEphemeralQueryData( + phase=phase, + request_id=request_id, + answer=answer, + chunk=chunk, + error=error, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["phase"] = to_enum(UIEphemeralQueryPhase, self.phase) + result["requestId"] = from_str(self.request_id) + if self.answer is not None: + result["answer"] = from_union([from_none, from_str], self.answer) + if self.chunk is not None: + result["chunk"] = from_union([from_none, from_str], self.chunk) + if self.error is not None: + result["error"] = from_union([from_none, from_str], self.error) + return result + + @dataclass class AbortData: "Turn abort information including the reason for termination" @@ -1274,6 +2290,83 @@ def to_dict(self) -> dict: return result +@dataclass +class AgentInterruptedData: + "Metadata for work the user interrupted while the agent was running" + activity: AgentInterruptedActivity + elapsed: timedelta + turn: int + api_endpoint: str | None = None + cancel_phase: AgentInterruptedCancelPhase | None = None + interrupted_agent_count: int | None = None + model: str | None = None + output_ttft: timedelta | None = None + reasoning_effort: str | None = None + safe_tool_names: list[str] | None = None + tool_call_ids: list[str] | None = None + tool_names: list[str] | None = None + transport: ModelCallFailureTransport | None = None + + @staticmethod + def from_dict(obj: Any) -> "AgentInterruptedData": + assert isinstance(obj, dict) + activity = parse_enum(AgentInterruptedActivity, obj.get("activity")) + elapsed = from_timedelta(obj.get("elapsedMs")) + turn = from_int(obj.get("turn")) + api_endpoint = from_union([from_none, from_str], obj.get("apiEndpoint")) + cancel_phase = from_union([from_none, lambda x: parse_enum(AgentInterruptedCancelPhase, x)], obj.get("cancelPhase")) + interrupted_agent_count = from_union([from_none, from_int], obj.get("interruptedAgentCount")) + model = from_union([from_none, from_str], obj.get("model")) + output_ttft = from_union([from_none, from_timedelta], obj.get("outputTtftMs")) + reasoning_effort = from_union([from_none, from_str], obj.get("reasoningEffort")) + safe_tool_names = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("safeToolNames")) + tool_call_ids = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("toolCallIds")) + tool_names = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("toolNames")) + transport = from_union([from_none, lambda x: parse_enum(ModelCallFailureTransport, x)], obj.get("transport")) + return AgentInterruptedData( + activity=activity, + elapsed=elapsed, + turn=turn, + api_endpoint=api_endpoint, + cancel_phase=cancel_phase, + interrupted_agent_count=interrupted_agent_count, + model=model, + output_ttft=output_ttft, + reasoning_effort=reasoning_effort, + safe_tool_names=safe_tool_names, + tool_call_ids=tool_call_ids, + tool_names=tool_names, + transport=transport, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["activity"] = to_enum(AgentInterruptedActivity, self.activity) + result["elapsedMs"] = to_timedelta(self.elapsed) + result["turn"] = to_int(self.turn) + if self.api_endpoint is not None: + result["apiEndpoint"] = from_union([from_none, from_str], self.api_endpoint) + if self.cancel_phase is not None: + result["cancelPhase"] = from_union([from_none, lambda x: to_enum(AgentInterruptedCancelPhase, x)], self.cancel_phase) + if self.interrupted_agent_count is not None: + result["interruptedAgentCount"] = from_union([from_none, to_int], self.interrupted_agent_count) + if self.model is not None: + result["model"] = from_union([from_none, from_str], self.model) + if self.output_ttft is not None: + result["outputTtftMs"] = from_union([from_none, to_timedelta], self.output_ttft) + if self.reasoning_effort is not None: + result["reasoningEffort"] = from_union([from_none, from_str], self.reasoning_effort) + if self.safe_tool_names is not None: + result["safeToolNames"] = from_union([from_none, lambda x: from_list(from_str, x)], self.safe_tool_names) + if self.tool_call_ids is not None: + result["toolCallIds"] = from_union([from_none, lambda x: from_list(from_str, x)], self.tool_call_ids) + if self.tool_names is not None: + result["toolNames"] = from_union([from_none, lambda x: from_list(from_str, x)], self.tool_names) + if self.transport is not None: + result["transport"] = from_union([from_none, lambda x: to_enum(ModelCallFailureTransport, x)], self.transport) + return result + + @dataclass class AssistantIdleData: "Payload emitted whenever the main agent's processing loop goes idle, including while related background work (running agents or in-flight attached shell commands) is still pending and the session-level idle event is therefore deferred" @@ -1325,12 +2418,15 @@ class AssistantMessageData: citations: Citations | None = None client_request_id: str | None = None encrypted_content: str | None = None + # Experimental: this field is part of an experimental API and may change or be removed. + fusion: FusionAttribution | None = None interaction_id: str | None = None model: str | None = None output_tokens: int | None = None # Deprecated: this field is deprecated. parent_tool_call_id: str | None = None phase: str | None = None + reasoning_blocks: AssistantMessageReasoningBlocks | None = None reasoning_opaque: str | None = None reasoning_text: str | None = None reasoning_wire_field: str | None = None @@ -1352,11 +2448,13 @@ def from_dict(obj: Any) -> "AssistantMessageData": 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")) + fusion = from_union([from_none, FusionAttribution.from_dict], obj.get("fusion")) interaction_id = from_union([from_none, from_str], obj.get("interactionId")) model = from_union([from_none, from_str], obj.get("model")) output_tokens = from_union([from_none, from_int], obj.get("outputTokens")) parent_tool_call_id = from_union([from_none, from_str], obj.get("parentToolCallId")) phase = from_union([from_none, from_str], obj.get("phase")) + reasoning_blocks = from_union([from_none, AssistantMessageReasoningBlocks.from_dict], obj.get("reasoningBlocks")) reasoning_opaque = from_union([from_none, from_str], obj.get("reasoningOpaque")) reasoning_text = from_union([from_none, from_str], obj.get("reasoningText")) reasoning_wire_field = from_union([from_none, from_str], obj.get("reasoningWireField")) @@ -1375,11 +2473,13 @@ def from_dict(obj: Any) -> "AssistantMessageData": citations=citations, client_request_id=client_request_id, encrypted_content=encrypted_content, + fusion=fusion, interaction_id=interaction_id, model=model, output_tokens=output_tokens, parent_tool_call_id=parent_tool_call_id, phase=phase, + reasoning_blocks=reasoning_blocks, reasoning_opaque=reasoning_opaque, reasoning_text=reasoning_text, reasoning_wire_field=reasoning_wire_field, @@ -1407,6 +2507,8 @@ def to_dict(self) -> dict: result["clientRequestId"] = from_union([from_none, from_str], self.client_request_id) if self.encrypted_content is not None: result["encryptedContent"] = from_union([from_none, from_str], self.encrypted_content) + if self.fusion is not None: + result["fusion"] = from_union([from_none, lambda x: to_class(FusionAttribution, x)], self.fusion) if self.interaction_id is not None: result["interactionId"] = from_union([from_none, from_str], self.interaction_id) if self.model is not None: @@ -1417,6 +2519,8 @@ def to_dict(self) -> dict: result["parentToolCallId"] = from_union([from_none, from_str], self.parent_tool_call_id) if self.phase is not None: result["phase"] = from_union([from_none, from_str], self.phase) + if self.reasoning_blocks is not None: + result["reasoningBlocks"] = from_union([from_none, lambda x: to_class(AssistantMessageReasoningBlocks, x)], self.reasoning_blocks) if self.reasoning_opaque is not None: result["reasoningOpaque"] = from_union([from_none, from_str], self.reasoning_opaque) if self.reasoning_text is not None: @@ -1497,6 +2601,7 @@ class AssistantMessageToolRequest: name: str tool_call_id: str arguments: Any = None + caller: AssistantMessageToolRequestCaller | None = None intention_summary: str | None = None mcp_server_name: str | None = None mcp_tool_name: str | None = None @@ -1509,6 +2614,7 @@ def from_dict(obj: Any) -> "AssistantMessageToolRequest": name = from_str(obj.get("name")) tool_call_id = from_str(obj.get("toolCallId")) arguments = obj.get("arguments") + caller = from_union([from_none, AssistantMessageToolRequestCaller.from_dict], obj.get("caller")) intention_summary = from_union([from_none, from_str], obj.get("intentionSummary")) mcp_server_name = from_union([from_none, from_str], obj.get("mcpServerName")) mcp_tool_name = from_union([from_none, from_str], obj.get("mcpToolName")) @@ -1518,6 +2624,7 @@ def from_dict(obj: Any) -> "AssistantMessageToolRequest": name=name, tool_call_id=tool_call_id, arguments=arguments, + caller=caller, intention_summary=intention_summary, mcp_server_name=mcp_server_name, mcp_tool_name=mcp_tool_name, @@ -1531,6 +2638,8 @@ def to_dict(self) -> dict: result["toolCallId"] = from_str(self.tool_call_id) if self.arguments is not None: result["arguments"] = self.arguments + if self.caller is not None: + result["caller"] = from_union([from_none, lambda x: to_class(AssistantMessageToolRequestCaller, x)], self.caller) if self.intention_summary is not None: result["intentionSummary"] = from_union([from_none, from_str], self.intention_summary) if self.mcp_server_name is not None: @@ -1544,6 +2653,29 @@ def to_dict(self) -> dict: return result +@dataclass +class AssistantMessageToolRequestCaller: + "Hosted program that requested this client tool call" + caller_id: str + type: AssistantMessageToolRequestCallerType + + @staticmethod + def from_dict(obj: Any) -> "AssistantMessageToolRequestCaller": + assert isinstance(obj, dict) + caller_id = from_str(obj.get("callerId")) + type = parse_enum(AssistantMessageToolRequestCallerType, obj.get("type")) + return AssistantMessageToolRequestCaller( + caller_id=caller_id, + type=type, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["callerId"] = from_str(self.caller_id) + result["type"] = to_enum(AssistantMessageToolRequestCallerType, self.type) + return result + + @dataclass class AssistantReasoningData: "Assistant reasoning content for timeline display with complete thinking text" @@ -1816,12 +2948,17 @@ def to_dict(self) -> dict: class AssistantUsageData: "LLM API call usage metrics including tokens, costs, quotas, and billing information" model: str + accepted_prediction_tokens: int | None = None 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 + # Internal: this field is an internal SDK API and is not part of the public surface. + _cache_details_reported: bool | None = None cache_expires_at: datetime | None = None cache_read_tokens: int | None = None + # Internal: this field is an internal SDK API and is not part of the public surface. + _cache_ttl_seconds: int | None = None cache_write_tokens: int | None = None content_filter_triggered: bool | None = None copilot_usage: AssistantUsageCopilotUsage | None = None @@ -1829,20 +2966,31 @@ class AssistantUsageData: cost: float | None = None duration: timedelta | None = None finish_reason: str | None = None + # Internal: this field is an internal SDK API and is not part of the public surface. + _frontier_source: str | None = None + # Experimental: this field is part of an experimental API and may change or be removed. + fusion: FusionAttribution | None = None initiator: str | None = None input_tokens: int | None = None interaction_type: str | None = None inter_token_latency: timedelta | None = None + is_auto: bool | None = None + is_byok: bool | None = None + max_output_tokens: int | None = None + max_prompt_tokens: int | 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 + output_ttft: timedelta | None = None # Deprecated: this field is deprecated. parent_tool_call_id: str | None = None provider_call_id: str | None = None # Internal: this field is an internal SDK API and is not part of the public surface. _quota_snapshots: dict[str, _AssistantUsageQuotaSnapshot] | None = None reasoning_effort: str | None = None + reasoning_summary: ReasoningSummary | None = None reasoning_tokens: int | None = None + rejected_prediction_tokens: int | None = None rte: bool | None = None service_request_id: str | None = None time_to_first_token: timedelta | None = None @@ -1850,82 +2998,115 @@ class AssistantUsageData: _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 + transport: AssistantUsageTransport | None = None @staticmethod def from_dict(obj: Any) -> "AssistantUsageData": assert isinstance(obj, dict) model = from_str(obj.get("model")) + accepted_prediction_tokens = from_union([from_none, from_int], obj.get("acceptedPredictionTokens")) 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_details_reported = from_union([from_none, from_bool], obj.get("cacheDetailsReported")) 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_ttl_seconds = from_union([from_none, from_int], obj.get("cacheTtlSeconds")) cache_write_tokens = from_union([from_none, from_int], obj.get("cacheWriteTokens")) content_filter_triggered = from_union([from_none, from_bool], obj.get("contentFilterTriggered")) copilot_usage = from_union([from_none, AssistantUsageCopilotUsage.from_dict], obj.get("copilotUsage")) cost = from_union([from_none, from_float], obj.get("cost")) duration = from_union([from_none, from_timedelta], obj.get("duration")) finish_reason = from_union([from_none, from_str], obj.get("finishReason")) + _frontier_source = from_union([from_none, from_str], obj.get("frontierSource")) + fusion = from_union([from_none, FusionAttribution.from_dict], obj.get("fusion")) 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")) + is_auto = from_union([from_none, from_bool], obj.get("isAuto")) + is_byok = from_union([from_none, from_bool], obj.get("isByok")) + max_output_tokens = from_union([from_none, from_int], obj.get("maxOutputTokens")) + max_prompt_tokens = from_union([from_none, from_int], obj.get("maxPromptTokens")) _num_tool_calls = from_union([from_none, from_int], obj.get("numToolCalls")) output_tokens = from_union([from_none, from_int], obj.get("outputTokens")) + output_ttft = from_union([from_none, from_timedelta], obj.get("outputTtftMs")) 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_summary = from_union([from_none, lambda x: parse_enum(ReasoningSummary, x)], obj.get("reasoningSummary")) reasoning_tokens = from_union([from_none, from_int], obj.get("reasoningTokens")) + rejected_prediction_tokens = from_union([from_none, from_int], obj.get("rejectedPredictionTokens")) 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")) + transport = from_union([from_none, lambda x: parse_enum(AssistantUsageTransport, x)], obj.get("transport")) return AssistantUsageData( model=model, + accepted_prediction_tokens=accepted_prediction_tokens, api_call_id=api_call_id, api_endpoint=api_endpoint, _available_tool_count=_available_tool_count, + _cache_details_reported=_cache_details_reported, cache_expires_at=cache_expires_at, cache_read_tokens=cache_read_tokens, + _cache_ttl_seconds=_cache_ttl_seconds, cache_write_tokens=cache_write_tokens, content_filter_triggered=content_filter_triggered, copilot_usage=copilot_usage, cost=cost, duration=duration, finish_reason=finish_reason, + _frontier_source=_frontier_source, + fusion=fusion, initiator=initiator, input_tokens=input_tokens, interaction_type=interaction_type, inter_token_latency=inter_token_latency, + is_auto=is_auto, + is_byok=is_byok, + max_output_tokens=max_output_tokens, + max_prompt_tokens=max_prompt_tokens, _num_tool_calls=_num_tool_calls, output_tokens=output_tokens, + output_ttft=output_ttft, parent_tool_call_id=parent_tool_call_id, provider_call_id=provider_call_id, _quota_snapshots=_quota_snapshots, reasoning_effort=reasoning_effort, + reasoning_summary=reasoning_summary, reasoning_tokens=reasoning_tokens, + rejected_prediction_tokens=rejected_prediction_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, + transport=transport, ) def to_dict(self) -> dict: result: dict = {} result["model"] = from_str(self.model) + if self.accepted_prediction_tokens is not None: + result["acceptedPredictionTokens"] = from_union([from_none, to_int], self.accepted_prediction_tokens) if self.api_call_id is not None: 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_details_reported is not None: + result["cacheDetailsReported"] = from_union([from_none, from_bool], self._cache_details_reported) 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: result["cacheReadTokens"] = from_union([from_none, to_int], self.cache_read_tokens) + if self._cache_ttl_seconds is not None: + result["cacheTtlSeconds"] = from_union([from_none, to_int], self._cache_ttl_seconds) if self.cache_write_tokens is not None: result["cacheWriteTokens"] = from_union([from_none, to_int], self.cache_write_tokens) if self.content_filter_triggered is not None: @@ -1938,6 +3119,10 @@ def to_dict(self) -> dict: result["duration"] = from_union([from_none, to_timedelta_int], self.duration) if self.finish_reason is not None: result["finishReason"] = from_union([from_none, from_str], self.finish_reason) + if self._frontier_source is not None: + result["frontierSource"] = from_union([from_none, from_str], self._frontier_source) + if self.fusion is not None: + result["fusion"] = from_union([from_none, lambda x: to_class(FusionAttribution, x)], self.fusion) if self.initiator is not None: result["initiator"] = from_union([from_none, from_str], self.initiator) if self.input_tokens is not None: @@ -1946,10 +3131,20 @@ def to_dict(self) -> dict: 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.is_auto is not None: + result["isAuto"] = from_union([from_none, from_bool], self.is_auto) + if self.is_byok is not None: + result["isByok"] = from_union([from_none, from_bool], self.is_byok) + if self.max_output_tokens is not None: + result["maxOutputTokens"] = from_union([from_none, to_int], self.max_output_tokens) + if self.max_prompt_tokens is not None: + result["maxPromptTokens"] = from_union([from_none, to_int], self.max_prompt_tokens) 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.output_ttft is not None: + result["outputTtftMs"] = from_union([from_none, to_timedelta], self.output_ttft) if self.parent_tool_call_id is not None: result["parentToolCallId"] = from_union([from_none, from_str], self.parent_tool_call_id) if self.provider_call_id is not None: @@ -1958,8 +3153,12 @@ def to_dict(self) -> dict: result["quotaSnapshots"] = from_union([from_none, lambda x: from_dict(lambda x: to_class(_AssistantUsageQuotaSnapshot, x), x)], self._quota_snapshots) 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: + result["reasoningSummary"] = from_union([from_none, lambda x: to_enum(ReasoningSummary, x)], self.reasoning_summary) if self.reasoning_tokens is not None: result["reasoningTokens"] = from_union([from_none, to_int], self.reasoning_tokens) + if self.rejected_prediction_tokens is not None: + result["rejectedPredictionTokens"] = from_union([from_none, to_int], self.rejected_prediction_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: @@ -1970,6 +3169,8 @@ def to_dict(self) -> dict: 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) + if self.transport is not None: + result["transport"] = from_union([from_none, lambda x: to_enum(AssistantUsageTransport, x)], self.transport) return result @@ -3053,9 +4254,65 @@ def to_dict(self) -> dict: return result +@dataclass +class CompletionReceiptEventRange: + "Inclusive durable event range summarized by a completion receipt." + end_event_id: str + start_event_id: str + + @staticmethod + def from_dict(obj: Any) -> "CompletionReceiptEventRange": + assert isinstance(obj, dict) + end_event_id = from_str(obj.get("endEventId")) + start_event_id = from_str(obj.get("startEventId")) + return CompletionReceiptEventRange( + end_event_id=end_event_id, + start_event_id=start_event_id, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["endEventId"] = from_str(self.end_event_id) + result["startEventId"] = from_str(self.start_event_id) + return result + + +@dataclass +class CompletionReceiptFinalTool: + "Final structured tool completion in the covered event range." + status: CompletionReceiptToolStatus + tool_call_id: str + exit_code: int | None = None + tool_name: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "CompletionReceiptFinalTool": + assert isinstance(obj, dict) + status = parse_enum(CompletionReceiptToolStatus, obj.get("status")) + tool_call_id = from_str(obj.get("toolCallId")) + exit_code = from_union([from_none, from_int], obj.get("exitCode")) + tool_name = from_union([from_none, from_str], obj.get("toolName")) + return CompletionReceiptFinalTool( + status=status, + tool_call_id=tool_call_id, + exit_code=exit_code, + tool_name=tool_name, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["status"] = to_enum(CompletionReceiptToolStatus, self.status) + result["toolCallId"] = from_str(self.tool_call_id) + if self.exit_code is not None: + result["exitCode"] = from_union([from_none, to_int], self.exit_code) + if self.tool_name is not None: + result["toolName"] = from_union([from_none, from_str], self.tool_name) + return result + + @dataclass class CustomAgentsUpdatedAgent: - "A single loaded custom agent in `session.custom_agents_updated`, with identity, source, tools, invocability, and model override." + "A single loaded custom agent in `session.custom_agents_updated`, with identity, source, tools, invocability, and authored model configuration." description: str display_name: str id: str @@ -3064,6 +4321,8 @@ class CustomAgentsUpdatedAgent: tools: list[str] | None user_invocable: bool model: str | None = None + model_policy: AgentModelPolicy | None = None + models: list[str] | None = None @staticmethod def from_dict(obj: Any) -> "CustomAgentsUpdatedAgent": @@ -3076,6 +4335,8 @@ def from_dict(obj: Any) -> "CustomAgentsUpdatedAgent": tools = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("tools")) user_invocable = from_bool(obj.get("userInvocable")) model = from_union([from_none, from_str], obj.get("model")) + model_policy = from_union([from_none, lambda x: parse_enum(AgentModelPolicy, x)], obj.get("modelPolicy")) + models = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("models")) return CustomAgentsUpdatedAgent( description=description, display_name=display_name, @@ -3085,6 +4346,8 @@ def from_dict(obj: Any) -> "CustomAgentsUpdatedAgent": tools=tools, user_invocable=user_invocable, model=model, + model_policy=model_policy, + models=models, ) def to_dict(self) -> dict: @@ -3098,6 +4361,10 @@ def to_dict(self) -> dict: result["userInvocable"] = from_bool(self.user_invocable) if self.model is not None: result["model"] = from_union([from_none, from_str], self.model) + if self.model_policy is not None: + result["modelPolicy"] = from_union([from_none, lambda x: to_enum(AgentModelPolicy, x)], self.model_policy) + if self.models is not None: + result["models"] = from_union([from_none, lambda x: from_list(from_str, x)], self.models) return result @@ -3309,6 +4576,7 @@ class ExitPlanModeRequestedData: recommended_action: ExitPlanModeAction request_id: str summary: str + model: str | None = None @staticmethod def from_dict(obj: Any) -> "ExitPlanModeRequestedData": @@ -3318,12 +4586,14 @@ def from_dict(obj: Any) -> "ExitPlanModeRequestedData": recommended_action = parse_enum(ExitPlanModeAction, obj.get("recommendedAction")) request_id = from_str(obj.get("requestId")) summary = from_str(obj.get("summary")) + model = from_union([from_none, from_str], obj.get("model")) return ExitPlanModeRequestedData( actions=actions, plan_content=plan_content, recommended_action=recommended_action, request_id=request_id, summary=summary, + model=model, ) def to_dict(self) -> dict: @@ -3333,6 +4603,8 @@ def to_dict(self) -> dict: result["recommendedAction"] = to_enum(ExitPlanModeAction, self.recommended_action) result["requestId"] = from_str(self.request_id) result["summary"] = from_str(self.summary) + if self.model is not None: + result["model"] = from_union([from_none, from_str], self.model) return result @@ -3394,6 +4666,7 @@ class ExternalToolRequestedData: tool_call_id: str tool_name: str arguments: Any = None + provider_id: str | None = None traceparent: str | None = None tracestate: str | None = None working_directory: str | None = None @@ -3406,6 +4679,7 @@ def from_dict(obj: Any) -> "ExternalToolRequestedData": tool_call_id = from_str(obj.get("toolCallId")) tool_name = from_str(obj.get("toolName")) arguments = obj.get("arguments") + provider_id = from_union([from_none, from_str], obj.get("providerId")) traceparent = from_union([from_none, from_str], obj.get("traceparent")) tracestate = from_union([from_none, from_str], obj.get("tracestate")) working_directory = from_union([from_none, from_str], obj.get("workingDirectory")) @@ -3415,6 +4689,7 @@ def from_dict(obj: Any) -> "ExternalToolRequestedData": tool_call_id=tool_call_id, tool_name=tool_name, arguments=arguments, + provider_id=provider_id, traceparent=traceparent, tracestate=tracestate, working_directory=working_directory, @@ -3428,6 +4703,8 @@ def to_dict(self) -> dict: result["toolName"] = from_str(self.tool_name) if self.arguments is not None: result["arguments"] = self.arguments + if self.provider_id is not None: + result["providerId"] = from_union([from_none, from_str], self.provider_id) if self.traceparent is not None: result["traceparent"] = from_union([from_none, from_str], self.traceparent) if self.tracestate is not None: @@ -3583,6 +4860,7 @@ class HookEndData: success: bool error: HookEndError | None = None output: Any = None + parent_tool_call_id: str | None = None @staticmethod def from_dict(obj: Any) -> "HookEndData": @@ -3592,12 +4870,14 @@ def from_dict(obj: Any) -> "HookEndData": success = from_bool(obj.get("success")) error = from_union([from_none, HookEndError.from_dict], obj.get("error")) output = obj.get("output") + parent_tool_call_id = from_union([from_none, from_str], obj.get("parentToolCallId")) return HookEndData( hook_invocation_id=hook_invocation_id, hook_type=hook_type, success=success, error=error, output=output, + parent_tool_call_id=parent_tool_call_id, ) def to_dict(self) -> dict: @@ -3609,6 +4889,8 @@ def to_dict(self) -> dict: result["error"] = from_union([from_none, lambda x: to_class(HookEndError, x)], self.error) if self.output is not None: result["output"] = self.output + if self.parent_tool_call_id is not None: + result["parentToolCallId"] = from_union([from_none, from_str], self.parent_tool_call_id) return result @@ -3671,6 +4953,7 @@ class HookStartData: hook_invocation_id: str hook_type: str input: Any = None + parent_tool_call_id: str | None = None @staticmethod def from_dict(obj: Any) -> "HookStartData": @@ -3678,10 +4961,12 @@ def from_dict(obj: Any) -> "HookStartData": hook_invocation_id = from_str(obj.get("hookInvocationId")) hook_type = from_str(obj.get("hookType")) input = obj.get("input") + parent_tool_call_id = from_union([from_none, from_str], obj.get("parentToolCallId")) return HookStartData( hook_invocation_id=hook_invocation_id, hook_type=hook_type, input=input, + parent_tool_call_id=parent_tool_call_id, ) def to_dict(self) -> dict: @@ -3690,6 +4975,8 @@ def to_dict(self) -> dict: result["hookType"] = from_str(self.hook_type) if self.input is not None: result["input"] = self.input + if self.parent_tool_call_id is not None: + result["parentToolCallId"] = from_union([from_none, from_str], self.parent_tool_call_id) return result @@ -4145,7 +5432,10 @@ class ModelCallFailureData: error_message: str | None = None error_type: str | None = None failure_kind: ModelCallFailureKind | None = None + # Experimental: this field is part of an experimental API and may change or be removed. + fusion: FusionAttribution | None = None initiator: str | None = None + interaction_type: str | None = None is_auto: bool | None = None is_byok: bool | None = None max_output_tokens: int | None = None @@ -4173,7 +5463,9 @@ def from_dict(obj: Any) -> "ModelCallFailureData": error_message = from_union([from_none, from_str], obj.get("errorMessage")) error_type = from_union([from_none, from_str], obj.get("errorType")) failure_kind = from_union([from_none, lambda x: parse_enum(ModelCallFailureKind, x)], obj.get("failureKind")) + fusion = from_union([from_none, FusionAttribution.from_dict], obj.get("fusion")) initiator = from_union([from_none, from_str], obj.get("initiator")) + interaction_type = from_union([from_none, from_str], obj.get("interactionType")) is_auto = from_union([from_none, from_bool], obj.get("isAuto")) is_byok = from_union([from_none, from_bool], obj.get("isByok")) max_output_tokens = from_union([from_none, from_int], obj.get("maxOutputTokens")) @@ -4197,7 +5489,9 @@ def from_dict(obj: Any) -> "ModelCallFailureData": error_message=error_message, error_type=error_type, failure_kind=failure_kind, + fusion=fusion, initiator=initiator, + interaction_type=interaction_type, is_auto=is_auto, is_byok=is_byok, max_output_tokens=max_output_tokens, @@ -4232,8 +5526,12 @@ def to_dict(self) -> dict: result["errorType"] = from_union([from_none, from_str], self.error_type) if self.failure_kind is not None: result["failureKind"] = from_union([from_none, lambda x: to_enum(ModelCallFailureKind, x)], self.failure_kind) + if self.fusion is not None: + result["fusion"] = from_union([from_none, lambda x: to_class(FusionAttribution, x)], self.fusion) if self.initiator is not None: result["initiator"] = from_union([from_none, from_str], self.initiator) + if self.interaction_type is not None: + result["interactionType"] = from_union([from_none, from_str], self.interaction_type) if self.is_auto is not None: result["isAuto"] = from_union([from_none, from_bool], self.is_auto) if self.is_byok is not None: @@ -4307,10 +5605,53 @@ def to_dict(self) -> dict: return result +@dataclass +class ModelCallFinishedData: + "Final lifecycle outcome for one logical model dispatch. A logical dispatch may include internal reconnect or fallback work, so event count is not provider HTTP-request count." + dispatch_duration: timedelta + edit_classifier_version: int + outcome: ModelCallFinishedOutcome + turn_id: str + contains_built_in_file_edit_request: bool | None = None + interaction_id: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "ModelCallFinishedData": + assert isinstance(obj, dict) + dispatch_duration = from_timedelta(obj.get("dispatchDurationMs")) + edit_classifier_version = from_int(obj.get("editClassifierVersion")) + outcome = parse_enum(ModelCallFinishedOutcome, obj.get("outcome")) + turn_id = from_str(obj.get("turnId")) + contains_built_in_file_edit_request = from_union([from_none, from_bool], obj.get("containsBuiltInFileEditRequest")) + interaction_id = from_union([from_none, from_str], obj.get("interactionId")) + return ModelCallFinishedData( + dispatch_duration=dispatch_duration, + edit_classifier_version=edit_classifier_version, + outcome=outcome, + turn_id=turn_id, + contains_built_in_file_edit_request=contains_built_in_file_edit_request, + interaction_id=interaction_id, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["dispatchDurationMs"] = to_timedelta(self.dispatch_duration) + result["editClassifierVersion"] = to_int(self.edit_classifier_version) + result["outcome"] = to_enum(ModelCallFinishedOutcome, self.outcome) + result["turnId"] = from_str(self.turn_id) + if self.contains_built_in_file_edit_request is not None: + result["containsBuiltInFileEditRequest"] = from_union([from_none, from_bool], self.contains_built_in_file_edit_request) + if self.interaction_id is not None: + result["interactionId"] = from_union([from_none, from_str], self.interaction_id) + return result + + @dataclass class ModelCallStartData: "Model API dispatch metadata for internal telemetry" turn_id: str + # Experimental: this field is part of an experimental API and may change or be removed. + fusion: FusionAttribution | None = None 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 @@ -4319,10 +5660,12 @@ class ModelCallStartData: def from_dict(obj: Any) -> "ModelCallStartData": assert isinstance(obj, dict) turn_id = from_str(obj.get("turnId")) + fusion = from_union([from_none, FusionAttribution.from_dict], obj.get("fusion")) 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, + fusion=fusion, model=model, _previous_response_id=_previous_response_id, ) @@ -4330,6 +5673,8 @@ def from_dict(obj: Any) -> "ModelCallStartData": def to_dict(self) -> dict: result: dict = {} result["turnId"] = from_str(self.turn_id) + if self.fusion is not None: + result["fusion"] = from_union([from_none, lambda x: to_class(FusionAttribution, x)], self.fusion) if self.model is not None: result["model"] = from_union([from_none, from_str], self.model) if self._previous_response_id is not None: @@ -4353,16 +5698,21 @@ def to_dict(self) -> dict: class PermissionApproved: "Permission response variant indicating the request was approved without persisting an approval rule." kind: ClassVar[str] = "approved" + managed_approval_handled: bool | None = None @staticmethod def from_dict(obj: Any) -> "PermissionApproved": assert isinstance(obj, dict) + managed_approval_handled = from_union([from_none, from_bool], obj.get("managedApprovalHandled")) return PermissionApproved( + managed_approval_handled=managed_approval_handled, ) def to_dict(self) -> dict: result: dict = {} result["kind"] = self.kind + if self.managed_approval_handled is not None: + result["managedApprovalHandled"] = from_union([from_none, from_bool], self.managed_approval_handled) return result @@ -4372,15 +5722,18 @@ class PermissionApprovedForLocation: approval: UserToolSessionApproval kind: ClassVar[str] = "approved-for-location" location_key: str + managed_approval_handled: bool | None = None @staticmethod def from_dict(obj: Any) -> "PermissionApprovedForLocation": assert isinstance(obj, dict) approval = _load_UserToolSessionApproval(obj.get("approval")) location_key = from_str(obj.get("locationKey")) + managed_approval_handled = from_union([from_none, from_bool], obj.get("managedApprovalHandled")) return PermissionApprovedForLocation( approval=approval, location_key=location_key, + managed_approval_handled=managed_approval_handled, ) def to_dict(self) -> dict: @@ -4388,6 +5741,8 @@ def to_dict(self) -> dict: result["approval"] = self.approval.to_dict() result["kind"] = self.kind result["locationKey"] = from_str(self.location_key) + if self.managed_approval_handled is not None: + result["managedApprovalHandled"] = from_union([from_none, from_bool], self.managed_approval_handled) return result @@ -4396,19 +5751,24 @@ class PermissionApprovedForSession: "Permission response variant that approves a request and remembers the provided approval for the rest of the session." approval: UserToolSessionApproval kind: ClassVar[str] = "approved-for-session" + managed_approval_handled: bool | None = None @staticmethod def from_dict(obj: Any) -> "PermissionApprovedForSession": assert isinstance(obj, dict) approval = _load_UserToolSessionApproval(obj.get("approval")) + managed_approval_handled = from_union([from_none, from_bool], obj.get("managedApprovalHandled")) return PermissionApprovedForSession( approval=approval, + managed_approval_handled=managed_approval_handled, ) def to_dict(self) -> dict: result: dict = {} result["approval"] = self.approval.to_dict() result["kind"] = self.kind + if self.managed_approval_handled is not None: + result["managedApprovalHandled"] = from_union([from_none, from_bool], self.managed_approval_handled) return result @@ -4588,7 +5948,7 @@ class PermissionPromptRequestCommands: intention: str kind: ClassVar[str] = "commands" # Experimental: this field is part of an experimental API and may change or be removed. - auto_approval: PermissionAutoApproval | None = None + assisted_approval: PermissionAssistedApproval | None = None managed_approval_required: bool | None = None tool_call_id: str | None = None warning: str | None = None @@ -4600,7 +5960,7 @@ def from_dict(obj: Any) -> "PermissionPromptRequestCommands": command_identifiers = from_list(from_str, obj.get("commandIdentifiers")) 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")) + assisted_approval = from_union([from_none, PermissionAssistedApproval.from_dict], obj.get("assistedApproval")) 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")) @@ -4609,7 +5969,7 @@ def from_dict(obj: Any) -> "PermissionPromptRequestCommands": command_identifiers=command_identifiers, full_command_text=full_command_text, intention=intention, - auto_approval=auto_approval, + assisted_approval=assisted_approval, managed_approval_required=managed_approval_required, tool_call_id=tool_call_id, warning=warning, @@ -4622,8 +5982,8 @@ def to_dict(self) -> dict: result["fullCommandText"] = from_str(self.full_command_text) result["intention"] = from_str(self.intention) 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.assisted_approval is not None: + result["assistedApproval"] = from_union([from_none, lambda x: to_class(PermissionAssistedApproval, x)], self.assisted_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: @@ -4641,7 +6001,7 @@ class PermissionPromptRequestCustomTool: tool_name: str args: Any = None # Experimental: this field is part of an experimental API and may change or be removed. - auto_approval: PermissionAutoApproval | None = None + assisted_approval: PermissionAssistedApproval | None = None tool_call_id: str | None = None @staticmethod @@ -4650,13 +6010,13 @@ def from_dict(obj: Any) -> "PermissionPromptRequestCustomTool": tool_description = from_str(obj.get("toolDescription")) tool_name = from_str(obj.get("toolName")) args = obj.get("args") - auto_approval = from_union([from_none, PermissionAutoApproval.from_dict], obj.get("autoApproval")) + assisted_approval = from_union([from_none, PermissionAssistedApproval.from_dict], obj.get("assistedApproval")) tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) return PermissionPromptRequestCustomTool( tool_description=tool_description, tool_name=tool_name, args=args, - auto_approval=auto_approval, + assisted_approval=assisted_approval, tool_call_id=tool_call_id, ) @@ -4667,8 +6027,44 @@ def to_dict(self) -> dict: result["toolName"] = from_str(self.tool_name) if self.args is not None: result["args"] = self.args - if self.auto_approval is not None: - result["autoApproval"] = from_union([from_none, lambda x: to_class(PermissionAutoApproval, x)], self.auto_approval) + if self.assisted_approval is not None: + result["assistedApproval"] = from_union([from_none, lambda x: to_class(PermissionAssistedApproval, x)], self.assisted_approval) + if self.tool_call_id is not None: + result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) + return result + + +@dataclass +class PermissionPromptRequestExtensionEnvAccess: + "Extension sensitive environment variable access prompt" + environment_variables: list[str] + extension_name: str + kind: ClassVar[str] = "extension-env-access" + # Experimental: this field is part of an experimental API and may change or be removed. + assisted_approval: PermissionAssistedApproval | None = None + tool_call_id: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "PermissionPromptRequestExtensionEnvAccess": + assert isinstance(obj, dict) + environment_variables = from_list(from_str, obj.get("environmentVariables")) + extension_name = from_str(obj.get("extensionName")) + assisted_approval = from_union([from_none, PermissionAssistedApproval.from_dict], obj.get("assistedApproval")) + tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) + return PermissionPromptRequestExtensionEnvAccess( + environment_variables=environment_variables, + extension_name=extension_name, + assisted_approval=assisted_approval, + tool_call_id=tool_call_id, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["environmentVariables"] = from_list(from_str, self.environment_variables) + result["extensionName"] = from_str(self.extension_name) + result["kind"] = self.kind + if self.assisted_approval is not None: + result["assistedApproval"] = from_union([from_none, lambda x: to_class(PermissionAssistedApproval, x)], self.assisted_approval) if self.tool_call_id is not None: result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) return result @@ -4680,7 +6076,7 @@ class PermissionPromptRequestExtensionManagement: kind: ClassVar[str] = "extension-management" operation: str # Experimental: this field is part of an experimental API and may change or be removed. - auto_approval: PermissionAutoApproval | None = None + assisted_approval: PermissionAssistedApproval | None = None extension_name: str | None = None tool_call_id: str | None = None @@ -4688,12 +6084,12 @@ class PermissionPromptRequestExtensionManagement: def from_dict(obj: Any) -> "PermissionPromptRequestExtensionManagement": assert isinstance(obj, dict) operation = from_str(obj.get("operation")) - auto_approval = from_union([from_none, PermissionAutoApproval.from_dict], obj.get("autoApproval")) + assisted_approval = from_union([from_none, PermissionAssistedApproval.from_dict], obj.get("assistedApproval")) extension_name = from_union([from_none, from_str], obj.get("extensionName")) tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) return PermissionPromptRequestExtensionManagement( operation=operation, - auto_approval=auto_approval, + assisted_approval=assisted_approval, extension_name=extension_name, tool_call_id=tool_call_id, ) @@ -4702,8 +6098,8 @@ def to_dict(self) -> dict: result: dict = {} result["kind"] = self.kind result["operation"] = from_str(self.operation) - if self.auto_approval is not None: - result["autoApproval"] = from_union([from_none, lambda x: to_class(PermissionAutoApproval, x)], self.auto_approval) + if self.assisted_approval is not None: + result["assistedApproval"] = from_union([from_none, lambda x: to_class(PermissionAssistedApproval, x)], self.assisted_approval) if self.extension_name is not None: result["extensionName"] = from_union([from_none, from_str], self.extension_name) if self.tool_call_id is not None: @@ -4718,7 +6114,7 @@ class PermissionPromptRequestExtensionPermissionAccess: extension_name: str kind: ClassVar[str] = "extension-permission-access" # Experimental: this field is part of an experimental API and may change or be removed. - auto_approval: PermissionAutoApproval | None = None + assisted_approval: PermissionAssistedApproval | None = None tool_call_id: str | None = None @staticmethod @@ -4726,12 +6122,12 @@ def from_dict(obj: Any) -> "PermissionPromptRequestExtensionPermissionAccess": assert isinstance(obj, dict) capabilities = from_list(from_str, obj.get("capabilities")) extension_name = from_str(obj.get("extensionName")) - auto_approval = from_union([from_none, PermissionAutoApproval.from_dict], obj.get("autoApproval")) + assisted_approval = from_union([from_none, PermissionAssistedApproval.from_dict], obj.get("assistedApproval")) tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) return PermissionPromptRequestExtensionPermissionAccess( capabilities=capabilities, extension_name=extension_name, - auto_approval=auto_approval, + assisted_approval=assisted_approval, tool_call_id=tool_call_id, ) @@ -4740,8 +6136,8 @@ def to_dict(self) -> dict: result["capabilities"] = from_list(from_str, self.capabilities) result["extensionName"] = from_str(self.extension_name) 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.assisted_approval is not None: + result["assistedApproval"] = from_union([from_none, lambda x: to_class(PermissionAssistedApproval, x)], self.assisted_approval) if self.tool_call_id is not None: result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) return result @@ -4758,7 +6154,7 @@ class PermissionPromptRequestFactory: 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 + assisted_approval: PermissionAssistedApproval | None = None declared_max_ai_credits: float | None = None declared_max_concurrent_subagents: int | None = None declared_max_total_subagents: int | None = None @@ -4779,7 +6175,7 @@ def from_dict(obj: Any) -> "PermissionPromptRequestFactory": 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")) + assisted_approval = from_union([from_none, PermissionAssistedApproval.from_dict], obj.get("assistedApproval")) 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")) @@ -4797,7 +6193,7 @@ def from_dict(obj: Any) -> "PermissionPromptRequestFactory": name=name, operation=operation, phases=phases, - auto_approval=auto_approval, + assisted_approval=assisted_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, @@ -4819,8 +6215,8 @@ def to_dict(self) -> dict: 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.assisted_approval is not None: + result["assistedApproval"] = from_union([from_none, lambda x: to_class(PermissionAssistedApproval, x)], self.assisted_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: @@ -4850,7 +6246,7 @@ class PermissionPromptRequestHook: kind: ClassVar[str] = "hook" tool_name: str # Experimental: this field is part of an experimental API and may change or be removed. - auto_approval: PermissionAutoApproval | None = None + assisted_approval: PermissionAssistedApproval | None = None hook_message: str | None = None tool_args: Any = None tool_call_id: str | None = None @@ -4859,13 +6255,13 @@ class PermissionPromptRequestHook: def from_dict(obj: Any) -> "PermissionPromptRequestHook": assert isinstance(obj, dict) tool_name = from_str(obj.get("toolName")) - auto_approval = from_union([from_none, PermissionAutoApproval.from_dict], obj.get("autoApproval")) + assisted_approval = from_union([from_none, PermissionAssistedApproval.from_dict], obj.get("assistedApproval")) 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")) return PermissionPromptRequestHook( tool_name=tool_name, - auto_approval=auto_approval, + assisted_approval=assisted_approval, hook_message=hook_message, tool_args=tool_args, tool_call_id=tool_call_id, @@ -4875,8 +6271,8 @@ def to_dict(self) -> dict: result: dict = {} result["kind"] = self.kind result["toolName"] = from_str(self.tool_name) - if self.auto_approval is not None: - result["autoApproval"] = from_union([from_none, lambda x: to_class(PermissionAutoApproval, x)], self.auto_approval) + if self.assisted_approval is not None: + result["assistedApproval"] = from_union([from_none, lambda x: to_class(PermissionAssistedApproval, x)], self.assisted_approval) if self.hook_message is not None: result["hookMessage"] = from_union([from_none, from_str], self.hook_message) if self.tool_args is not None: @@ -4895,7 +6291,10 @@ class PermissionPromptRequestMcp: tool_title: str args: Any = None # Experimental: this field is part of an experimental API and may change or be removed. - auto_approval: PermissionAutoApproval | None = None + assisted_approval: PermissionAssistedApproval | None = None + can_offer_server_wide_approval: bool | None = None + # Experimental: this field is part of an experimental API and may change or be removed. + permission_recommendation: PermissionRecommendation | None = None tool_call_id: str | None = None @staticmethod @@ -4905,14 +6304,18 @@ def from_dict(obj: Any) -> "PermissionPromptRequestMcp": tool_name = from_str(obj.get("toolName")) tool_title = from_str(obj.get("toolTitle")) args = obj.get("args") - auto_approval = from_union([from_none, PermissionAutoApproval.from_dict], obj.get("autoApproval")) + assisted_approval = from_union([from_none, PermissionAssistedApproval.from_dict], obj.get("assistedApproval")) + can_offer_server_wide_approval = from_union([from_none, from_bool], obj.get("canOfferServerWideApproval")) + permission_recommendation = from_union([from_none, lambda x: parse_enum(PermissionRecommendation, x)], obj.get("permissionRecommendation")) tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) return PermissionPromptRequestMcp( server_name=server_name, tool_name=tool_name, tool_title=tool_title, args=args, - auto_approval=auto_approval, + assisted_approval=assisted_approval, + can_offer_server_wide_approval=can_offer_server_wide_approval, + permission_recommendation=permission_recommendation, tool_call_id=tool_call_id, ) @@ -4924,8 +6327,12 @@ def to_dict(self) -> dict: result["toolTitle"] = from_str(self.tool_title) if self.args is not None: result["args"] = self.args - if self.auto_approval is not None: - result["autoApproval"] = from_union([from_none, lambda x: to_class(PermissionAutoApproval, x)], self.auto_approval) + if self.assisted_approval is not None: + result["assistedApproval"] = from_union([from_none, lambda x: to_class(PermissionAssistedApproval, x)], self.assisted_approval) + if self.can_offer_server_wide_approval is not None: + result["canOfferServerWideApproval"] = from_union([from_none, from_bool], self.can_offer_server_wide_approval) + if self.permission_recommendation is not None: + result["permissionRecommendation"] = from_union([from_none, lambda x: to_enum(PermissionRecommendation, x)], self.permission_recommendation) if self.tool_call_id is not None: result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) return result @@ -4938,7 +6345,7 @@ class PermissionPromptRequestMemory: kind: ClassVar[str] = "memory" action: PermissionRequestMemoryAction | None = None # Experimental: this field is part of an experimental API and may change or be removed. - auto_approval: PermissionAutoApproval | None = None + assisted_approval: PermissionAssistedApproval | None = None citations: str | None = None direction: PermissionRequestMemoryDirection | None = None reason: str | None = None @@ -4950,7 +6357,7 @@ def from_dict(obj: Any) -> "PermissionPromptRequestMemory": assert isinstance(obj, dict) fact = from_str(obj.get("fact")) action = from_union([from_none, lambda x: parse_enum(PermissionRequestMemoryAction, x)], obj.get("action")) - auto_approval = from_union([from_none, PermissionAutoApproval.from_dict], obj.get("autoApproval")) + assisted_approval = from_union([from_none, PermissionAssistedApproval.from_dict], obj.get("assistedApproval")) citations = from_union([from_none, from_str], obj.get("citations")) direction = from_union([from_none, lambda x: parse_enum(PermissionRequestMemoryDirection, x)], obj.get("direction")) reason = from_union([from_none, from_str], obj.get("reason")) @@ -4959,7 +6366,7 @@ def from_dict(obj: Any) -> "PermissionPromptRequestMemory": return PermissionPromptRequestMemory( fact=fact, action=action, - auto_approval=auto_approval, + assisted_approval=assisted_approval, citations=citations, direction=direction, reason=reason, @@ -4973,8 +6380,8 @@ def to_dict(self) -> dict: result["kind"] = self.kind if self.action is not None: result["action"] = from_union([from_none, lambda x: to_enum(PermissionRequestMemoryAction, x)], self.action) - if self.auto_approval is not None: - result["autoApproval"] = from_union([from_none, lambda x: to_class(PermissionAutoApproval, x)], self.auto_approval) + if self.assisted_approval is not None: + result["assistedApproval"] = from_union([from_none, lambda x: to_class(PermissionAssistedApproval, x)], self.assisted_approval) if self.citations is not None: result["citations"] = from_union([from_none, from_str], self.citations) if self.direction is not None: @@ -4995,7 +6402,7 @@ class PermissionPromptRequestPath: kind: ClassVar[str] = "path" paths: list[str] # Experimental: this field is part of an experimental API and may change or be removed. - auto_approval: PermissionAutoApproval | None = None + assisted_approval: PermissionAssistedApproval | None = None tool_call_id: str | None = None @staticmethod @@ -5003,12 +6410,12 @@ def from_dict(obj: Any) -> "PermissionPromptRequestPath": assert isinstance(obj, dict) access_kind = parse_enum(PermissionPromptRequestPathAccessKind, obj.get("accessKind")) paths = from_list(from_str, obj.get("paths")) - auto_approval = from_union([from_none, PermissionAutoApproval.from_dict], obj.get("autoApproval")) + assisted_approval = from_union([from_none, PermissionAssistedApproval.from_dict], obj.get("assistedApproval")) tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) return PermissionPromptRequestPath( access_kind=access_kind, paths=paths, - auto_approval=auto_approval, + assisted_approval=assisted_approval, tool_call_id=tool_call_id, ) @@ -5017,8 +6424,8 @@ def to_dict(self) -> dict: result["accessKind"] = to_enum(PermissionPromptRequestPathAccessKind, self.access_kind) result["kind"] = self.kind result["paths"] = from_list(from_str, self.paths) - if self.auto_approval is not None: - result["autoApproval"] = from_union([from_none, lambda x: to_class(PermissionAutoApproval, x)], self.auto_approval) + if self.assisted_approval is not None: + result["assistedApproval"] = from_union([from_none, lambda x: to_class(PermissionAssistedApproval, x)], self.assisted_approval) if self.tool_call_id is not None: result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) return result @@ -5031,7 +6438,7 @@ class PermissionPromptRequestRead: kind: ClassVar[str] = "read" path: str # Experimental: this field is part of an experimental API and may change or be removed. - auto_approval: PermissionAutoApproval | None = None + assisted_approval: PermissionAssistedApproval | None = None managed_approval_required: bool | None = None tool_call_id: str | None = None @@ -5040,13 +6447,13 @@ def from_dict(obj: Any) -> "PermissionPromptRequestRead": assert isinstance(obj, dict) intention = from_str(obj.get("intention")) path = from_str(obj.get("path")) - auto_approval = from_union([from_none, PermissionAutoApproval.from_dict], obj.get("autoApproval")) + assisted_approval = from_union([from_none, PermissionAssistedApproval.from_dict], obj.get("assistedApproval")) 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, + assisted_approval=assisted_approval, managed_approval_required=managed_approval_required, tool_call_id=tool_call_id, ) @@ -5056,8 +6463,8 @@ def to_dict(self) -> dict: result["intention"] = from_str(self.intention) result["kind"] = self.kind 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.assisted_approval is not None: + result["assistedApproval"] = from_union([from_none, lambda x: to_class(PermissionAssistedApproval, x)], self.assisted_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: @@ -5072,7 +6479,7 @@ class PermissionPromptRequestUrl: kind: ClassVar[str] = "url" url: str # Experimental: this field is part of an experimental API and may change or be removed. - auto_approval: PermissionAutoApproval | None = None + assisted_approval: PermissionAssistedApproval | None = None managed_approval_required: bool | None = None redirected_from: str | None = None request_sandbox_bypass: bool | None = None @@ -5084,7 +6491,7 @@ def from_dict(obj: Any) -> "PermissionPromptRequestUrl": assert isinstance(obj, dict) intention = from_str(obj.get("intention")) url = from_str(obj.get("url")) - auto_approval = from_union([from_none, PermissionAutoApproval.from_dict], obj.get("autoApproval")) + assisted_approval = from_union([from_none, PermissionAssistedApproval.from_dict], obj.get("assistedApproval")) 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")) @@ -5093,7 +6500,7 @@ def from_dict(obj: Any) -> "PermissionPromptRequestUrl": return PermissionPromptRequestUrl( intention=intention, url=url, - auto_approval=auto_approval, + assisted_approval=assisted_approval, managed_approval_required=managed_approval_required, redirected_from=redirected_from, request_sandbox_bypass=request_sandbox_bypass, @@ -5106,8 +6513,8 @@ def to_dict(self) -> dict: result["intention"] = from_str(self.intention) result["kind"] = self.kind 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.assisted_approval is not None: + result["assistedApproval"] = from_union([from_none, lambda x: to_class(PermissionAssistedApproval, x)], self.assisted_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: @@ -5130,7 +6537,7 @@ class PermissionPromptRequestWrite: intention: str kind: ClassVar[str] = "write" # Experimental: this field is part of an experimental API and may change or be removed. - auto_approval: PermissionAutoApproval | None = None + assisted_approval: PermissionAssistedApproval | None = None managed_approval_required: bool | None = None new_file_contents: str | None = None tool_call_id: str | None = None @@ -5142,7 +6549,7 @@ def from_dict(obj: Any) -> "PermissionPromptRequestWrite": diff = from_str(obj.get("diff")) 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")) + assisted_approval = from_union([from_none, PermissionAssistedApproval.from_dict], obj.get("assistedApproval")) 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")) @@ -5151,7 +6558,7 @@ def from_dict(obj: Any) -> "PermissionPromptRequestWrite": diff=diff, file_name=file_name, intention=intention, - auto_approval=auto_approval, + assisted_approval=assisted_approval, managed_approval_required=managed_approval_required, new_file_contents=new_file_contents, tool_call_id=tool_call_id, @@ -5164,8 +6571,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.auto_approval is not None: - result["autoApproval"] = from_union([from_none, lambda x: to_class(PermissionAutoApproval, x)], self.auto_approval) + if self.assisted_approval is not None: + result["assistedApproval"] = from_union([from_none, lambda x: to_class(PermissionAssistedApproval, x)], self.assisted_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: @@ -5184,6 +6591,7 @@ class PermissionRequestCustomTool: args: Any = None tool_call_id: str | None = None managed_approval_required: bool | None = None + skip_permission: bool | None = None @staticmethod def from_dict(obj: Any) -> "PermissionRequestCustomTool": @@ -5193,12 +6601,14 @@ def from_dict(obj: Any) -> "PermissionRequestCustomTool": 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")) + skip_permission = from_union([from_none, from_bool], obj.get("skipPermission")) return PermissionRequestCustomTool( tool_description=tool_description, tool_name=tool_name, args=args, tool_call_id=tool_call_id, managed_approval_required=managed_approval_required, + skip_permission=skip_permission, ) def to_dict(self) -> dict: @@ -5208,6 +6618,43 @@ def to_dict(self) -> dict: result["toolName"] = from_str(self.tool_name) if self.args is not None: 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) + if self.skip_permission is not None: + result["skipPermission"] = from_union([from_none, from_bool], self.skip_permission) + return result + + +@dataclass +class PermissionRequestExtensionEnvAccess: + "Extension sensitive environment variable access request" + environment_variables: list[str] + extension_name: str + kind: ClassVar[str] = "extension-env-access" + tool_call_id: str | None = None + managed_approval_required: bool | None = None + + @staticmethod + def from_dict(obj: Any) -> "PermissionRequestExtensionEnvAccess": + assert isinstance(obj, dict) + environment_variables = from_list(from_str, obj.get("environmentVariables")) + 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 PermissionRequestExtensionEnvAccess( + environment_variables=environment_variables, + extension_name=extension_name, + tool_call_id=tool_call_id, + managed_approval_required=managed_approval_required, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["environmentVariables"] = from_list(from_str, self.environment_variables) + result["extensionName"] = from_str(self.extension_name) + 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: @@ -5427,6 +6874,8 @@ class PermissionRequestMcp: tool_name: str tool_title: str args: Any = None + # Experimental: this field is part of an experimental API and may change or be removed. + permission_recommendation: PermissionRecommendation | None = None tool_call_id: str | None = None managed_approval_required: bool | None = None @@ -5438,6 +6887,7 @@ def from_dict(obj: Any) -> "PermissionRequestMcp": tool_name = from_str(obj.get("toolName")) tool_title = from_str(obj.get("toolTitle")) args = obj.get("args") + permission_recommendation = from_union([from_none, lambda x: parse_enum(PermissionRecommendation, x)], obj.get("permissionRecommendation")) 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( @@ -5446,6 +6896,7 @@ def from_dict(obj: Any) -> "PermissionRequestMcp": tool_name=tool_name, tool_title=tool_title, args=args, + permission_recommendation=permission_recommendation, tool_call_id=tool_call_id, managed_approval_required=managed_approval_required, ) @@ -5459,6 +6910,8 @@ def to_dict(self) -> dict: result["toolTitle"] = from_str(self.tool_title) if self.args is not None: result["args"] = self.args + if self.permission_recommendation is not None: + result["permissionRecommendation"] = from_union([from_none, lambda x: to_enum(PermissionRecommendation, x)], self.permission_recommendation) 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: @@ -5472,9 +6925,13 @@ class PermissionRequestMemory: fact: str kind: ClassVar[str] = "memory" action: PermissionRequestMemoryAction | None = None + # Experimental: this field is part of an experimental API and may change or be removed. + assisted_approval: PermissionAssistedApproval | None = None citations: str | None = None direction: PermissionRequestMemoryDirection | None = None reason: str | None = None + repo_nwo: str | None = None + scope: PermissionRequestMemoryScope | None = None subject: str | None = None tool_call_id: str | None = None managed_approval_required: bool | None = None @@ -5484,18 +6941,24 @@ def from_dict(obj: Any) -> "PermissionRequestMemory": assert isinstance(obj, dict) fact = from_str(obj.get("fact")) action = from_union([from_none, lambda x: parse_enum(PermissionRequestMemoryAction, x)], obj.get("action")) + assisted_approval = from_union([from_none, PermissionAssistedApproval.from_dict], obj.get("assistedApproval")) citations = from_union([from_none, from_str], obj.get("citations")) direction = from_union([from_none, lambda x: parse_enum(PermissionRequestMemoryDirection, x)], obj.get("direction")) reason = from_union([from_none, from_str], obj.get("reason")) + repo_nwo = from_union([from_none, from_str], obj.get("repoNwo")) + scope = from_union([from_none, lambda x: parse_enum(PermissionRequestMemoryScope, x)], obj.get("scope")) 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, + assisted_approval=assisted_approval, citations=citations, direction=direction, reason=reason, + repo_nwo=repo_nwo, + scope=scope, subject=subject, tool_call_id=tool_call_id, managed_approval_required=managed_approval_required, @@ -5507,12 +6970,18 @@ def to_dict(self) -> dict: result["kind"] = self.kind if self.action is not None: result["action"] = from_union([from_none, lambda x: to_enum(PermissionRequestMemoryAction, x)], self.action) + if self.assisted_approval is not None: + result["assistedApproval"] = from_union([from_none, lambda x: to_class(PermissionAssistedApproval, x)], self.assisted_approval) if self.citations is not None: result["citations"] = from_union([from_none, from_str], self.citations) if self.direction is not None: result["direction"] = from_union([from_none, lambda x: to_enum(PermissionRequestMemoryDirection, x)], self.direction) if self.reason is not None: result["reason"] = from_union([from_none, from_str], self.reason) + if self.repo_nwo is not None: + result["repoNwo"] = from_union([from_none, from_str], self.repo_nwo) + if self.scope is not None: + result["scope"] = from_union([from_none, lambda x: to_enum(PermissionRequestMemoryScope, x)], self.scope) if self.subject is not None: result["subject"] = from_union([from_none, from_str], self.subject) if self.tool_call_id is not None: @@ -5820,6 +7289,7 @@ class PermissionRequestedData: "Permission request notification requiring client approval with request details" permission_request: PermissionRequest request_id: str + agent_mode: SessionMode | None = None prompt_request: PermissionPromptRequest | None = None resolved_by_hook: bool | None = None risk_assessment: Any = None @@ -5829,12 +7299,14 @@ def from_dict(obj: Any) -> "PermissionRequestedData": assert isinstance(obj, dict) permission_request = _load_PermissionRequest(obj.get("permissionRequest")) request_id = from_str(obj.get("requestId")) + agent_mode = from_union([from_none, lambda x: parse_enum(SessionMode, x)], obj.get("agentMode")) 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, + agent_mode=agent_mode, prompt_request=prompt_request, resolved_by_hook=resolved_by_hook, risk_assessment=risk_assessment, @@ -5844,6 +7316,8 @@ def to_dict(self) -> dict: result: dict = {} result["permissionRequest"] = self.permission_request.to_dict() result["requestId"] = from_str(self.request_id) + if self.agent_mode is not None: + result["agentMode"] = from_union([from_none, lambda x: to_enum(SessionMode, x)], self.agent_mode) if self.prompt_request is not None: result["promptRequest"] = from_union([from_none, lambda x: x.to_dict()], self.prompt_request) if self.resolved_by_hook is not None: @@ -5913,6 +7387,147 @@ def to_dict(self) -> dict: return result +@dataclass +class PromptCacheBreakData: + "A detected loss of a previously cached prompt prefix" + contributing_reasons: list[str] + frontier_tokens: int + primary_reason: str + retention_ratio: float + shortfall_tokens: int + survived_tokens: int + # Internal: this field is an internal SDK API and is not part of the public surface. + _after_request: Any = None + # Internal: this field is an internal SDK API and is not part of the public surface. + _agent_name: str | None = None + # Internal: this field is an internal SDK API and is not part of the public surface. + _before_request: Any = None + # Internal: this field is an internal SDK API and is not part of the public surface. + _cache_config_changed_fields: list[str] | None = None + # Internal: this field is an internal SDK API and is not part of the public surface. + _model_from: str | None = None + # Internal: this field is an internal SDK API and is not part of the public surface. + _model_to: str | None = None + # Internal: this field is an internal SDK API and is not part of the public surface. + _rewrite_message_index: int | None = None + # Internal: this field is an internal SDK API and is not part of the public surface. + _rewrite_shape: str | None = None + # Internal: this field is an internal SDK API and is not part of the public surface. + _rewrite_source: list[str] | None = None + # Internal: this field is an internal SDK API and is not part of the public surface. + _system_segments_changed: list[str] | None = None + # Internal: this field is an internal SDK API and is not part of the public surface. + _tools_added: list[str] | None = None + # Internal: this field is an internal SDK API and is not part of the public surface. + _tools_added_raw: list[str] | None = None + # Internal: this field is an internal SDK API and is not part of the public surface. + _tools_redefined: list[str] | None = None + # Internal: this field is an internal SDK API and is not part of the public surface. + _tools_redefined_raw: list[str] | None = None + # Internal: this field is an internal SDK API and is not part of the public surface. + _tools_removed: list[str] | None = None + # Internal: this field is an internal SDK API and is not part of the public surface. + _tools_removed_raw: list[str] | None = None + # Internal: this field is an internal SDK API and is not part of the public surface. + _tools_reordered: bool | None = None + + @staticmethod + def from_dict(obj: Any) -> "PromptCacheBreakData": + assert isinstance(obj, dict) + contributing_reasons = from_list(from_str, obj.get("contributingReasons")) + frontier_tokens = from_int(obj.get("frontierTokens")) + primary_reason = from_str(obj.get("primaryReason")) + retention_ratio = from_float(obj.get("retentionRatio")) + shortfall_tokens = from_int(obj.get("shortfallTokens")) + survived_tokens = from_int(obj.get("survivedTokens")) + _after_request = obj.get("afterRequest") + _agent_name = from_union([from_none, from_str], obj.get("agentName")) + _before_request = obj.get("beforeRequest") + _cache_config_changed_fields = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("cacheConfigChangedFields")) + _model_from = from_union([from_none, from_str], obj.get("modelFrom")) + _model_to = from_union([from_none, from_str], obj.get("modelTo")) + _rewrite_message_index = from_union([from_none, from_int], obj.get("rewriteMessageIndex")) + _rewrite_shape = from_union([from_none, from_str], obj.get("rewriteShape")) + _rewrite_source = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("rewriteSource")) + _system_segments_changed = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("systemSegmentsChanged")) + _tools_added = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("toolsAdded")) + _tools_added_raw = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("toolsAddedRaw")) + _tools_redefined = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("toolsRedefined")) + _tools_redefined_raw = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("toolsRedefinedRaw")) + _tools_removed = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("toolsRemoved")) + _tools_removed_raw = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("toolsRemovedRaw")) + _tools_reordered = from_union([from_none, from_bool], obj.get("toolsReordered")) + return PromptCacheBreakData( + contributing_reasons=contributing_reasons, + frontier_tokens=frontier_tokens, + primary_reason=primary_reason, + retention_ratio=retention_ratio, + shortfall_tokens=shortfall_tokens, + survived_tokens=survived_tokens, + _after_request=_after_request, + _agent_name=_agent_name, + _before_request=_before_request, + _cache_config_changed_fields=_cache_config_changed_fields, + _model_from=_model_from, + _model_to=_model_to, + _rewrite_message_index=_rewrite_message_index, + _rewrite_shape=_rewrite_shape, + _rewrite_source=_rewrite_source, + _system_segments_changed=_system_segments_changed, + _tools_added=_tools_added, + _tools_added_raw=_tools_added_raw, + _tools_redefined=_tools_redefined, + _tools_redefined_raw=_tools_redefined_raw, + _tools_removed=_tools_removed, + _tools_removed_raw=_tools_removed_raw, + _tools_reordered=_tools_reordered, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["contributingReasons"] = from_list(from_str, self.contributing_reasons) + result["frontierTokens"] = to_int(self.frontier_tokens) + result["primaryReason"] = from_str(self.primary_reason) + result["retentionRatio"] = to_float(self.retention_ratio) + result["shortfallTokens"] = to_int(self.shortfall_tokens) + result["survivedTokens"] = to_int(self.survived_tokens) + if self._after_request is not None: + result["afterRequest"] = self._after_request + if self._agent_name is not None: + result["agentName"] = from_union([from_none, from_str], self._agent_name) + if self._before_request is not None: + result["beforeRequest"] = self._before_request + if self._cache_config_changed_fields is not None: + result["cacheConfigChangedFields"] = from_union([from_none, lambda x: from_list(from_str, x)], self._cache_config_changed_fields) + if self._model_from is not None: + result["modelFrom"] = from_union([from_none, from_str], self._model_from) + if self._model_to is not None: + result["modelTo"] = from_union([from_none, from_str], self._model_to) + if self._rewrite_message_index is not None: + result["rewriteMessageIndex"] = from_union([from_none, to_int], self._rewrite_message_index) + if self._rewrite_shape is not None: + result["rewriteShape"] = from_union([from_none, from_str], self._rewrite_shape) + if self._rewrite_source is not None: + result["rewriteSource"] = from_union([from_none, lambda x: from_list(from_str, x)], self._rewrite_source) + if self._system_segments_changed is not None: + result["systemSegmentsChanged"] = from_union([from_none, lambda x: from_list(from_str, x)], self._system_segments_changed) + if self._tools_added is not None: + result["toolsAdded"] = from_union([from_none, lambda x: from_list(from_str, x)], self._tools_added) + if self._tools_added_raw is not None: + result["toolsAddedRaw"] = from_union([from_none, lambda x: from_list(from_str, x)], self._tools_added_raw) + if self._tools_redefined is not None: + result["toolsRedefined"] = from_union([from_none, lambda x: from_list(from_str, x)], self._tools_redefined) + if self._tools_redefined_raw is not None: + result["toolsRedefinedRaw"] = from_union([from_none, lambda x: from_list(from_str, x)], self._tools_redefined_raw) + if self._tools_removed is not None: + result["toolsRemoved"] = from_union([from_none, lambda x: from_list(from_str, x)], self._tools_removed) + if self._tools_removed_raw is not None: + result["toolsRemovedRaw"] = from_union([from_none, lambda x: from_list(from_str, x)], self._tools_removed_raw) + if self._tools_reordered is not None: + result["toolsReordered"] = from_union([from_none, from_bool], self._tools_reordered) + return result + + @dataclass class SamplingCompletedData: "Sampling request completion notification signaling UI dismissal" @@ -5959,6 +7574,18 @@ def to_dict(self) -> dict: return result +@dataclass +class SandboxDecisionData: + "Payload of `sandbox.decision`, a bounded governance record of what the process sandbox was configured to do and whether it took effect. Discriminated by `kind`." + @staticmethod + def from_dict(obj: Any) -> "SandboxDecisionData": + assert isinstance(obj, dict) + return SandboxDecisionData() + + def to_dict(self) -> dict: + return {} + + @dataclass class SessionAutopilotObjectiveChangedData: "Autopilot objective state file operation details indicating what changed" @@ -6049,6 +7676,7 @@ def to_dict(self) -> dict: class SessionCompactionCompleteData: "Conversation compaction results including success status, metrics, and optional error details" success: bool + behavior_model_id: str | None = None checkpoint_number: int | None = None checkpoint_path: str | None = None compaction_tokens_used: CompactionCompleteCompactionTokensUsed | None = None @@ -6073,6 +7701,7 @@ class SessionCompactionCompleteData: def from_dict(obj: Any) -> "SessionCompactionCompleteData": assert isinstance(obj, dict) success = from_bool(obj.get("success")) + behavior_model_id = from_union([from_none, from_str], obj.get("behaviorModelId")) checkpoint_number = from_union([from_none, from_int], obj.get("checkpointNumber")) checkpoint_path = from_union([from_none, from_str], obj.get("checkpointPath")) compaction_tokens_used = from_union([from_none, CompactionCompleteCompactionTokensUsed.from_dict], obj.get("compactionTokensUsed")) @@ -6094,6 +7723,7 @@ def from_dict(obj: Any) -> "SessionCompactionCompleteData": trigger = from_union([from_none, lambda x: parse_enum(CompactionTrigger, x)], obj.get("trigger")) return SessionCompactionCompleteData( success=success, + behavior_model_id=behavior_model_id, checkpoint_number=checkpoint_number, checkpoint_path=checkpoint_path, compaction_tokens_used=compaction_tokens_used, @@ -6118,6 +7748,8 @@ def from_dict(obj: Any) -> "SessionCompactionCompleteData": def to_dict(self) -> dict: result: dict = {} result["success"] = from_bool(self.success) + if self.behavior_model_id is not None: + result["behaviorModelId"] = from_union([from_none, from_str], self.behavior_model_id) if self.checkpoint_number is not None: result["checkpointNumber"] = from_union([from_none, to_int], self.checkpoint_number) if self.checkpoint_path is not None: @@ -6504,19 +8136,24 @@ def to_dict(self) -> dict: class SessionIdleData: "Payload indicating the session is idle with no background agents or attached shell commands in flight" aborted: bool | None = None + mode: SessionMode | None = None @staticmethod def from_dict(obj: Any) -> "SessionIdleData": assert isinstance(obj, dict) aborted = from_union([from_none, from_bool], obj.get("aborted")) + mode = from_union([from_none, lambda x: parse_enum(SessionMode, x)], obj.get("mode")) return SessionIdleData( aborted=aborted, + mode=mode, ) def to_dict(self) -> dict: result: dict = {} if self.aborted is not None: result["aborted"] = from_union([from_none, from_bool], self.aborted) + if self.mode is not None: + result["mode"] = from_union([from_none, lambda x: to_enum(SessionMode, x)], self.mode) return result @@ -6722,6 +8359,30 @@ def to_dict(self) -> dict: return result +@dataclass +class SessionModeNoticeDeliveredData: + "Records that a mode transition notice reached the model so cache-stable mode tools can remain offered across resume." + mode: SessionMode + content: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "SessionModeNoticeDeliveredData": + assert isinstance(obj, dict) + mode = parse_enum(SessionMode, obj.get("mode")) + content = from_union([from_none, from_str], obj.get("content")) + return SessionModeNoticeDeliveredData( + mode=mode, + content=content, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["mode"] = to_enum(SessionMode, self.mode) + if self.content is not None: + result["content"] = from_union([from_none, from_str], self.content) + return result + + @dataclass class SessionModelChangeData: "Model change details including previous and new model identifiers" @@ -6734,6 +8395,7 @@ class SessionModelChangeData: previous_verbosity: Verbosity | None = None reasoning_effort: str | None = None reasoning_summary: ReasoningSummary | None = None + source: ModelChangeSource | None = None verbosity: Verbosity | None = None @staticmethod @@ -6748,6 +8410,7 @@ def from_dict(obj: Any) -> "SessionModelChangeData": previous_verbosity = from_union([from_none, lambda x: parse_enum(Verbosity, x)], obj.get("previousVerbosity")) 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")) + source = from_union([from_none, lambda x: parse_enum(ModelChangeSource, x)], obj.get("source")) verbosity = from_union([from_none, lambda x: parse_enum(Verbosity, x)], obj.get("verbosity")) return SessionModelChangeData( new_model=new_model, @@ -6759,6 +8422,7 @@ def from_dict(obj: Any) -> "SessionModelChangeData": previous_verbosity=previous_verbosity, reasoning_effort=reasoning_effort, reasoning_summary=reasoning_summary, + source=source, verbosity=verbosity, ) @@ -6781,46 +8445,13 @@ def to_dict(self) -> dict: result["reasoningEffort"] = from_union([from_none, from_str], self.reasoning_effort) if self.reasoning_summary is not None: result["reasoningSummary"] = from_union([from_none, lambda x: to_enum(ReasoningSummary, x)], self.reasoning_summary) + if self.source is not None: + result["source"] = from_union([from_none, lambda x: to_enum(ModelChangeSource, x)], self.source) if self.verbosity is not None: result["verbosity"] = from_union([from_none, lambda x: to_enum(Verbosity, x)], self.verbosity) return result -@dataclass -class SessionPermissionsChangedData: - "Permissions change details carrying the aggregate allow-all transition." - allow_all_permissions: bool - previous_allow_all_permissions: bool - # Experimental: this field is part of an experimental API and may change or be removed. - allow_all_permission_mode: PermissionAllowAllMode | None = None - # Experimental: this field is part of an experimental API and may change or be removed. - previous_allow_all_permission_mode: PermissionAllowAllMode | None = None - - @staticmethod - def from_dict(obj: Any) -> "SessionPermissionsChangedData": - assert isinstance(obj, dict) - allow_all_permissions = from_bool(obj.get("allowAllPermissions")) - previous_allow_all_permissions = from_bool(obj.get("previousAllowAllPermissions")) - allow_all_permission_mode = from_union([from_none, lambda x: parse_enum(PermissionAllowAllMode, x)], obj.get("allowAllPermissionMode")) - previous_allow_all_permission_mode = from_union([from_none, lambda x: parse_enum(PermissionAllowAllMode, x)], obj.get("previousAllowAllPermissionMode")) - return SessionPermissionsChangedData( - allow_all_permissions=allow_all_permissions, - previous_allow_all_permissions=previous_allow_all_permissions, - allow_all_permission_mode=allow_all_permission_mode, - previous_allow_all_permission_mode=previous_allow_all_permission_mode, - ) - - def to_dict(self) -> dict: - result: dict = {} - result["allowAllPermissions"] = from_bool(self.allow_all_permissions) - result["previousAllowAllPermissions"] = from_bool(self.previous_allow_all_permissions) - if self.allow_all_permission_mode is not None: - result["allowAllPermissionMode"] = from_union([from_none, lambda x: to_enum(PermissionAllowAllMode, x)], self.allow_all_permission_mode) - if self.previous_allow_all_permission_mode is not None: - result["previousAllowAllPermissionMode"] = from_union([from_none, lambda x: to_enum(PermissionAllowAllMode, x)], self.previous_allow_all_permission_mode) - return result - - @dataclass class SessionPlanChangedData: "Plan file operation details indicating what changed" @@ -6865,6 +8496,7 @@ class SessionResumeData: event_count: int resume_time: datetime already_in_use: bool | None = None + auto_tier: AutoTier | None = None context: WorkingDirectoryContext | None = None context_tier: ContextTier | None = None continue_pending_work: bool | None = None @@ -6883,6 +8515,7 @@ def from_dict(obj: Any) -> "SessionResumeData": event_count = from_int(obj.get("eventCount")) resume_time = from_datetime(obj.get("resumeTime")) already_in_use = from_union([from_none, from_bool], obj.get("alreadyInUse")) + auto_tier = from_union([from_none, lambda x: parse_enum(AutoTier, x)], obj.get("autoTier")) 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")) continue_pending_work = from_union([from_none, from_bool], obj.get("continuePendingWork")) @@ -6898,6 +8531,7 @@ def from_dict(obj: Any) -> "SessionResumeData": event_count=event_count, resume_time=resume_time, already_in_use=already_in_use, + auto_tier=auto_tier, context=context, context_tier=context_tier, continue_pending_work=continue_pending_work, @@ -6917,6 +8551,8 @@ def to_dict(self) -> dict: result["resumeTime"] = to_datetime(self.resume_time) if self.already_in_use is not None: result["alreadyInUse"] = from_union([from_none, from_bool], self.already_in_use) + if self.auto_tier is not None: + result["autoTier"] = from_union([from_none, lambda x: to_enum(AutoTier, x)], self.auto_tier) if self.context is not None: result["context"] = from_union([from_none, lambda x: to_class(WorkingDirectoryContext, x)], self.context) if self.context_tier is not None: @@ -7074,6 +8710,7 @@ class SessionShutdownData: session_start_time: int shutdown_type: ShutdownType total_api_duration: timedelta + agent_metrics: dict[str, ShutdownAgentMetric] | None = None conversation_tokens: int | None = None current_model: str | None = None current_tokens: int | None = None @@ -7095,6 +8732,7 @@ def from_dict(obj: Any) -> "SessionShutdownData": session_start_time = from_int(obj.get("sessionStartTime")) shutdown_type = parse_enum(ShutdownType, obj.get("shutdownType")) total_api_duration = from_timedelta(obj.get("totalApiDurationMs")) + agent_metrics = from_union([from_none, lambda x: from_dict(ShutdownAgentMetric.from_dict, x)], obj.get("agentMetrics")) conversation_tokens = from_union([from_none, from_int], obj.get("conversationTokens")) current_model = from_union([from_none, from_str], obj.get("currentModel")) current_tokens = from_union([from_none, from_int], obj.get("currentTokens")) @@ -7111,6 +8749,7 @@ def from_dict(obj: Any) -> "SessionShutdownData": session_start_time=session_start_time, shutdown_type=shutdown_type, total_api_duration=total_api_duration, + agent_metrics=agent_metrics, conversation_tokens=conversation_tokens, current_model=current_model, current_tokens=current_tokens, @@ -7130,6 +8769,8 @@ def to_dict(self) -> dict: result["sessionStartTime"] = to_int(self.session_start_time) result["shutdownType"] = to_enum(ShutdownType, self.shutdown_type) result["totalApiDurationMs"] = to_timedelta_int(self.total_api_duration) + if self.agent_metrics is not None: + result["agentMetrics"] = from_union([from_none, lambda x: from_dict(lambda x: to_class(ShutdownAgentMetric, x), x)], self.agent_metrics) if self.conversation_tokens is not None: result["conversationTokens"] = from_union([from_none, to_int], self.conversation_tokens) if self.current_model is not None: @@ -7204,6 +8845,7 @@ class SessionStartData: start_time: datetime version: int already_in_use: bool | None = None + auto_tier: AutoTier | None = None context: WorkingDirectoryContext | None = None context_tier: ContextTier | None = None detached_from_spawning_parent_session_id: str | None = None @@ -7224,6 +8866,7 @@ def from_dict(obj: Any) -> "SessionStartData": start_time = from_datetime(obj.get("startTime")) version = from_int(obj.get("version")) already_in_use = from_union([from_none, from_bool], obj.get("alreadyInUse")) + auto_tier = from_union([from_none, lambda x: parse_enum(AutoTier, x)], obj.get("autoTier")) 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")) @@ -7241,6 +8884,7 @@ def from_dict(obj: Any) -> "SessionStartData": start_time=start_time, version=version, already_in_use=already_in_use, + auto_tier=auto_tier, context=context, context_tier=context_tier, detached_from_spawning_parent_session_id=detached_from_spawning_parent_session_id, @@ -7262,6 +8906,8 @@ def to_dict(self) -> dict: result["version"] = to_int(self.version) if self.already_in_use is not None: result["alreadyInUse"] = from_union([from_none, from_bool], self.already_in_use) + if self.auto_tier is not None: + result["autoTier"] = from_union([from_none, lambda x: to_enum(AutoTier, x)], self.auto_tier) if self.context is not None: result["context"] = from_union([from_none, lambda x: to_class(WorkingDirectoryContext, x)], self.context) if self.context_tier is not None: @@ -7429,6 +9075,8 @@ class SessionUsageCheckpointData: # Internal: this field is an internal SDK API and is not part of the public surface. _model_cache_state: list[_UsageCheckpointModelCacheState] | None = None # Internal: this field is an internal SDK API and is not part of the public surface. + _prompt_cache_break_state: list[Any] | None = None + # Internal: this field is an internal SDK API and is not part of the public surface. _total_premium_requests: float | None = None @staticmethod @@ -7436,10 +9084,12 @@ def from_dict(obj: Any) -> "SessionUsageCheckpointData": assert isinstance(obj, dict) total_nano_aiu = from_float(obj.get("totalNanoAiu")) _model_cache_state = from_union([from_none, lambda x: from_list(_UsageCheckpointModelCacheState.from_dict, x)], obj.get("modelCacheState")) + _prompt_cache_break_state = from_union([from_none, lambda x: from_list(lambda x: x, x)], obj.get("promptCacheBreakState")) _total_premium_requests = from_union([from_none, from_float], obj.get("totalPremiumRequests")) return SessionUsageCheckpointData( total_nano_aiu=total_nano_aiu, _model_cache_state=_model_cache_state, + _prompt_cache_break_state=_prompt_cache_break_state, _total_premium_requests=_total_premium_requests, ) @@ -7448,6 +9098,8 @@ def to_dict(self) -> dict: result["totalNanoAiu"] = to_float(self.total_nano_aiu) if self._model_cache_state is not None: result["modelCacheState"] = from_union([from_none, lambda x: from_list(lambda x: to_class(_UsageCheckpointModelCacheState, x), x)], self._model_cache_state) + if self._prompt_cache_break_state is not None: + result["promptCacheBreakState"] = from_union([from_none, lambda x: from_list(lambda x: x, x)], self._prompt_cache_break_state) if self._total_premium_requests is not None: result["totalPremiumRequests"] = from_union([from_none, to_float], self._total_premium_requests) return result @@ -7551,6 +9203,43 @@ def to_dict(self) -> dict: return result +@dataclass +class ShutdownAgentMetric: + "Usage attributed to one agent instance at session shutdown." + model_metrics: dict[str, ShutdownModelMetric] + total_api_duration: timedelta + total_nano_aiu: float + agent_display_name: str | None = None + agent_name: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "ShutdownAgentMetric": + assert isinstance(obj, dict) + model_metrics = from_dict(ShutdownModelMetric.from_dict, obj.get("modelMetrics")) + total_api_duration = from_timedelta(obj.get("totalApiDurationMs")) + total_nano_aiu = from_float(obj.get("totalNanoAiu")) + agent_display_name = from_union([from_none, from_str], obj.get("agentDisplayName")) + agent_name = from_union([from_none, from_str], obj.get("agentName")) + return ShutdownAgentMetric( + model_metrics=model_metrics, + total_api_duration=total_api_duration, + total_nano_aiu=total_nano_aiu, + agent_display_name=agent_display_name, + agent_name=agent_name, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["modelMetrics"] = from_dict(lambda x: to_class(ShutdownModelMetric, x), self.model_metrics) + result["totalApiDurationMs"] = to_timedelta_int(self.total_api_duration) + result["totalNanoAiu"] = to_float(self.total_nano_aiu) + if self.agent_display_name is not None: + result["agentDisplayName"] = from_union([from_none, from_str], self.agent_display_name) + if self.agent_name is not None: + result["agentName"] = from_union([from_none, from_str], self.agent_name) + return result + + @dataclass class ShutdownCodeChanges: "Aggregate code change metrics for the session" @@ -7721,6 +9410,7 @@ class SkillInvokedData: path: str allowed_tools: list[str] | None = None description: str | None = None + disable_model_invocation: bool | None = None model: str | None = None plugin_name: str | None = None plugin_version: str | None = None @@ -7735,6 +9425,7 @@ def from_dict(obj: Any) -> "SkillInvokedData": path = from_str(obj.get("path")) allowed_tools = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("allowedTools")) description = from_union([from_none, from_str], obj.get("description")) + disable_model_invocation = from_union([from_none, from_bool], obj.get("disableModelInvocation")) model = from_union([from_none, from_str], obj.get("model")) plugin_name = from_union([from_none, from_str], obj.get("pluginName")) plugin_version = from_union([from_none, from_str], obj.get("pluginVersion")) @@ -7746,6 +9437,7 @@ def from_dict(obj: Any) -> "SkillInvokedData": path=path, allowed_tools=allowed_tools, description=description, + disable_model_invocation=disable_model_invocation, model=model, plugin_name=plugin_name, plugin_version=plugin_version, @@ -7762,6 +9454,8 @@ def to_dict(self) -> dict: result["allowedTools"] = from_union([from_none, lambda x: from_list(from_str, x)], self.allowed_tools) if self.description is not None: result["description"] = from_union([from_none, from_str], self.description) + if self.disable_model_invocation is not None: + result["disableModelInvocation"] = from_union([from_none, from_bool], self.disable_model_invocation) if self.model is not None: result["model"] = from_union([from_none, from_str], self.model) if self.plugin_name is not None: @@ -7784,6 +9478,7 @@ class SkillsLoadedSkill: source: SkillSource user_invocable: bool argument_hint: str | None = None + command_name: str | None = None path: str | None = None @staticmethod @@ -7795,6 +9490,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, @@ -7803,6 +9499,7 @@ def from_dict(obj: Any) -> "SkillsLoadedSkill": source=source, user_invocable=user_invocable, argument_hint=argument_hint, + command_name=command_name, path=path, ) @@ -7815,6 +9512,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 @@ -7826,8 +9525,15 @@ class SubagentCompletedData: agent_display_name: str agent_name: str tool_call_id: str + cancelled: bool | None = None + configured_model_matches_actual: bool | None = None + configured_model_preference: str | None = None duration: timedelta | None = None + explicit_model_matches_preference: bool | None = None + explicit_model_override: str | None = None + first_dispatched_model: str | None = None model: str | None = None + model_override_reason: str | None = None total_tokens: int | None = None total_tool_calls: int | None = None @@ -7837,16 +9543,30 @@ 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")) + configured_model_matches_actual = from_union([from_none, from_bool], obj.get("configuredModelMatchesActual")) + configured_model_preference = from_union([from_none, from_str], obj.get("configuredModelPreference")) duration = from_union([from_none, from_timedelta], obj.get("durationMs")) + explicit_model_matches_preference = from_union([from_none, from_bool], obj.get("explicitModelMatchesPreference")) + explicit_model_override = from_union([from_none, from_str], obj.get("explicitModelOverride")) + first_dispatched_model = from_union([from_none, from_str], obj.get("firstDispatchedModel")) model = from_union([from_none, from_str], obj.get("model")) + model_override_reason = from_union([from_none, from_str], obj.get("modelOverrideReason")) total_tokens = from_union([from_none, from_int], obj.get("totalTokens")) total_tool_calls = from_union([from_none, from_int], obj.get("totalToolCalls")) return SubagentCompletedData( agent_display_name=agent_display_name, agent_name=agent_name, tool_call_id=tool_call_id, + cancelled=cancelled, + configured_model_matches_actual=configured_model_matches_actual, + configured_model_preference=configured_model_preference, duration=duration, + explicit_model_matches_preference=explicit_model_matches_preference, + explicit_model_override=explicit_model_override, + first_dispatched_model=first_dispatched_model, model=model, + model_override_reason=model_override_reason, total_tokens=total_tokens, total_tool_calls=total_tool_calls, ) @@ -7856,10 +9576,24 @@ 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.configured_model_matches_actual is not None: + result["configuredModelMatchesActual"] = from_union([from_none, from_bool], self.configured_model_matches_actual) + if self.configured_model_preference is not None: + result["configuredModelPreference"] = from_union([from_none, from_str], self.configured_model_preference) if self.duration is not None: result["durationMs"] = from_union([from_none, to_timedelta_int], self.duration) + if self.explicit_model_matches_preference is not None: + result["explicitModelMatchesPreference"] = from_union([from_none, from_bool], self.explicit_model_matches_preference) + if self.explicit_model_override is not None: + result["explicitModelOverride"] = from_union([from_none, from_str], self.explicit_model_override) + if self.first_dispatched_model is not None: + result["firstDispatchedModel"] = from_union([from_none, from_str], self.first_dispatched_model) if self.model is not None: result["model"] = from_union([from_none, from_str], self.model) + if self.model_override_reason is not None: + result["modelOverrideReason"] = from_union([from_none, from_str], self.model_override_reason) if self.total_tokens is not None: result["totalTokens"] = from_union([from_none, to_int], self.total_tokens) if self.total_tool_calls is not None: @@ -7867,6 +9601,39 @@ def to_dict(self) -> dict: return result +@dataclass +class SubagentConfiguredData: + "Resolved runtime configuration for a configured sub-agent" + model: str + multi_turn: bool + context_tier: str | None = None + reasoning_effort: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "SubagentConfiguredData": + assert isinstance(obj, dict) + model = from_str(obj.get("model")) + multi_turn = from_bool(obj.get("multiTurn")) + context_tier = from_union([from_none, from_str], obj.get("contextTier")) + reasoning_effort = from_union([from_none, from_str], obj.get("reasoningEffort")) + return SubagentConfiguredData( + model=model, + multi_turn=multi_turn, + context_tier=context_tier, + reasoning_effort=reasoning_effort, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["model"] = from_str(self.model) + result["multiTurn"] = from_bool(self.multi_turn) + if self.context_tier is not None: + result["contextTier"] = from_union([from_none, from_str], self.context_tier) + if self.reasoning_effort is not None: + result["reasoningEffort"] = from_union([from_none, from_str], self.reasoning_effort) + return result + + @dataclass class SubagentDeselectedData: "Empty payload; the event signals that the custom agent was deselected, returning to the default agent" @@ -7886,8 +9653,14 @@ class SubagentFailedData: agent_name: str error: str tool_call_id: str + configured_model_matches_actual: bool | None = None + configured_model_preference: str | None = None duration: timedelta | None = None + explicit_model_matches_preference: bool | None = None + explicit_model_override: str | None = None + first_dispatched_model: str | None = None model: str | None = None + model_override_reason: str | None = None total_tokens: int | None = None total_tool_calls: int | None = None @@ -7898,8 +9671,14 @@ def from_dict(obj: Any) -> "SubagentFailedData": agent_name = from_str(obj.get("agentName")) error = from_str(obj.get("error")) tool_call_id = from_str(obj.get("toolCallId")) + configured_model_matches_actual = from_union([from_none, from_bool], obj.get("configuredModelMatchesActual")) + configured_model_preference = from_union([from_none, from_str], obj.get("configuredModelPreference")) duration = from_union([from_none, from_timedelta], obj.get("durationMs")) + explicit_model_matches_preference = from_union([from_none, from_bool], obj.get("explicitModelMatchesPreference")) + explicit_model_override = from_union([from_none, from_str], obj.get("explicitModelOverride")) + first_dispatched_model = from_union([from_none, from_str], obj.get("firstDispatchedModel")) model = from_union([from_none, from_str], obj.get("model")) + model_override_reason = from_union([from_none, from_str], obj.get("modelOverrideReason")) total_tokens = from_union([from_none, from_int], obj.get("totalTokens")) total_tool_calls = from_union([from_none, from_int], obj.get("totalToolCalls")) return SubagentFailedData( @@ -7907,8 +9686,14 @@ def from_dict(obj: Any) -> "SubagentFailedData": agent_name=agent_name, error=error, tool_call_id=tool_call_id, + configured_model_matches_actual=configured_model_matches_actual, + configured_model_preference=configured_model_preference, duration=duration, + explicit_model_matches_preference=explicit_model_matches_preference, + explicit_model_override=explicit_model_override, + first_dispatched_model=first_dispatched_model, model=model, + model_override_reason=model_override_reason, total_tokens=total_tokens, total_tool_calls=total_tool_calls, ) @@ -7919,10 +9704,22 @@ def to_dict(self) -> dict: result["agentName"] = from_str(self.agent_name) result["error"] = from_str(self.error) result["toolCallId"] = from_str(self.tool_call_id) + if self.configured_model_matches_actual is not None: + result["configuredModelMatchesActual"] = from_union([from_none, from_bool], self.configured_model_matches_actual) + if self.configured_model_preference is not None: + result["configuredModelPreference"] = from_union([from_none, from_str], self.configured_model_preference) if self.duration is not None: result["durationMs"] = from_union([from_none, to_timedelta_int], self.duration) + if self.explicit_model_matches_preference is not None: + result["explicitModelMatchesPreference"] = from_union([from_none, from_bool], self.explicit_model_matches_preference) + if self.explicit_model_override is not None: + result["explicitModelOverride"] = from_union([from_none, from_str], self.explicit_model_override) + if self.first_dispatched_model is not None: + result["firstDispatchedModel"] = from_union([from_none, from_str], self.first_dispatched_model) if self.model is not None: result["model"] = from_union([from_none, from_str], self.model) + if self.model_override_reason is not None: + result["modelOverrideReason"] = from_union([from_none, from_str], self.model_override_reason) if self.total_tokens is not None: result["totalTokens"] = from_union([from_none, to_int], self.total_tokens) if self.total_tool_calls is not None: @@ -7964,7 +9761,12 @@ class SubagentStartedData: agent_display_name: str agent_name: str tool_call_id: str + agent_type: str | None = None + execution_mode: str | None = None + factory_run_id: str | None = None model: str | None = None + parent_id: str | None = None + resumable: bool | None = None @staticmethod def from_dict(obj: Any) -> "SubagentStartedData": @@ -7973,13 +9775,23 @@ def from_dict(obj: Any) -> "SubagentStartedData": agent_display_name = from_str(obj.get("agentDisplayName")) agent_name = from_str(obj.get("agentName")) tool_call_id = from_str(obj.get("toolCallId")) + agent_type = from_union([from_none, from_str], obj.get("agentType")) + execution_mode = from_union([from_none, from_str], obj.get("executionMode")) + factory_run_id = from_union([from_none, from_str], obj.get("factoryRunId")) model = from_union([from_none, from_str], obj.get("model")) + parent_id = from_union([from_none, from_str], obj.get("parentId")) + resumable = from_union([from_none, from_bool], obj.get("resumable")) return SubagentStartedData( agent_description=agent_description, agent_display_name=agent_display_name, agent_name=agent_name, tool_call_id=tool_call_id, + agent_type=agent_type, + execution_mode=execution_mode, + factory_run_id=factory_run_id, model=model, + parent_id=parent_id, + resumable=resumable, ) def to_dict(self) -> dict: @@ -7988,8 +9800,18 @@ 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.agent_type is not None: + result["agentType"] = from_union([from_none, from_str], self.agent_type) + if self.execution_mode is not None: + result["executionMode"] = from_union([from_none, from_str], self.execution_mode) + if self.factory_run_id is not None: + result["factoryRunId"] = from_union([from_none, from_str], self.factory_run_id) if self.model is not None: result["model"] = from_union([from_none, from_str], self.model) + if self.parent_id is not None: + result["parentId"] = from_union([from_none, from_str], self.parent_id) + if self.resumable is not None: + result["resumable"] = from_union([from_none, from_bool], self.resumable) return result @@ -8064,6 +9886,7 @@ class SystemNotificationAgentCompleted: status: SystemNotificationAgentCompletedStatus type: ClassVar[str] = "agent_completed" description: str | None = None + display_name: str | None = None prompt: str | None = None @staticmethod @@ -8073,12 +9896,14 @@ def from_dict(obj: Any) -> "SystemNotificationAgentCompleted": agent_type = from_str(obj.get("agentType")) status = parse_enum(SystemNotificationAgentCompletedStatus, obj.get("status")) description = from_union([from_none, from_str], obj.get("description")) + display_name = from_union([from_none, from_str], obj.get("displayName")) prompt = from_union([from_none, from_str], obj.get("prompt")) return SystemNotificationAgentCompleted( agent_id=agent_id, agent_type=agent_type, status=status, description=description, + display_name=display_name, prompt=prompt, ) @@ -8090,6 +9915,8 @@ def to_dict(self) -> dict: result["type"] = self.type if self.description is not None: result["description"] = from_union([from_none, from_str], self.description) + if self.display_name is not None: + result["displayName"] = from_union([from_none, from_str], self.display_name) if self.prompt is not None: result["prompt"] = from_union([from_none, from_str], self.prompt) return result @@ -8102,6 +9929,7 @@ class SystemNotificationAgentIdle: agent_type: str type: ClassVar[str] = "agent_idle" description: str | None = None + display_name: str | None = None @staticmethod def from_dict(obj: Any) -> "SystemNotificationAgentIdle": @@ -8109,10 +9937,12 @@ def from_dict(obj: Any) -> "SystemNotificationAgentIdle": agent_id = from_str(obj.get("agentId")) agent_type = from_str(obj.get("agentType")) description = from_union([from_none, from_str], obj.get("description")) + display_name = from_union([from_none, from_str], obj.get("displayName")) return SystemNotificationAgentIdle( agent_id=agent_id, agent_type=agent_type, description=description, + display_name=display_name, ) def to_dict(self) -> dict: @@ -8122,6 +9952,8 @@ def to_dict(self) -> dict: result["type"] = self.type if self.description is not None: result["description"] = from_union([from_none, from_str], self.description) + if self.display_name is not None: + result["displayName"] = from_union([from_none, from_str], self.display_name) return result @@ -8516,6 +10348,7 @@ class ToolExecutionCompleteContentShellExit: shell_id: str type: ClassVar[str] = "shell_exit" cwd: str | None = None + output_file_path: str | None = None output_preview: str | None = None output_truncated: bool | None = None @@ -8525,12 +10358,14 @@ def from_dict(obj: Any) -> "ToolExecutionCompleteContentShellExit": exit_code = from_int(obj.get("exitCode")) shell_id = from_str(obj.get("shellId")) cwd = from_union([from_none, from_str], obj.get("cwd")) + output_file_path = from_union([from_none, from_str], obj.get("outputFilePath")) output_preview = from_union([from_none, from_str], obj.get("outputPreview")) output_truncated = from_union([from_none, from_bool], obj.get("outputTruncated")) return ToolExecutionCompleteContentShellExit( exit_code=exit_code, shell_id=shell_id, cwd=cwd, + output_file_path=output_file_path, output_preview=output_preview, output_truncated=output_truncated, ) @@ -8542,6 +10377,8 @@ def to_dict(self) -> dict: result["type"] = self.type if self.cwd is not None: result["cwd"] = from_union([from_none, from_str], self.cwd) + if self.output_file_path is not None: + result["outputFilePath"] = from_union([from_none, from_str], self.output_file_path) if self.output_preview is not None: result["outputPreview"] = from_union([from_none, from_str], self.output_preview) if self.output_truncated is not None: @@ -8576,6 +10413,8 @@ class ToolExecutionCompleteData: success: bool tool_call_id: str error: ToolExecutionCompleteError | None = None + # Experimental: this field is part of an experimental API and may change or be removed. + fusion: FusionAttribution | None = None interaction_id: str | None = None is_user_requested: bool | None = None # Experimental: this field is part of an experimental API and may change or be removed. @@ -8596,6 +10435,7 @@ def from_dict(obj: Any) -> "ToolExecutionCompleteData": success = from_bool(obj.get("success")) tool_call_id = from_str(obj.get("toolCallId")) error = from_union([from_none, ToolExecutionCompleteError.from_dict], obj.get("error")) + fusion = from_union([from_none, FusionAttribution.from_dict], obj.get("fusion")) interaction_id = from_union([from_none, from_str], obj.get("interactionId")) is_user_requested = from_union([from_none, from_bool], obj.get("isUserRequested")) mcp_meta = obj.get("mcpMeta") @@ -8611,6 +10451,7 @@ def from_dict(obj: Any) -> "ToolExecutionCompleteData": success=success, tool_call_id=tool_call_id, error=error, + fusion=fusion, interaction_id=interaction_id, is_user_requested=is_user_requested, mcp_meta=mcp_meta, @@ -8630,6 +10471,8 @@ def to_dict(self) -> dict: result["toolCallId"] = from_str(self.tool_call_id) if self.error is not None: result["error"] = from_union([from_none, lambda x: to_class(ToolExecutionCompleteError, x)], self.error) + if self.fusion is not None: + result["fusion"] = from_union([from_none, lambda x: to_class(FusionAttribution, x)], self.fusion) if self.interaction_id is not None: result["interactionId"] = from_union([from_none, from_str], self.interaction_id) if self.is_user_requested is not None: @@ -9074,6 +10917,8 @@ class ToolExecutionStartData: tool_name: str arguments: Any = None display_verbatim: bool | None = None + # Experimental: this field is part of an experimental API and may change or be removed. + fusion: FusionAttribution | None = None mcp_server_name: str | None = None mcp_tool_name: str | None = None model: str | None = None @@ -9091,6 +10936,7 @@ def from_dict(obj: Any) -> "ToolExecutionStartData": tool_name = from_str(obj.get("toolName")) arguments = obj.get("arguments") display_verbatim = from_union([from_none, from_bool], obj.get("displayVerbatim")) + fusion = from_union([from_none, FusionAttribution.from_dict], obj.get("fusion")) mcp_server_name = from_union([from_none, from_str], obj.get("mcpServerName")) mcp_tool_name = from_union([from_none, from_str], obj.get("mcpToolName")) model = from_union([from_none, from_str], obj.get("model")) @@ -9104,6 +10950,7 @@ def from_dict(obj: Any) -> "ToolExecutionStartData": tool_name=tool_name, arguments=arguments, display_verbatim=display_verbatim, + fusion=fusion, mcp_server_name=mcp_server_name, mcp_tool_name=mcp_tool_name, model=model, @@ -9122,6 +10969,8 @@ def to_dict(self) -> dict: result["arguments"] = self.arguments if self.display_verbatim is not None: result["displayVerbatim"] = from_union([from_none, from_bool], self.display_verbatim) + if self.fusion is not None: + result["fusion"] = from_union([from_none, lambda x: to_class(FusionAttribution, x)], self.fusion) if self.mcp_server_name is not None: result["mcpServerName"] = from_union([from_none, from_str], self.mcp_server_name) if self.mcp_tool_name is not None: @@ -9399,11 +11248,13 @@ class UserMessageData: delivery: UserMessageDelivery | None = None interaction_id: str | None = None is_autopilot_continuation: bool | None = None + message_id: str | None = None native_document_path_fallback_paths: list[str] | None = None parent_agent_task_id: str | None = None source: str | None = None supported_native_document_mime_types: list[str] | None = None transformed_content: str | None = None + turn_id: str | None = None @staticmethod def from_dict(obj: Any) -> "UserMessageData": @@ -9414,11 +11265,13 @@ def from_dict(obj: Any) -> "UserMessageData": delivery = from_union([from_none, lambda x: parse_enum(UserMessageDelivery, x)], obj.get("delivery")) interaction_id = from_union([from_none, from_str], obj.get("interactionId")) is_autopilot_continuation = from_union([from_none, from_bool], obj.get("isAutopilotContinuation")) + message_id = from_union([from_none, from_str], obj.get("messageId")) native_document_path_fallback_paths = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("nativeDocumentPathFallbackPaths")) parent_agent_task_id = from_union([from_none, from_str], obj.get("parentAgentTaskId")) source = from_union([from_none, from_str], obj.get("source")) supported_native_document_mime_types = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("supportedNativeDocumentMimeTypes")) transformed_content = from_union([from_none, from_str], obj.get("transformedContent")) + turn_id = from_union([from_none, from_str], obj.get("turnId")) return UserMessageData( content=content, agent_mode=agent_mode, @@ -9426,11 +11279,13 @@ def from_dict(obj: Any) -> "UserMessageData": delivery=delivery, interaction_id=interaction_id, is_autopilot_continuation=is_autopilot_continuation, + message_id=message_id, native_document_path_fallback_paths=native_document_path_fallback_paths, parent_agent_task_id=parent_agent_task_id, source=source, supported_native_document_mime_types=supported_native_document_mime_types, transformed_content=transformed_content, + turn_id=turn_id, ) def to_dict(self) -> dict: @@ -9446,6 +11301,8 @@ def to_dict(self) -> dict: result["interactionId"] = from_union([from_none, from_str], self.interaction_id) if self.is_autopilot_continuation is not None: result["isAutopilotContinuation"] = from_union([from_none, from_bool], self.is_autopilot_continuation) + if self.message_id is not None: + result["messageId"] = from_union([from_none, from_str], self.message_id) if self.native_document_path_fallback_paths is not None: result["nativeDocumentPathFallbackPaths"] = from_union([from_none, lambda x: from_list(from_str, x)], self.native_document_path_fallback_paths) if self.parent_agent_task_id is not None: @@ -9456,6 +11313,8 @@ def to_dict(self) -> dict: result["supportedNativeDocumentMimeTypes"] = from_union([from_none, lambda x: from_list(from_str, x)], self.supported_native_document_mime_types) if self.transformed_content is not None: result["transformedContent"] = from_union([from_none, from_str], self.transformed_content) + if self.turn_id is not None: + result["turnId"] = from_union([from_none, from_str], self.turn_id) return result @@ -9501,6 +11360,31 @@ def to_dict(self) -> dict: return result +@dataclass +class UserToolSessionApprovalExtensionEnvAccess: + "Session-scoped tool-approval rule for an extension's access to sensitive environment variables, keyed by extension name and the exact set of variable names." + environment_variables: list[str] + extension_name: str + kind: ClassVar[str] = "extension-env-access" + + @staticmethod + def from_dict(obj: Any) -> "UserToolSessionApprovalExtensionEnvAccess": + assert isinstance(obj, dict) + environment_variables = from_list(from_str, obj.get("environmentVariables")) + extension_name = from_str(obj.get("extensionName")) + return UserToolSessionApprovalExtensionEnvAccess( + environment_variables=environment_variables, + extension_name=extension_name, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["environmentVariables"] = from_list(from_str, self.environment_variables) + result["extensionName"] = from_str(self.extension_name) + result["kind"] = self.kind + return result + + @dataclass class UserToolSessionApprovalExtensionManagement: "Session-scoped tool-approval rule for extension-management operations, optionally narrowed by operation." @@ -9749,6 +11633,7 @@ def _load_PermissionPromptRequest(obj: Any) -> "PermissionPromptRequest": 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 "extension-env-access": return PermissionPromptRequestExtensionEnvAccess.from_dict(obj) case _: raise ValueError(f"Unknown PermissionPromptRequest kind: {kind!r}") @@ -9767,6 +11652,7 @@ def _load_PermissionRequest(obj: Any) -> "PermissionRequest": 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 "extension-env-access": return PermissionRequestExtensionEnvAccess.from_dict(obj) case _: raise ValueError(f"Unknown PermissionRequest kind: {kind!r}") @@ -9828,6 +11714,7 @@ def _load_UserToolSessionApproval(obj: Any) -> "UserToolSessionApproval": 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 "extension-env-access": return UserToolSessionApprovalExtensionEnvAccess.from_dict(obj) case _: raise ValueError(f"Unknown UserToolSessionApproval kind: {kind!r}") @@ -9844,11 +11731,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 | PermissionPromptRequestFactory | PermissionPromptRequestExtensionPermissionAccess +PermissionPromptRequest = PermissionPromptRequestCommands | PermissionPromptRequestWrite | PermissionPromptRequestRead | PermissionPromptRequestMcp | PermissionPromptRequestUrl | PermissionPromptRequestMemory | PermissionPromptRequestCustomTool | PermissionPromptRequestPath | PermissionPromptRequestHook | PermissionPromptRequestExtensionManagement | PermissionPromptRequestFactory | PermissionPromptRequestExtensionPermissionAccess | PermissionPromptRequestExtensionEnvAccess # Details of the permission being requested -PermissionRequest = PermissionRequestShell | PermissionRequestWrite | PermissionRequestRead | PermissionRequestMcp | PermissionRequestUrl | PermissionRequestMemory | PermissionRequestCustomTool | PermissionRequestHook | PermissionRequestExtensionManagement | PermissionRequestFactory | PermissionRequestExtensionPermissionAccess +PermissionRequest = PermissionRequestShell | PermissionRequestWrite | PermissionRequestRead | PermissionRequestMcp | PermissionRequestUrl | PermissionRequestMemory | PermissionRequestCustomTool | PermissionRequestHook | PermissionRequestExtensionManagement | PermissionRequestFactory | PermissionRequestExtensionPermissionAccess | PermissionRequestExtensionEnvAccess # Location within a cited source (character, page, or content-block range) that supports a span. @@ -9860,7 +11747,7 @@ def _load_UserToolSessionApproval(obj: Any) -> "UserToolSessionApproval": # The approval to add as a session-scoped rule -UserToolSessionApproval = UserToolSessionApprovalCommands | UserToolSessionApprovalRead | UserToolSessionApprovalWrite | UserToolSessionApprovalMcp | UserToolSessionApprovalMemory | UserToolSessionApprovalCustomTool | UserToolSessionApprovalExtensionManagement | UserToolSessionApprovalFactory | UserToolSessionApprovalExtensionPermissionAccess +UserToolSessionApproval = UserToolSessionApprovalCommands | UserToolSessionApprovalRead | UserToolSessionApprovalWrite | UserToolSessionApprovalMcp | UserToolSessionApprovalMemory | UserToolSessionApprovalCustomTool | UserToolSessionApprovalExtensionManagement | UserToolSessionApprovalFactory | UserToolSessionApprovalExtensionPermissionAccess | UserToolSessionApprovalExtensionEnvAccess # The embedded resource contents, either text or base64-encoded binary @@ -9872,8 +11759,8 @@ def _load_UserToolSessionApproval(obj: Any) -> "UserToolSessionApproval": # 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." +class AssistedApprovalJudgeFailureReason(Enum): + "Why the assisted-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. @@ -9887,13 +11774,13 @@ class AutoApprovalJudgeFailureReason(Enum): # 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)." +class AssistedApprovalRecommendation(Enum): + "Outcome of the assisted-approval safety judge for a permission request. Present only in assisted mode; its absence means the judge did not evaluate the request." # The judge evaluated the request and recommends automatically approving it. APPROVE = "approve" - # The judge evaluated the request and does not recommend auto-approving it; explicit approval is required. Whether that means prompting, denying, or something else is the consumer's decision. + # The judge evaluated the request and does not recommend automatically approving it; explicit approval is required. Whether that means prompting, denying, or something else is the consumer's decision. REQUIRE_APPROVAL = "requireApproval" - # Auto mode is enabled, but this request category is never auto-approvable (for example, sandbox-bypass requests), so the judge was not consulted. + # Assisted mode is enabled, but this request category is never automatically approvable (for example, sandbox-bypass requests), so the judge was not consulted. EXCLUDED = "excluded" # The judge was consulted but did not return a usable recommendation, so the request requires explicit approval. ERROR = "error" @@ -9911,14 +11798,126 @@ class CitationProvider(Enum): # Experimental: this enum is part of an experimental API and may change or be removed. -class PermissionAllowAllMode(Enum): - "Allow-all mode for the session." +class FusionConversationScope(Enum): + "Conversation scope in which a HydraFusion phase executes." + # Canonical root conversation history. + ROOT = "root" + # Isolated read-only review history that does not enter the root conversation. + REVIEW = "review" + + +# Experimental: this enum is part of an experimental API and may change or be removed. +class FusionFollowUpAction(Enum): + "Server-recommended routing behavior for a later HydraFusion turn." + # Reuse the durable primary model without routing. + REUSE_PRIMARY = "reuse_primary" + # Request a new routing decision. + REROUTE = "reroute" + + +# Experimental: this enum is part of an experimental API and may change or be removed. +class FusionPattern(Enum): + "Validated HydraFusion execution pattern." + # Run one primary solver phase. + SINGLE = "single" + # Run a primary phase, a judge, and an optional repair. + CASCADE = "cascade" + # Run a primary draft, a read-only critique, and a revision. + CRITIQUE = "critique" + + +# Experimental: this enum is part of an experimental API and may change or be removed. +class FusionPhaseActivityKind(Enum): + "Content-safe activity observed while a HydraFusion phase is running." + # The provider produced additional private output bytes. + MODEL_OUTPUT = "model_output" + # A tool began executing inside the phase. + TOOL_STARTED = "tool_started" + # A tool finished executing inside the phase. + TOOL_COMPLETED = "tool_completed" + + +# Experimental: this enum is part of an experimental API and may change or be removed. +class FusionPhaseKind(Enum): + "HydraFusion phase kind." + # Primary solver phase. + PRIMARY = "primary" + # Read-only cascade judge phase. + JUDGE = "judge" + # Cascade repair phase. + REPAIR = "repair" + # Initial critique-pattern draft phase. + DRAFT = "draft" + # Read-only critique phase. + CRITIC = "critic" + # Critique-pattern revision phase. + REVISION = "revision" + # Follow-up phase continuing from the resolved model. + FOLLOW_UP = "follow_up" + + +# Experimental: this enum is part of an experimental API and may change or be removed. +class FusionPhaseStatus(Enum): + "Durable outcome status of a HydraFusion phase." + # The phase completed successfully. + SUCCEEDED = "succeeded" + # The phase failed. + FAILED = "failed" + # The phase was cancelled. + CANCELLED = "cancelled" + + +# Experimental: this enum is part of an experimental API and may change or be removed. +class _FusionProjectionMode(Enum): + "How a durable phase checkpoint contributes its exact message to canonical root history." + # Append the exact root message immediately. + APPEND = "append" + # Hold a terminal message outside canonical history until the final commit selects it. + STAGED = "staged" + # Do not project the checkpoint into root history. + NONE = "none" + + +# Experimental: this enum is part of an experimental API and may change or be removed. +class FusionTurnKind(Enum): + "Kind of turn for which HydraFusion routing is running." + # A user-message turn. + USER = "user" + # A conversation-compaction turn. + COMPACTION = "compaction" + + +# Experimental: this enum is part of an experimental API and may change or be removed. +class PermissionMode(Enum): + "Permission mode for the session." # Permission requests follow the normal approval flow. - OFF = "off" + MANUAL = "manual" + # Permission requests include an LLM safety recommendation; clients may automatically approve requests judged acceptable. + ASSISTED = "assisted" # Tool, path, and URL permission requests are automatically approved. - ON = "on" - # 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. - AUTO = "auto" + ALLOW_ALL = "allow-all" + + +# Experimental: this enum is part of an experimental API and may change or be removed. +class PermissionRecommendation(Enum): + "Advisory recommendation the runtime attaches to a permission request whose origin it can vouch for by construction. Unlike the auto-approval judge this does not depend on auto mode and does not evaluate what the tool call does; its absence simply means the runtime has no opinion and the request follows the host's normal approval flow." + # The runtime vouches for the request's origin and recommends approving it without prompting. The host still owns the decision and may deny it; deny rules, managed policy, and the auto-approval safety judge all outrank this recommendation. + APPROVE = "approve" + + +# Experimental: this enum is part of an experimental API and may change or be removed. +class UIEphemeralQueryPhase(Enum): + "Lifecycle phase for a Rust-owned ephemeral query stream." + # The ephemeral query stream has begun. + STARTED = "started" + # A partial result chunk was produced by the stream. + CHUNK = "chunk" + # The ephemeral query stream finished successfully. + COMPLETED = "completed" + # The ephemeral query stream ended with an error. + FAILED = "failed" + # The ephemeral query stream was cancelled before completing. + ABORTED = "aborted" class AbortReason(Enum): @@ -9933,6 +11932,39 @@ class AbortReason(Enum): AUTOPILOT_CREDIT_LIMIT = "autopilot_credit_limit" +class AgentInterruptedActivity(Enum): + "What the agent was doing when the user interrupted it." + # A request to the model was open. + MODEL_CALL = "model_call" + # The turn was sleeping between retry attempts. + RETRY_BACKOFF = "retry_backoff" + # One or more tools were executing. + TOOL_CALL = "tool_call" + # Background sub-agents were running while the main loop was idle. + BACKGROUND_AGENT = "background_agent" + + +class AgentInterruptedCancelPhase(Enum): + "Where the interruption landed relative to the first streamed token." + # No output had been produced when the request was cancelled. + PRE_FIRST_TOKEN = "pre_first_token" + # The response was already streaming when the request was cancelled. + MID_STREAM = "mid_stream" + + +class AgentModelPolicy(Enum): + "Whether configured models are advisory preferences or required constraints" + # Treat the authored models as advisory preferences that callers may override. + PREFERRED = "preferred" + # Require subagent execution to use one of the authored models. + REQUIRED = "required" + + +class AssistantMessageToolRequestCallerType(Enum): + "Hosted program caller type" + PROGRAM = "program" + + class AssistantMessageToolRequestType(Enum): "Tool call type: \"function\" for standard tool calls, \"custom\" for grammar-based tool calls. Defaults to \"function\" when absent." # Standard function-style tool call. @@ -9953,6 +11985,14 @@ class AssistantUsageApiEndpoint(Enum): WS_RESPONSES = "ws:/responses" +class AssistantUsageTransport(Enum): + "Transport used for a successful model call" + # HTTP transport, including SSE streams. + HTTP = "http" + # WebSocket transport. + WEBSOCKET = "websocket" + + class AttachmentGitHubReferenceType(Enum): "Type of GitHub reference" # GitHub issue reference. @@ -9983,6 +12023,16 @@ class AutoModeSwitchResponse(Enum): NO = "no" +class AutoTier(Enum): + "Routing preference used when the session model is `auto`." + # Optimize for efficiency. + EFFICIENCY = "efficiency" + # Balance efficiency and intelligence. + BALANCE = "balance" + # Optimize for intelligence. + INTELLIGENCE = "intelligence" + + class AutopilotObjectiveChangedOperation(Enum): "The type of operation performed on the autopilot objective state file" # Autopilot objective state file was created for a new objective. @@ -10035,6 +12085,30 @@ class CompactionTrigger(Enum): MODEL_SWITCH = "model_switch" +class CompletionReceiptStopReason(Enum): + "Runtime reason the completion decision was accepted." + # The model reached a natural terminal response. + NATURAL = "natural" + # A terminal tool ended the interaction. + TERMINAL_TOOL = "terminal_tool" + # The configured agentStop continuation limit was reached. + AGENT_STOP_BLOCK_LIMIT = "agent_stop_block_limit" + + +class CompletionReceiptToolStatus(Enum): + "Structured terminal status from a tool completion event." + # The tool completed successfully. + SUCCESS = "success" + # The tool failed without a more specific structured status. + FAILURE = "failure" + # The tool exceeded its time budget. + TIMEOUT = "timeout" + # The user rejected the tool call. + REJECTED = "rejected" + # The permissions service denied the tool call. + DENIED = "denied" + + class ContextTier(Enum): "Allowed values for the `ContextTier` enumeration." # Default context tier with standard context window size. @@ -10105,6 +12179,18 @@ class FactoryPermissionOperation(Enum): AUTHOR = "author" +class FactoryRunSettledStatus(Enum): + "Terminal status a factory run committed. A settled run is never `pending` or `running`, so those two members of the run-status domain are deliberately absent." + # The factory body resolved and its result was committed. + COMPLETED = "completed" + # The run was stopped by a limit, an approval refusal or another policy decision. + HALTED = "halted" + # The run was cancelled by its caller or by session disposal. + CANCELLED = "cancelled" + # The run failed, with `failureType` carrying the class when it has one. + ERROR = "error" + + class HandoffSourceType(Enum): "Origin type of the session being handed off" # The handoff originated from a remote session. @@ -10121,25 +12207,33 @@ class ManagedSettingsEnforcedAction(Enum): class ManagedSettingsEnforcedEscalation(Enum): "For a `bypass_permissions_blocked` action, which permission-escalation primitive was refused" - # Full allow-all ("/allow-all on") permissions — auto-approving tools, paths, and URLs. + # Full allow-all permissions — automatically approving tools, paths, and URLs. ALLOW_ALL = "allow_all" - # Auto-approval of all tool permission requests. + # Automatic approval of all tool permission requests. APPROVE_ALL = "approve_all" - # Advisory auto-approval ("/allow-all auto") mode — keeps normal prompt paths and adds LLM-advised approval, distinct from full allow-all. - AUTO_APPROVAL = "auto_approval" + # Assisted mode — keeps normal prompt paths and adds an LLM recommendation, distinct from allow-all. + ASSISTED_APPROVAL = "assisted_approval" # Unrestricted filesystem access outside the session's allowed directories. UNRESTRICTED_PATHS = "unrestricted_paths" # Unrestricted URL fetch access. UNRESTRICTED_URLS = "unrestricted_urls" + # A server-wide MCP "Always Allow" (or `--allow-tool `) blanket that would auto-approve every tool from an MCP server. Capped to per-tool approval; each tool still prompts. + SERVER_WIDE_MCP_APPROVAL = "server_wide_mcp_approval" 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" + # A policy helper registered by device or server policy contributed. Device registration takes priority when present. + POLICY_HELPER = "policyHelper" + # More than one channel contributed. Ordinary keys resolve device over server over policy helper per key, while permissions compose restrictively across all present layers. + MIXED = "mixed" + # No managed policy is in force (no channel contributed). NONE = "none" @@ -10259,6 +12353,44 @@ class ModelCallFailureTransport(Enum): WEBSOCKET = "websocket" +class ModelCallFinishedOutcome(Enum): + "Final outcome of one logical model dispatch after response acceptance processing" + # The provider response was accepted for continued agent processing. + SUCCESS = "success" + # The dispatch ended with a provider or transport error. + ERROR = "error" + # The dispatch was cancelled before an accepted response was produced. + CANCELLED = "cancelled" + # The provider response was rejected during post-response acceptance processing. + REJECTED = "rejected" + + +class ModelChangeSource(Enum): + "Origin of an effective session model change." + # The user selected a model directly with `/model `. + MODEL_COMMAND = "model_command" + # The user selected the model with `/settings`. + SETTINGS_COMMAND = "settings_command" + # The user selected the model with the `/config` alias. + CONFIG_COMMAND = "config_command" + # The user selected the model in the model picker, including the picker opened by bare `/model`. + MODEL_PICKER = "model_picker" + # Organization-managed settings selected the model. + MANAGED_SETTINGS = "managed_settings" + # Repository settings selected the model. + REPO_SETTINGS = "repo_settings" + # Startup model resolution selected the model. + STARTUP = "startup" + # Selecting an agent selected its configured model. + AGENT = "agent" + # Entering, leaving, or reconfiguring plan mode selected the model. + PLAN_MODE = "plan_mode" + # The runtime selected the model automatically, such as rate-limit recovery or refusal fallback. + AUTOMATIC = "automatic" + # An SDK or RPC caller selected the model. + SDK = "sdk" + + class OmittedBinaryOmittedReason(Enum): "Why the binary data is absent: it exceeded the inline size limit, or its asset was unavailable" # Bytes exceeded the session's inline size limit. @@ -10301,6 +12433,14 @@ class PermissionRequestMemoryDirection(Enum): DOWNVOTE = "downvote" +class PermissionRequestMemoryScope(Enum): + "Scope of a stored memory." + # Store the memory for the current repository. + REPOSITORY = "repository" + # Store the memory for the current user. + USER = "user" + + class PersistedBinaryImageType(Enum): "Binary result type discriminator. Use \"image\" for images and \"resource\" for other binary data." # Binary image data. @@ -10378,7 +12518,7 @@ class SkillInvokedTrigger(Enum): class SkillSource(Enum): - "Source location type (e.g., project, personal-copilot, plugin, builtin)" + "Source location type (e.g., project, personal-copilot, plugin, builtin, sdk)" # Skill defined in the current project's skill directories. PROJECT = "project" # Skill discovered from a parent directory in the current workspace tree. @@ -10393,6 +12533,8 @@ class SkillSource(Enum): CUSTOM = "custom" # Skill bundled with the runtime. BUILTIN = "builtin" + # Pathless skill supplied lazily by an SDK skill provider. + SDK = "sdk" class SystemMessageRole(Enum): @@ -10505,7 +12647,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 | 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 +SessionEventData = SessionStartData | SessionResumeData | SessionRemoteSteerableChangedData | SessionErrorData | SessionIdleData | SessionTitleChangedData | SessionScheduleCreatedData | SessionScheduleCancelledData | SessionScheduleRearmedData | SessionAutopilotObjectiveChangedData | SessionInfoData | SessionWarningData | SessionModelChangeData | SessionModeChangedData | SessionModeNoticeDeliveredData | SessionSessionLimitsChangedData | SessionPermissionsChangedData | SessionPlanChangedData | SessionTodosChangedData | SessionWorkspaceFileChangedData | SessionHandoffData | SessionTruncationData | SessionSnapshotRewindData | SessionShutdownData | SessionUsageCheckpointData | SessionContextChangedData | SessionUsageInfoData | SessionContextClearedData | SessionCompactionStartData | SessionCompactionCompleteData | SessionTaskCompleteData | SessionCompletionReceiptData | SessionFusionRouteStartedData | SessionFusionRouteFailedData | SessionFusionResolvedData | SessionFusionCompletedData | UserMessageData | PendingMessagesModifiedData | AssistantTurnStartData | AssistantTurnRetryData | AgentInterruptedData | AssistantIntentData | AssistantFusionPhaseStartedData | AssistantFusionPhaseActivityData | AssistantFusionPhaseCompletedData | AssistantFusionPhaseFailedData | AssistantServerToolProgressData | AssistantReasoningData | AssistantReasoningDeltaData | AssistantToolCallDeltaData | AssistantStreamingDeltaData | AssistantMessageData | AssistantMessageStartData | AssistantMessageDeltaData | AssistantTurnEndData | AssistantIdleData | AssistantUsageData | PromptCacheBreakData | ModelCallFailureData | ModelCallFinishedData | ModelCallStartData | AbortData | ToolUserRequestedData | ToolExecutionStartData | ToolExecutionPartialResultData | ToolExecutionProgressData | ToolExecutionCompleteData | ToolSearchActivatedData | SkillInvokedData | SandboxDecisionData | SubagentStartedData | SubagentConfiguredData | SubagentCompletedData | SubagentFailedData | SubagentSelectedData | SubagentDeselectedData | HookStartData | HookEndData | HookProgressData | SessionBinaryAssetData | SystemMessageData | SystemNotificationData | PermissionRequestedData | PermissionCompletedData | UserInputRequestedData | UserInputCompletedData | ElicitationRequestedData | ElicitationCompletedData | SamplingRequestedData | SamplingCompletedData | McpOauthRequiredData | McpOauthCompletedData | McpHeadersRefreshRequiredData | McpHeadersRefreshCompletedData | SessionCustomNotificationData | UiEphemeralQueryData | ExternalToolRequestedData | ExternalToolCompletedData | CommandQueuedData | CommandExecuteData | CommandCompletedData | AutoModeSwitchRequestedData | AutoModeSwitchCompletedData | SessionLimitsExhaustedRequestedData | SessionLimitsExhaustedCompletedData | SessionAutoModeResolvedData | SessionManagedSettingsResolvedData | SessionManagedSettingsEnforcedData | CommandsChangedData | CapabilitiesChangedData | ExitPlanModeRequestedData | ExitPlanModeCompletedData | SessionToolsUpdatedData | SessionBackgroundTasksChangedData | FactoryRunUpdatedData | FactoryRunStartedData | FactoryRunSettledData | SessionSkillsLoadedData | SessionCustomAgentsUpdatedData | SessionMcpServersLoadedData | SessionMcpServerStatusChangedData | McpToolsListChangedData | McpResourcesListChangedData | McpPromptsListChangedData | SessionExtensionsLoadedData | SessionCanvasOpenedData | SessionCanvasRegistryChangedData | SessionCanvasClosedData | SessionCanvasUnavailableData | SessionCanvasRecordedData | SessionCanvasRemovedData | SessionExtensionsAttachmentsPushedData | McpAppToolCallCompleteData | RawSessionEventData | Data @dataclass @@ -10545,6 +12687,7 @@ def from_dict(obj: Any) -> "SessionEvent": case SessionEventType.SESSION_WARNING: data = SessionWarningData.from_dict(data_obj) case SessionEventType.SESSION_MODEL_CHANGE: data = SessionModelChangeData.from_dict(data_obj) case SessionEventType.SESSION_MODE_CHANGED: data = SessionModeChangedData.from_dict(data_obj) + case SessionEventType.SESSION_MODE_NOTICE_DELIVERED: data = SessionModeNoticeDeliveredData.from_dict(data_obj) case SessionEventType.SESSION_SESSION_LIMITS_CHANGED: data = SessionSessionLimitsChangedData.from_dict(data_obj) case SessionEventType.SESSION_PERMISSIONS_CHANGED: data = SessionPermissionsChangedData.from_dict(data_obj) case SessionEventType.SESSION_PLAN_CHANGED: data = SessionPlanChangedData.from_dict(data_obj) @@ -10561,11 +12704,21 @@ def from_dict(obj: Any) -> "SessionEvent": 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) + case SessionEventType.SESSION_COMPLETION_RECEIPT: data = SessionCompletionReceiptData.from_dict(data_obj) + case SessionEventType.SESSION_FUSION_ROUTE_STARTED: data = SessionFusionRouteStartedData.from_dict(data_obj) + case SessionEventType.SESSION_FUSION_ROUTE_FAILED: data = SessionFusionRouteFailedData.from_dict(data_obj) + case SessionEventType.SESSION_FUSION_RESOLVED: data = SessionFusionResolvedData.from_dict(data_obj) + case SessionEventType.SESSION_FUSION_COMPLETED: data = SessionFusionCompletedData.from_dict(data_obj) case SessionEventType.USER_MESSAGE: data = UserMessageData.from_dict(data_obj) case SessionEventType.PENDING_MESSAGES_MODIFIED: data = PendingMessagesModifiedData.from_dict(data_obj) case SessionEventType.ASSISTANT_TURN_START: data = AssistantTurnStartData.from_dict(data_obj) case SessionEventType.ASSISTANT_TURN_RETRY: data = AssistantTurnRetryData.from_dict(data_obj) + case SessionEventType.AGENT_INTERRUPTED: data = AgentInterruptedData.from_dict(data_obj) case SessionEventType.ASSISTANT_INTENT: data = AssistantIntentData.from_dict(data_obj) + case SessionEventType.ASSISTANT_FUSION_PHASE_STARTED: data = AssistantFusionPhaseStartedData.from_dict(data_obj) + case SessionEventType.ASSISTANT_FUSION_PHASE_ACTIVITY: data = AssistantFusionPhaseActivityData.from_dict(data_obj) + case SessionEventType.ASSISTANT_FUSION_PHASE_COMPLETED: data = AssistantFusionPhaseCompletedData.from_dict(data_obj) + case SessionEventType.ASSISTANT_FUSION_PHASE_FAILED: data = AssistantFusionPhaseFailedData.from_dict(data_obj) case SessionEventType.ASSISTANT_SERVER_TOOL_PROGRESS: data = AssistantServerToolProgressData.from_dict(data_obj) case SessionEventType.ASSISTANT_REASONING: data = AssistantReasoningData.from_dict(data_obj) case SessionEventType.ASSISTANT_REASONING_DELTA: data = AssistantReasoningDeltaData.from_dict(data_obj) @@ -10577,7 +12730,9 @@ def from_dict(obj: Any) -> "SessionEvent": case SessionEventType.ASSISTANT_TURN_END: data = AssistantTurnEndData.from_dict(data_obj) case SessionEventType.ASSISTANT_IDLE: data = AssistantIdleData.from_dict(data_obj) case SessionEventType.ASSISTANT_USAGE: data = AssistantUsageData.from_dict(data_obj) + case SessionEventType.PROMPT_CACHE_BREAK: data = PromptCacheBreakData.from_dict(data_obj) case SessionEventType.MODEL_CALL_FAILURE: data = ModelCallFailureData.from_dict(data_obj) + case SessionEventType.MODEL_CALL_FINISHED: data = ModelCallFinishedData.from_dict(data_obj) case SessionEventType.MODEL_CALL_START: data = ModelCallStartData.from_dict(data_obj) case SessionEventType.ABORT: data = AbortData.from_dict(data_obj) case SessionEventType.TOOL_USER_REQUESTED: data = ToolUserRequestedData.from_dict(data_obj) @@ -10587,7 +12742,9 @@ def from_dict(obj: Any) -> "SessionEvent": case SessionEventType.TOOL_EXECUTION_COMPLETE: data = ToolExecutionCompleteData.from_dict(data_obj) case SessionEventType.TOOL_SEARCH_ACTIVATED: data = ToolSearchActivatedData.from_dict(data_obj) case SessionEventType.SKILL_INVOKED: data = SkillInvokedData.from_dict(data_obj) + case SessionEventType.SANDBOX_DECISION: data = SandboxDecisionData.from_dict(data_obj) case SessionEventType.SUBAGENT_STARTED: data = SubagentStartedData.from_dict(data_obj) + case SessionEventType.SUBAGENT_CONFIGURED: data = SubagentConfiguredData.from_dict(data_obj) case SessionEventType.SUBAGENT_COMPLETED: data = SubagentCompletedData.from_dict(data_obj) case SessionEventType.SUBAGENT_FAILED: data = SubagentFailedData.from_dict(data_obj) case SessionEventType.SUBAGENT_SELECTED: data = SubagentSelectedData.from_dict(data_obj) @@ -10611,6 +12768,7 @@ def from_dict(obj: Any) -> "SessionEvent": case SessionEventType.MCP_HEADERS_REFRESH_REQUIRED: data = McpHeadersRefreshRequiredData.from_dict(data_obj) case SessionEventType.MCP_HEADERS_REFRESH_COMPLETED: data = McpHeadersRefreshCompletedData.from_dict(data_obj) case SessionEventType.SESSION_CUSTOM_NOTIFICATION: data = SessionCustomNotificationData.from_dict(data_obj) + case SessionEventType.UI_EPHEMERAL_QUERY: data = UiEphemeralQueryData.from_dict(data_obj) case SessionEventType.EXTERNAL_TOOL_REQUESTED: data = ExternalToolRequestedData.from_dict(data_obj) case SessionEventType.EXTERNAL_TOOL_COMPLETED: data = ExternalToolCompletedData.from_dict(data_obj) case SessionEventType.COMMAND_QUEUED: data = CommandQueuedData.from_dict(data_obj) @@ -10630,6 +12788,8 @@ def from_dict(obj: Any) -> "SessionEvent": 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.FACTORY_RUN_STARTED: data = FactoryRunStartedData.from_dict(data_obj) + case SessionEventType.FACTORY_RUN_SETTLED: data = FactoryRunSettledData.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) @@ -10682,13 +12842,24 @@ def session_event_to_dict(x: SessionEvent) -> Any: __all__ = [ "AbortData", "AbortReason", + "AgentInterruptedActivity", + "AgentInterruptedCancelPhase", + "AgentInterruptedData", + "AgentModelPolicy", + "AssistantFusionPhaseActivityData", + "AssistantFusionPhaseCompletedData", + "AssistantFusionPhaseFailedData", + "AssistantFusionPhaseStartedData", "AssistantIdleData", "AssistantIntentData", "AssistantMessageData", "AssistantMessageDeltaData", + "AssistantMessageReasoningBlocks", "AssistantMessageServerTools", "AssistantMessageStartData", "AssistantMessageToolRequest", + "AssistantMessageToolRequestCaller", + "AssistantMessageToolRequestCallerType", "AssistantMessageToolRequestType", "AssistantReasoningData", "AssistantReasoningDeltaData", @@ -10702,6 +12873,9 @@ def session_event_to_dict(x: SessionEvent) -> Any: "AssistantUsageCopilotUsage", "AssistantUsageCopilotUsageTokenDetail", "AssistantUsageData", + "AssistantUsageTransport", + "AssistedApprovalJudgeFailureReason", + "AssistedApprovalRecommendation", "Attachment", "AttachmentBlob", "AttachmentDirectory", @@ -10725,12 +12899,11 @@ def session_event_to_dict(x: SessionEvent) -> Any: "AttachmentSelectionDetails", "AttachmentSelectionDetailsEnd", "AttachmentSelectionDetailsStart", - "AutoApprovalJudgeFailureReason", - "AutoApprovalRecommendation", "AutoModeResolvedReasoningBucket", "AutoModeSwitchCompletedData", "AutoModeSwitchRequestedData", "AutoModeSwitchResponse", + "AutoTier", "AutopilotObjectiveChangedOperation", "AutopilotObjectiveChangedStatus", "BinaryAssetReference", @@ -10758,6 +12931,10 @@ def session_event_to_dict(x: SessionEvent) -> Any: "CompactionCompleteCompactionTokensUsed", "CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail", "CompactionTrigger", + "CompletionReceiptEventRange", + "CompletionReceiptFinalTool", + "CompletionReceiptStopReason", + "CompletionReceiptToolStatus", "ContextTier", "CustomAgentsUpdatedAgent", "Data", @@ -10778,7 +12955,22 @@ def session_event_to_dict(x: SessionEvent) -> Any: "ExternalToolRequestedData", "FactoryPermissionOperation", "FactoryPermissionPhase", + "FactoryRunSettledData", + "FactoryRunSettledStatus", + "FactoryRunStartedData", "FactoryRunUpdatedData", + "FusionAttribution", + "FusionConversationScope", + "FusionFollowUpAction", + "FusionFollowUpRecommendation", + "FusionPattern", + "FusionPhaseActivityKind", + "FusionPhaseKind", + "FusionPhasePlanStep", + "FusionPhaseStatus", + "FusionPhaseUsage", + "FusionScores", + "FusionTurnKind", "GitHubMcpToolConfig", "GitHubRepoRef", "HandoffRepository", @@ -10819,16 +13011,18 @@ def session_event_to_dict(x: SessionEvent) -> Any: "ModelCallFailureRequestFingerprint", "ModelCallFailureSource", "ModelCallFailureTransport", + "ModelCallFinishedData", + "ModelCallFinishedOutcome", "ModelCallStartData", + "ModelChangeSource", "OmittedBinaryOmittedReason", "OmittedBinaryResult", "OmittedBinaryType", "PendingMessagesModifiedData", - "PermissionAllowAllMode", "PermissionApproved", "PermissionApprovedForLocation", "PermissionApprovedForSession", - "PermissionAutoApproval", + "PermissionAssistedApproval", "PermissionCancelled", "PermissionCompletedData", "PermissionDeniedByContentExclusionPolicy", @@ -10836,9 +13030,11 @@ def session_event_to_dict(x: SessionEvent) -> Any: "PermissionDeniedByRules", "PermissionDeniedInteractivelyByUser", "PermissionDeniedNoApprovalRuleAndCouldNotRequestFromUser", + "PermissionMode", "PermissionPromptRequest", "PermissionPromptRequestCommands", "PermissionPromptRequestCustomTool", + "PermissionPromptRequestExtensionEnvAccess", "PermissionPromptRequestExtensionManagement", "PermissionPromptRequestExtensionPermissionAccess", "PermissionPromptRequestFactory", @@ -10850,8 +13046,10 @@ def session_event_to_dict(x: SessionEvent) -> Any: "PermissionPromptRequestRead", "PermissionPromptRequestUrl", "PermissionPromptRequestWrite", + "PermissionRecommendation", "PermissionRequest", "PermissionRequestCustomTool", + "PermissionRequestExtensionEnvAccess", "PermissionRequestExtensionManagement", "PermissionRequestExtensionPermissionAccess", "PermissionRequestFactory", @@ -10860,6 +13058,7 @@ def session_event_to_dict(x: SessionEvent) -> Any: "PermissionRequestMemory", "PermissionRequestMemoryAction", "PermissionRequestMemoryDirection", + "PermissionRequestMemoryScope", "PermissionRequestRead", "PermissionRequestShell", "PermissionRequestShellCommand", @@ -10874,10 +13073,12 @@ def session_event_to_dict(x: SessionEvent) -> Any: "PersistedBinaryImageType", "PersistedBinaryResult", "PlanChangedOperation", + "PromptCacheBreakData", "RawSessionEventData", "ReasoningSummary", "SamplingCompletedData", "SamplingRequestedData", + "SandboxDecisionData", "ScheduleOrigin", "SessionAutoModeResolvedData", "SessionAutopilotObjectiveChangedData", @@ -10891,6 +13092,7 @@ def session_event_to_dict(x: SessionEvent) -> Any: "SessionCanvasUnavailableData", "SessionCompactionCompleteData", "SessionCompactionStartData", + "SessionCompletionReceiptData", "SessionContextChangedData", "SessionContextClearedData", "SessionCustomAgentsUpdatedData", @@ -10901,6 +13103,10 @@ def session_event_to_dict(x: SessionEvent) -> Any: "SessionEventType", "SessionExtensionsAttachmentsPushedData", "SessionExtensionsLoadedData", + "SessionFusionCompletedData", + "SessionFusionResolvedData", + "SessionFusionRouteFailedData", + "SessionFusionRouteStartedData", "SessionHandoffData", "SessionIdleData", "SessionInfoData", @@ -10915,6 +13121,7 @@ def session_event_to_dict(x: SessionEvent) -> Any: "SessionMcpServersLoadedData", "SessionMode", "SessionModeChangedData", + "SessionModeNoticeDeliveredData", "SessionModelChangeData", "SessionPermissionsChangedData", "SessionPlanChangedData", @@ -10937,6 +13144,7 @@ def session_event_to_dict(x: SessionEvent) -> Any: "SessionUsageInfoData", "SessionWarningData", "SessionWorkspaceFileChangedData", + "ShutdownAgentMetric", "ShutdownCodeChanges", "ShutdownModelMetric", "ShutdownModelMetricRequests", @@ -10949,6 +13157,7 @@ def session_event_to_dict(x: SessionEvent) -> Any: "SkillSource", "SkillsLoadedSkill", "SubagentCompletedData", + "SubagentConfiguredData", "SubagentDeselectedData", "SubagentFailedData", "SubagentSelectedData", @@ -11006,6 +13215,8 @@ def session_event_to_dict(x: SessionEvent) -> Any: "ToolExecutionStartToolDescriptionMetaUIVisibility", "ToolSearchActivatedData", "ToolUserRequestedData", + "UIEphemeralQueryPhase", + "UiEphemeralQueryData", "UserInputCompletedData", "UserInputRequestedData", "UserMessageAgentMode", @@ -11014,6 +13225,7 @@ def session_event_to_dict(x: SessionEvent) -> Any: "UserToolSessionApproval", "UserToolSessionApprovalCommands", "UserToolSessionApprovalCustomTool", + "UserToolSessionApprovalExtensionEnvAccess", "UserToolSessionApprovalExtensionManagement", "UserToolSessionApprovalExtensionPermissionAccess", "UserToolSessionApprovalFactory", diff --git a/python/copilot/rpc.py b/python/copilot/rpc.py index 73c3d976d3..5585d8ce7a 100644 --- a/python/copilot/rpc.py +++ b/python/copilot/rpc.py @@ -7,7 +7,12 @@ """ from .generated.rpc import * # noqa: F401, F403 +from .generated.rpc import ( + BuiltinToolInputSchemaType as UIElicitationSchemaType, # noqa: F401 +) from .generated.rpc import ( SessionFsReaddirWithTypesEntryType as SessionFSReaddirWithTypesEntryType, # noqa: F401 ) -from .generated.rpc import __all__ # noqa: F401 +from .generated.rpc import __all__ as _generated_all + +__all__ = [*_generated_all, "UIElicitationSchemaType"] diff --git a/python/copilot/session.py b/python/copilot/session.py index 92c24bdd84..3c6d3d54a4 100644 --- a/python/copilot/session.py +++ b/python/copilot/session.py @@ -27,23 +27,22 @@ from ._telemetry import get_trace_context, trace_context from .canvas import CanvasError, CanvasHandler, OpenCanvasInstance from .generated.rpc import ( - CanvasHandler as RpcCanvasHandler, -) -from .generated.rpc import ( + BuiltinToolInputSchemaType, CanvasProviderCloseRequest, CanvasProviderInvokeActionRequest, CanvasProviderOpenRequest, CanvasProviderOpenResult, ClientSessionApiHandlers, CommandsHandlePendingCommandRequest, + GitHubTokenAcquireResultKind, HandlePendingToolCallRequest, LogRequest, MCPOauthHandlePendingRequest, MCPOauthPendingRequestResponse, - MCPOauthPendingRequestResponseKind, ModelSwitchToRequest, PermissionDecision, PermissionDecisionApproveOnce, + PermissionDecisionContext, PermissionDecisionRequest, PermissionDecisionUserNotAvailable, ProviderTokenAcquireRequest, @@ -56,9 +55,11 @@ UIElicitationSchema, UIElicitationSchemaProperty, UIElicitationSchemaPropertyType, - UIElicitationSchemaType, UIHandlePendingElicitationRequest, ) +from .generated.rpc import ( + CanvasHandler as RpcCanvasHandler, +) from .generated.rpc import ( ContextTier as _RpcContextTier, ) @@ -77,6 +78,7 @@ SessionErrorData, SessionEvent, SessionIdleData, + SessionMode, session_event_from_dict, ) from .generated.session_events import ( @@ -367,6 +369,44 @@ class PermissionNoResult: 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] @@ -374,7 +414,9 @@ class PermissionInvocation(TypedDict, total=False): _PermissionHandlerFn = Callable[ [PermissionRequest, PermissionInvocation], - PermissionRequestResult | Awaitable[PermissionRequestResult], + PermissionRequestResult + | AttributedPermissionResult + | Awaitable[PermissionRequestResult | AttributedPermissionResult], ] @@ -462,7 +504,7 @@ class McpAuthContext(TypedDict): class UserInputRequest(TypedDict, total=False): - """Request for user input from the agent (enables ask_user tool)""" + """Legacy question-and-answer request from the ask_user tool.""" question: str choices: list[str] @@ -720,7 +762,7 @@ async def confirm(self, message: str) -> bool: UIElicitationRequest( message=message, requested_schema=UIElicitationSchema( - type=UIElicitationSchemaType.OBJECT, + type=BuiltinToolInputSchemaType.OBJECT, properties={ "confirmed": UIElicitationSchemaProperty( type=UIElicitationSchemaPropertyType.BOOLEAN, @@ -755,7 +797,7 @@ async def select(self, message: str, options: list[str]) -> str | None: UIElicitationRequest( message=message, requested_schema=UIElicitationSchema( - type=UIElicitationSchemaType.OBJECT, + type=BuiltinToolInputSchemaType.OBJECT, properties={ "selection": UIElicitationSchemaProperty( type=UIElicitationSchemaPropertyType.STRING, @@ -1507,6 +1549,7 @@ def __init__( client: Any, workspace_path: os.PathLike[str] | str | None = None, managed_settings_enabled: bool = False, + on_disconnect: Callable[[], None] | None = None, ): """ Initialize a new CopilotSession. @@ -1559,6 +1602,17 @@ def __init__( self._open_canvases_lock = threading.Lock() self._rpc: SessionRpc | None = None self._destroyed = False + self._on_disconnect = on_disconnect + + def _set_disconnect_callback(self, callback: Callable[[], None]) -> None: + """Set the client-owned cleanup callback before the session becomes active.""" + self._on_disconnect = callback + + def _run_disconnect_callback(self) -> None: + callback = self._on_disconnect + self._on_disconnect = None + if callback is not None: + callback() @property def rpc(self) -> SessionRpc: @@ -1740,7 +1794,7 @@ def handler(event: SessionEventTypeAlias) -> None: total_start, session_id=self.session_id, ) - case SessionIdleData(): + case SessionIdleData() as data if data.mode != SessionMode.AUTOPILOT: log_timing( logger, logging.DEBUG, @@ -2174,7 +2228,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 @@ -2183,6 +2241,7 @@ async def _execute_permission_and_respond( PermissionDecisionRequest( request_id=request_id, result=result, + decision_context=decision_context, ) ) log_timing( @@ -2233,14 +2292,14 @@ async def _execute_mcp_auth_and_respond( if result and result.get("kind", "token") == "token": rpc_result = MCPOauthPendingRequestResponse( - kind=MCPOauthPendingRequestResponseKind.TOKEN, + kind=GitHubTokenAcquireResultKind.TOKEN, access_token=result["accessToken"], expires_in=result.get("expiresIn"), token_type=result.get("tokenType"), ) else: rpc_result = MCPOauthPendingRequestResponse( - kind=MCPOauthPendingRequestResponseKind.CANCELLED + kind=GitHubTokenAcquireResultKind.CANCELLED ) await self.rpc.mcp.oauth.handle_pending_request( MCPOauthHandlePendingRequest( @@ -2254,7 +2313,7 @@ async def _execute_mcp_auth_and_respond( MCPOauthHandlePendingRequest( request_id=request_id, result=MCPOauthPendingRequestResponse( - kind=MCPOauthPendingRequestResponseKind.CANCELLED + kind=GitHubTokenAcquireResultKind.CANCELLED ), ) ) @@ -2927,6 +2986,7 @@ async def disconnect(self) -> None: try: await self._client.request("session.destroy", {"sessionId": self.session_id}) finally: + self._run_disconnect_callback() # Clear handlers even if the request fails. with self._event_handlers_lock: self._event_handlers.clear() diff --git a/python/copilot/tools.py b/python/copilot/tools.py index de81fe7fd8..ad0bcb41bd 100644 --- a/python/copilot/tools.py +++ b/python/copilot/tools.py @@ -11,7 +11,11 @@ import json from collections.abc import Awaitable, Callable from dataclasses import dataclass, field +from datetime import date, datetime, time +from decimal import Decimal +from enum import Enum from typing import TYPE_CHECKING, Any, Literal, TypeVar, get_type_hints, overload +from uuid import UUID from pydantic import BaseModel, ValidationError @@ -82,6 +86,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) @@ -97,6 +106,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 @@ -112,6 +122,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 @@ -127,6 +138,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 @@ -141,6 +153,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. @@ -193,6 +206,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 @@ -288,6 +305,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 @@ -308,6 +326,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 @@ -348,10 +367,18 @@ def _normalize_result(result: Any) -> ToolResult: result_type="success", ) - # Everything else gets JSON-serialized (with Pydantic model support) + # Everything else gets JSON-serialized (with common Python and Pydantic values) def default(obj: Any) -> Any: if isinstance(obj, BaseModel): return obj.model_dump(mode="json") + if isinstance(obj, (date, datetime, time)): + return obj.isoformat() + if isinstance(obj, (Decimal, UUID)): + return str(obj) + if isinstance(obj, Enum): + return obj.value + if isinstance(obj, set): + return list(obj) raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable") try: diff --git a/python/e2e/_copilot_request_helpers.py b/python/e2e/_copilot_request_helpers.py index 2d91bc9bc2..d4073dd197 100644 --- a/python/e2e/_copilot_request_helpers.py +++ b/python/e2e/_copilot_request_helpers.py @@ -58,8 +58,8 @@ def _wants_stream(body: bytes) -> bool: def model_catalog(supported_endpoints: list[str] | None = None) -> dict: """The synthetic ``/models`` catalog payload.""" model: dict = { - "id": "claude-sonnet-4.5", - "name": "Claude Sonnet 4.5", + "id": "claude-sonnet-5", + "name": "Claude Sonnet 5", "object": "model", "vendor": "Anthropic", "version": "1", @@ -67,7 +67,7 @@ def model_catalog(supported_endpoints: list[str] | None = None) -> dict: "model_picker_enabled": True, "capabilities": { "type": "chat", - "family": "claude-sonnet-4.5", + "family": "claude-sonnet-5", "tokenizer": "o200k_base", "limits": {"max_context_window_tokens": 200000, "max_output_tokens": 8192}, "supports": { @@ -191,7 +191,7 @@ def build_inference_response(request: httpx.Request, text: str = SYNTHETIC_TEXT) "id": "chatcmpl-stub-1", "object": "chat.completion.chunk", "created": 1, - "model": "claude-sonnet-4.5", + "model": "claude-sonnet-5", } chunks = [ { @@ -234,7 +234,7 @@ def build_inference_response(request: httpx.Request, text: str = SYNTHETIC_TEXT) "id": "msg_stub_1", "type": "message", "role": "assistant", - "model": "claude-sonnet-4.5", + "model": "claude-sonnet-5", "content": [], "stop_reason": None, "stop_sequence": None, @@ -283,7 +283,7 @@ def build_inference_response(request: httpx.Request, text: str = SYNTHETIC_TEXT) "id": "msg_stub_1", "type": "message", "role": "assistant", - "model": "claude-sonnet-4.5", + "model": "claude-sonnet-5", "content": [{"type": "text", "text": text}], "stop_reason": "end_turn", "stop_sequence": None, @@ -300,7 +300,7 @@ def build_inference_response(request: httpx.Request, text: str = SYNTHETIC_TEXT) "id": "chatcmpl-stub-1", "object": "chat.completion", "created": 1, - "model": "claude-sonnet-4.5", + "model": "claude-sonnet-5", "choices": [ { "index": 0, diff --git a/python/e2e/conftest.py b/python/e2e/conftest.py index f441097f32..d61fe0d875 100644 --- a/python/e2e/conftest.py +++ b/python/e2e/conftest.py @@ -1,10 +1,14 @@ """Shared pytest fixtures for e2e tests.""" +import json import os +from pathlib import Path import pytest import pytest_asyncio +import copilot._cli_download as cli_download + from .testharness import E2ETestContext, is_inprocess_transport # Host-side auth resolution ranks HMAC above the GitHub token, so an ambient @@ -15,9 +19,14 @@ # .NET's InProcessEnvIsolation [ModuleInitializer] and Node's module-init guard. # Out-of-process children resolve auth in their own process where the token already # outranks HMAC. See https://github.com/github/copilot-sdk/issues/1934. +if not cli_download.CLI_VERSION: + package_json = json.loads((Path(__file__).parents[2] / "nodejs" / "package.json").read_text()) + cli_download.CLI_VERSION = package_json["copilotCliVersion"] + if is_inprocess_transport(): os.environ.pop("COPILOT_HMAC_KEY", None) os.environ.pop("CAPI_HMAC_KEY", None) + os.environ.pop("COPILOT_CLI_PATH", None) @pytest.hookimpl(tryfirst=True, hookwrapper=True) diff --git a/python/e2e/test_client_options_e2e.py b/python/e2e/test_client_options_e2e.py index fe1ed54820..4bceabcb5e 100644 --- a/python/e2e/test_client_options_e2e.py +++ b/python/e2e/test_client_options_e2e.py @@ -396,7 +396,7 @@ async def test_should_forward_advanced_session_options_in_create_wire_request( await client.start() session = await client.create_session( client_name="advanced-create-client", - model="claude-sonnet-4.5", + model="claude-sonnet-5", reasoning_effort="medium", reasoning_summary="detailed", context_tier="long_context", @@ -470,7 +470,7 @@ async def test_should_forward_advanced_session_options_in_create_wire_request( "provider": "create-provider", "id": "create-model", "name": "Create Model", - "model_id": "claude-sonnet-4.5", + "model_id": "claude-sonnet-5", "wire_model": "create-wire-model", "max_context_window_tokens": 12_000, "max_prompt_tokens": 10_000, @@ -482,7 +482,7 @@ async def test_should_forward_advanced_session_options_in_create_wire_request( try: params = _get_captured_request(capture_path, "session.create") assert params["clientName"] == "advanced-create-client" - assert params["model"] == "claude-sonnet-4.5" + assert params["model"] == "claude-sonnet-5" assert params["reasoningEffort"] == "medium" assert params["reasoningSummary"] == "detailed" assert params["contextTier"] == "long_context" @@ -538,7 +538,7 @@ async def test_should_forward_singular_provider_options_in_create_wire_request( try: await client.start() session = await client.create_session( - model="claude-sonnet-4.5", + model="claude-sonnet-5", provider={ "type": "azure", "wire_api": "responses", @@ -548,7 +548,7 @@ async def test_should_forward_singular_provider_options_in_create_wire_request( "bearer_token": "provider-bearer-token", "azure": {"api_version": "2024-02-15-preview"}, "headers": {"X-Provider-Wire": "yes"}, - "model_id": "claude-sonnet-4.5", + "model_id": "claude-sonnet-5", "wire_model": "azure-deployment", "max_prompt_tokens": 8192, "max_output_tokens": 1024, @@ -565,7 +565,7 @@ async def test_should_forward_singular_provider_options_in_create_wire_request( assert provider["bearerToken"] == "provider-bearer-token" assert provider["azure"]["apiVersion"] == "2024-02-15-preview" assert provider["headers"]["X-Provider-Wire"] == "yes" - assert provider["modelId"] == "claude-sonnet-4.5" + assert provider["modelId"] == "claude-sonnet-5" assert provider["wireModel"] == "azure-deployment" assert provider["maxPromptTokens"] == 8192 assert provider["maxOutputTokens"] == 1024 diff --git a/python/e2e/test_copilot_request_session_id_e2e.py b/python/e2e/test_copilot_request_session_id_e2e.py index 81624d73d0..75bf15f2a4 100644 --- a/python/e2e/test_copilot_request_session_id_e2e.py +++ b/python/e2e/test_copilot_request_session_id_e2e.py @@ -105,14 +105,14 @@ async def test_threads_session_id_into_byok_session(self, session_id_client): baseline = len(handler.records) session = await client.create_session( on_permission_request=PermissionHandler.approve_all, - model="claude-sonnet-4.5", + model="claude-sonnet-5", provider={ "type": "openai", "wire_api": "responses", "base_url": "https://byok.invalid/v1", "api_key": "byok-secret", - "model_id": "claude-sonnet-4.5", - "wire_model": "claude-sonnet-4.5", + "model_id": "claude-sonnet-5", + "wire_model": "claude-sonnet-5", }, ) byok_session_id = session.session_id diff --git a/python/e2e/test_event_fidelity_e2e.py b/python/e2e/test_event_fidelity_e2e.py index 25b18407a0..6b14ab2486 100644 --- a/python/e2e/test_event_fidelity_e2e.py +++ b/python/e2e/test_event_fidelity_e2e.py @@ -46,7 +46,7 @@ async def test_should_emit_events_in_correct_order_for_tool_using_conversation( assert user_idx < assistant_idx idle_idx = len(types) - 1 - types[::-1].index("session.idle") - assert idle_idx == len(types) - 1 + assert assistant_idx < idle_idx finally: unsubscribe() await session.disconnect() diff --git a/python/e2e/test_inprocess_ffi_e2e.py b/python/e2e/test_inprocess_ffi_e2e.py index c119c4ea4e..ea82037b7a 100644 --- a/python/e2e/test_inprocess_ffi_e2e.py +++ b/python/e2e/test_inprocess_ffi_e2e.py @@ -15,20 +15,14 @@ from copilot import CopilotClient, RuntimeConnection from .testharness import E2ETestContext -from .testharness.context import get_cli_path_for_tests pytestmark = pytest.mark.asyncio(loop_scope="module") class TestInProcessFfi: - async def test_should_start_and_connect_over_in_process_ffi( - self, ctx: E2ETestContext, monkeypatch: pytest.MonkeyPatch - ): - # In-process hosting loads the runtime cdylib next to the resolved CLI - # entrypoint and lets the native host spawn the worker. ``ping`` is a - # purely local RPC round-trip, so no auth or replay proxy is involved. - # If the native library is unavailable, start() raises and the test fails. - monkeypatch.setenv("COPILOT_CLI_PATH", get_cli_path_for_tests()) + async def test_should_start_and_connect_over_in_process_ffi(self, ctx: E2ETestContext): + # In-process hosting loads runtime.node directly. ``ping`` is a purely local + # RPC round-trip, so no auth or replay proxy is involved. client = CopilotClient(connection=RuntimeConnection.for_inprocess()) await client.start() diff --git a/python/e2e/test_mcp_and_agents_e2e.py b/python/e2e/test_mcp_and_agents_e2e.py index e583dbdd78..6f443a970c 100644 --- a/python/e2e/test_mcp_and_agents_e2e.py +++ b/python/e2e/test_mcp_and_agents_e2e.py @@ -99,7 +99,6 @@ async def test_should_accept_mcp_server_configuration_on_session_resume( on_permission_request=PermissionHandler.approve_all ) session_id = session1.session_id - await session1.send_and_wait("What is 1+1?") # Resume with MCP servers mcp_servers = _test_mcp_servers("test-server") diff --git a/python/e2e/test_mcp_oauth_e2e.py b/python/e2e/test_mcp_oauth_e2e.py index 9d70597c3e..76f1cbf721 100644 --- a/python/e2e/test_mcp_oauth_e2e.py +++ b/python/e2e/test_mcp_oauth_e2e.py @@ -8,11 +8,11 @@ import pytest from copilot.generated.rpc import ( + GitHubTokenAcquireResultKind, MCPAppsCallToolRequest, MCPListToolsRequest, MCPOauthHandlePendingRequest, MCPOauthPendingRequestResponse, - MCPOauthPendingRequestResponseKind, ) from copilot.session import MCPServerConfig, PermissionHandler from copilot.session_events import McpServerStatus @@ -206,7 +206,7 @@ async def on_mcp_auth_request(request, _invocation): MCPOauthHandlePendingRequest( request_id=request["requestId"], result=MCPOauthPendingRequestResponse( - kind=MCPOauthPendingRequestResponseKind.TOKEN, + kind=GitHubTokenAcquireResultKind.TOKEN, access_token=EXPECTED_TOKEN, token_type="Bearer", expires_in=3600, diff --git a/python/e2e/test_mode_handlers_e2e.py b/python/e2e/test_mode_handlers_e2e.py index f6173a4a5e..d5182c453d 100644 --- a/python/e2e/test_mode_handlers_e2e.py +++ b/python/e2e/test_mode_handlers_e2e.py @@ -6,6 +6,7 @@ import pytest +from copilot.rpc import ModeSetRequest from copilot.session import PermissionHandler from copilot.session_events import ( AutoModeSwitchCompletedData, @@ -15,6 +16,7 @@ ExitPlanModeCompletedData, ExitPlanModeRequestedData, SessionIdleData, + SessionMode, SessionModelChangeData, ) @@ -111,10 +113,8 @@ async def on_exit_plan_mode_request(request, invocation): ) ) - response = await session.send_and_wait( - PLAN_PROMPT, - agent_mode="plan", - ) + await session.rpc.mode.set(ModeSetRequest(mode=SessionMode.PLAN)) + response = await session.send_and_wait(PLAN_PROMPT) assert len(exit_plan_mode_requests) == 1 request = exit_plan_mode_requests[0] diff --git a/python/e2e/test_pending_work_resume_e2e.py b/python/e2e/test_pending_work_resume_e2e.py index 64c06c0421..5b6d978f31 100644 --- a/python/e2e/test_pending_work_resume_e2e.py +++ b/python/e2e/test_pending_work_resume_e2e.py @@ -20,11 +20,12 @@ HandlePendingToolCallRequest, PermissionDecisionRequest, PermissionDecisionUserNotAvailable, + SessionsCheckInUseRequest, ) from copilot.session import PermissionHandler from copilot.tools import Tool, ToolInvocation, ToolResult -from .testharness import DEFAULT_GITHUB_TOKEN, E2ETestContext +from .testharness import DEFAULT_GITHUB_TOKEN, E2ETestContext, wait_for_condition pytestmark = pytest.mark.asyncio(loop_scope="module") @@ -464,6 +465,7 @@ async def blocking_external_tool(args): await server.start() try: cli_url = f"localhost:{server.runtime_port}" + lock_observer: CopilotClient | None = None suspended_client = CopilotClient( connection=RuntimeConnection.for_uri( @@ -487,8 +489,41 @@ async def blocking_external_tool(args): assert (await asyncio.wait_for(tool_started, PENDING_WORK_TIMEOUT)) == "beta" if disconnect_original_client: + # force_stop closes the local socket before the server necessarily + # processes that disconnect. Observe the session lock from another + # runtime so resume cannot race the server's active-session cleanup. + lock_observer = _make_subprocess_client(ctx) + await lock_observer.start() + + async def session_lock_is_held() -> bool: + result = await lock_observer.rpc.sessions.check_in_use( + SessionsCheckInUseRequest(session_ids=[session_id]) + ) + return session_id in result.in_use + + await wait_for_condition( + session_lock_is_held, + timeout=PENDING_WORK_TIMEOUT, + timeout_message=( + f"Timed out waiting for session '{session_id}' to acquire its lock." + ), + ) await suspended_client.force_stop() + async def session_lock_is_released() -> bool: + result = await lock_observer.rpc.sessions.check_in_use( + SessionsCheckInUseRequest(session_ids=[session_id]) + ) + return session_id not in result.in_use + + await wait_for_condition( + session_lock_is_released, + timeout=PENDING_WORK_TIMEOUT, + timeout_message=( + f"Timed out waiting for session '{session_id}' to release its lock." + ), + ) + resumed_client = CopilotClient( connection=RuntimeConnection.for_uri( cli_url, connection_token="py-tcp-shared-test-token" @@ -550,6 +585,8 @@ async def resumed_external_tool(args): if not release_original.done(): release_original.set_result("ORIGINAL_SHOULD_NOT_WIN") await _safe_force_stop(suspended_client) + if lock_observer is not None: + await _safe_force_stop(lock_observer) finally: await _safe_force_stop(server) diff --git a/python/e2e/test_rewind_e2e.py b/python/e2e/test_rewind_e2e.py new file mode 100644 index 0000000000..cf7627c1a3 --- /dev/null +++ b/python/e2e/test_rewind_e2e.py @@ -0,0 +1,108 @@ +"""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" +ORIGINAL_FILE_CONTENT = "Original rewind content" +PREPARED_FILE_CONTENT = "Prepared rewind content" +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 + file_path.write_text(ORIGINAL_FILE_CONTENT, encoding="utf-8") + session = await ctx.client.create_session( + model="claude-sonnet-5", + enable_file_change_tracking=True, + on_permission_request=PermissionHandler.approve_all, + ) + + try: + ready = await session.send_and_wait( + f"Use the edit tool to replace the exact contents of {FILE_NAME} " + f"from {ORIGINAL_FILE_CONTENT} to {PREPARED_FILE_CONTENT}. " + "After the tool succeeds, reply with exactly SDK_REWIND_READY." + ) + assert ready is not None + assert ready.data.content == "SDK_REWIND_READY" + assert file_path.read_text(encoding="utf-8") == PREPARED_FILE_CONTENT + + response = await session.send_and_wait( + f"Use the edit tool to replace the exact contents of {FILE_NAME} " + f"from {PREPARED_FILE_CONTENT} to {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 + + # File change capture settles asynchronously after the turn completes, so a + # rewind point can briefly report zero restorable files. Poll until the + # capture lands instead of sampling once; the assertions below still run if + # it never does. + rewind_points = await session.rpc.history.list_rewind_points() + deadline = asyncio.get_running_loop().time() + 30 + while asyncio.get_running_loop().time() < deadline and not ( + rewind_points.unavailable_reason is None + and len(rewind_points.points) == 2 + and rewind_points.points[1].turn_changed_files + and rewind_points.points[1].can_restore_files + ): + 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) == 2 + rewind_point = rewind_points.points[1] + assert rewind_point.turn_changed_files + 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 file_path.read_text(encoding="utf-8") == PREPARED_FILE_CONTENT + + 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_e2e.py b/python/e2e/test_rpc_e2e.py index 4440635727..c9f08742cd 100644 --- a/python/e2e/test_rpc_e2e.py +++ b/python/e2e/test_rpc_e2e.py @@ -82,7 +82,7 @@ class TestSessionRpc: async def test_should_call_session_rpc_model_get_current(self, ctx: E2ETestContext): """Test calling session.rpc.model.getCurrent""" session = await ctx.client.create_session( - on_permission_request=PermissionHandler.approve_all, model="claude-sonnet-4.5" + on_permission_request=PermissionHandler.approve_all, model="claude-sonnet-5" ) result = await session.rpc.model.get_current() @@ -96,7 +96,7 @@ async def test_should_call_session_rpc_model_switch_to(self, ctx: E2ETestContext from copilot.rpc import ModelSwitchToRequest session = await ctx.client.create_session( - on_permission_request=PermissionHandler.approve_all, model="claude-sonnet-4.5" + on_permission_request=PermissionHandler.approve_all, model="claude-sonnet-5" ) # Get initial model diff --git a/python/e2e/test_rpc_mcp_config_e2e.py b/python/e2e/test_rpc_mcp_config_e2e.py index efa41cda2d..db605e07d6 100644 --- a/python/e2e/test_rpc_mcp_config_e2e.py +++ b/python/e2e/test_rpc_mcp_config_e2e.py @@ -18,8 +18,8 @@ MCPConfigRemoveRequest, MCPConfigUpdateRequest, MCPGrantType, - MCPServerConfig, - MCPServerConfigHTTPType, + MCPSerializableServerConfig, + MCPSerializableServerConfigType, ) from .testharness import E2ETestContext @@ -27,7 +27,7 @@ pytestmark = pytest.mark.asyncio(loop_scope="module") -def _server_config(servers: dict, name: str) -> MCPServerConfig: +def _server_config(servers: dict, name: str) -> MCPSerializableServerConfig: assert name in servers, f"Expected MCP server '{name}' to be present." return servers[name] @@ -37,8 +37,8 @@ async def test_should_call_server_mcp_config_rpcs(self, ctx: E2ETestContext): await ctx.client.start() server_name = f"sdk-test-{uuid.uuid4().hex}" - config = MCPServerConfig(command="node", args=[]) - updated_config = MCPServerConfig(command="node", args=["--version"]) + config = MCPSerializableServerConfig(command="node", args=[]) + updated_config = MCPSerializableServerConfig(command="node", args=["--version"]) initial = await ctx.client.rpc.mcp.config.list() assert server_name not in initial.servers @@ -70,8 +70,8 @@ async def test_should_round_trip_http_mcp_oauth_config_rpc(self, ctx: E2ETestCon await ctx.client.start() server_name = f"sdk-http-oauth-{uuid.uuid4().hex}" - config = MCPServerConfig( - type=MCPServerConfigHTTPType.HTTP, + config = MCPSerializableServerConfig( + type=MCPSerializableServerConfigType.HTTP, url="https://example.com/mcp", headers={"Authorization": "Bearer token"}, oauth_client_id="client-id", @@ -80,8 +80,8 @@ async def test_should_round_trip_http_mcp_oauth_config_rpc(self, ctx: E2ETestCon tools=["*"], timeout=3000, ) - updated_config = MCPServerConfig( - type=MCPServerConfigHTTPType.HTTP, + updated_config = MCPSerializableServerConfig( + type=MCPSerializableServerConfigType.HTTP, url="https://example.com/updated-mcp", oauth_client_id="updated-client-id", oauth_public_client=True, @@ -96,7 +96,7 @@ async def test_should_round_trip_http_mcp_oauth_config_rpc(self, ctx: E2ETestCon ) after_add = await ctx.client.rpc.mcp.config.list() added = _server_config(after_add.servers, server_name) - assert added.type == MCPServerConfigHTTPType.HTTP + assert added.type == MCPSerializableServerConfigType.HTTP assert added.url == "https://example.com/mcp" assert added.headers is not None assert added.headers["Authorization"] == "Bearer token" diff --git a/python/e2e/test_rpc_server_e2e.py b/python/e2e/test_rpc_server_e2e.py index e7c4a446ce..fdff3b8004 100644 --- a/python/e2e/test_rpc_server_e2e.py +++ b/python/e2e/test_rpc_server_e2e.py @@ -183,7 +183,7 @@ async def test_should_call_rpc_models_list_with_typed_result(self, authed_ctx: E await client.start() result = await client.rpc.models.list(ModelsListRequest()) assert result.models is not None - assert any(model.id == "claude-sonnet-4.5" for model in result.models) + assert any(model.id == "claude-sonnet-5" for model in result.models) assert all((model.name or "").strip() for model in result.models) finally: try: diff --git a/python/e2e/test_rpc_server_misc_e2e.py b/python/e2e/test_rpc_server_misc_e2e.py index d5ade1aec1..2b6b7d9514 100644 --- a/python/e2e/test_rpc_server_misc_e2e.py +++ b/python/e2e/test_rpc_server_misc_e2e.py @@ -131,8 +131,11 @@ async def test_should_login_list_get_current_auth_and_logout_account(self, ctx: client, home = await _create_isolated_client(ctx, github_token=None) try: - initial = await client.rpc.account.get_current_auth() - assert initial.auth_info is None + assert client._client is not None + initial_current = await client._client.request("account.getCurrentAuth", {}) + initial_auth_info = initial_current.get("authInfo") + assert initial_auth_info is not None + assert initial_auth_info.get("login") != login login_result = await client.rpc.account.login( AccountLoginRequest(host="https://github.com", login=login, token=token) @@ -152,21 +155,26 @@ async def test_should_login_list_get_current_auth_and_logout_account(self, ctx: ( user for user in users - if user.auth_info.type == "user" - and getattr(user.auth_info, "login", None) == login + if user.get("authInfo", {}).get("type") == "user" + and user.get("authInfo", {}).get("login") == login ), None, ) if account is not None: - assert account.token == token + assert account.get("token") == token logout = await client.rpc.account.logout( AccountLogoutRequest(auth_info=current.auth_info) ) assert logout.has_more_users is False - after_logout = await client.rpc.account.get_current_auth() - assert after_logout.auth_info is None + users_after_logout = await client.rpc.account.get_all_users() + assert all( + user.get("authInfo", {}).get("login") != login for user in users_after_logout + ) + + current_after_logout = await client._client.request("account.getCurrentAuth", {}) + assert current_after_logout.get("authInfo") == initial_auth_info finally: await _dispose_isolated(client, home) diff --git a/python/e2e/test_rpc_session_state_e2e.py b/python/e2e/test_rpc_session_state_e2e.py index f4b03d2e65..622192cfe9 100644 --- a/python/e2e/test_rpc_session_state_e2e.py +++ b/python/e2e/test_rpc_session_state_e2e.py @@ -104,7 +104,7 @@ class TestRpcSessionState: async def test_should_call_session_rpc_model_get_current(self, ctx: E2ETestContext): session = await ctx.client.create_session( on_permission_request=PermissionHandler.approve_all, - model="claude-sonnet-4.5", + model="claude-sonnet-5", ) try: result = await session.rpc.model.get_current() @@ -128,7 +128,7 @@ async def test_should_call_session_rpc_model_switchto(self, ctx: E2ETestContext) ) session = await isolated_ctx.client.create_session( on_permission_request=PermissionHandler.approve_all, - model="claude-sonnet-4.5", + model="claude-sonnet-5", ) try: before = await session.rpc.model.get_current() @@ -264,13 +264,13 @@ async def test_should_call_metadata_snapshot_set_working_directory_and_record_co session = await ctx.client.create_session( on_permission_request=PermissionHandler.approve_all, - model="claude-sonnet-4.5", + model="claude-sonnet-5", working_directory=first_dir, ) try: snapshot = await session.rpc.metadata.snapshot() assert snapshot.session_id == session.session_id - assert snapshot.selected_model == "claude-sonnet-4.5" + assert snapshot.selected_model == "claude-sonnet-5" assert snapshot.is_remote is False assert snapshot.already_in_use is False assert _path_equals(first_dir, snapshot.working_directory) @@ -395,7 +395,7 @@ async def snapshot_updated() -> bool: async def test_should_set_reasoning_effort_and_auto_name(self, ctx: E2ETestContext): session = await ctx.client.create_session( on_permission_request=PermissionHandler.approve_all, - model="claude-sonnet-4.5", + model="claude-sonnet-5", ) try: reasoning = await session.rpc.model.set_reasoning_effort( @@ -403,7 +403,7 @@ async def test_should_set_reasoning_effort_and_auto_name(self, ctx: E2ETestConte ) assert reasoning.reasoning_effort == "high" current = await session.rpc.model.get_current() - assert current.model_id == "claude-sonnet-4.5" + assert current.model_id == "claude-sonnet-5" assert current.reasoning_effort == "high" auto_name = f"Auto Session {uuid.uuid4().hex}" @@ -637,12 +637,12 @@ async def test_should_compact_session_history_after_messages(self, ctx: E2ETestC MetadataContextInfoRequest( prompt_token_limit=128_000, output_token_limit=4_096, - selected_model="claude-sonnet-4.5", + selected_model="claude-sonnet-5", ) ) if context_info.context_info is not None: context = context_info.context_info - assert context.model_name == "claude-sonnet-4.5" + assert context.model_name == "claude-sonnet-5" assert context.prompt_token_limit == 128_000 assert context.limit >= context.prompt_token_limit assert context.total_tokens > 0 @@ -657,7 +657,7 @@ async def test_should_compact_session_history_after_messages(self, ctx: E2ETestC ) recomputed = await session.rpc.metadata.recompute_context_tokens( - MetadataRecomputeContextTokensRequest(model_id="claude-sonnet-4.5") + MetadataRecomputeContextTokensRequest(model_id="claude-sonnet-5") ) assert recomputed.system_token_count > 0 assert recomputed.messages_token_count > 0 diff --git a/python/e2e/test_rpc_session_state_extras_e2e.py b/python/e2e/test_rpc_session_state_extras_e2e.py index 5d0d881a00..02ee0cd790 100644 --- a/python/e2e/test_rpc_session_state_extras_e2e.py +++ b/python/e2e/test_rpc_session_state_extras_e2e.py @@ -19,7 +19,7 @@ MetadataContextHeaviestMessagesRequest, ModelSwitchToRequest, NamedProviderConfig, - PermissionsSetAllowAllRequest, + PermissionsSetModeRequest, ProviderAddRequest, ProviderModelConfig, ProviderType, @@ -32,6 +32,7 @@ VisibilitySetRequest, ) from copilot.session import PermissionHandler +from copilot.session_events import PermissionMode from .testharness import E2ETestContext @@ -76,7 +77,7 @@ async def test_should_list_models_for_session(self, ctx: E2ETestContext): client = _make_authed_client(ctx, token) try: async with await client.create_session( - model="claude-sonnet-4.5", + model="claude-sonnet-5", on_permission_request=PermissionHandler.approve_all, github_token=token, ) as session: @@ -85,8 +86,7 @@ async def test_should_list_models_for_session(self, ctx: E2ETestContext): assert result.list is not None assert len(result.list) > 0 assert any( - "claude-sonnet-4.5" in json.dumps(model, sort_keys=True) - for model in result.list + "claude-sonnet-5" in json.dumps(model, sort_keys=True) for model in result.list ) finally: await _stop_client(client) @@ -125,7 +125,7 @@ async def test_should_add_byok_provider_and_model_at_runtime(self, ctx: E2ETestC provider=provider_name, id=model_id, name="SDK Runtime Model", - model_id="claude-sonnet-4.5", + model_id="claude-sonnet-5", wire_model="wire-sdk-runtime-model", max_context_window_tokens=4096, max_prompt_tokens=3072, @@ -185,26 +185,26 @@ async def test_should_get_and_set_allowall_permissions(self, ctx: E2ETestContext on_permission_request=PermissionHandler.approve_all, ) as session: try: - initial = await session.rpc.permissions.get_allow_all() - assert initial.enabled is False + initial = await session.rpc.permissions.get_mode() + assert initial.mode is PermissionMode.MANUAL - enable = await session.rpc.permissions.set_allow_all( - PermissionsSetAllowAllRequest(enabled=True) + enable = await session.rpc.permissions.set_mode( + PermissionsSetModeRequest(mode=PermissionMode.ALLOW_ALL) ) assert enable.success is True - assert enable.enabled is True - assert (await session.rpc.permissions.get_allow_all()).enabled is True + assert enable.mode is PermissionMode.ALLOW_ALL + assert (await session.rpc.permissions.get_mode()).mode is PermissionMode.ALLOW_ALL - disable = await session.rpc.permissions.set_allow_all( - PermissionsSetAllowAllRequest(enabled=False) + disable = await session.rpc.permissions.set_mode( + PermissionsSetModeRequest(mode=PermissionMode.MANUAL) ) assert disable.success is True - assert disable.enabled is False - assert (await session.rpc.permissions.get_allow_all()).enabled is False + assert disable.mode is PermissionMode.MANUAL + assert (await session.rpc.permissions.get_mode()).mode is PermissionMode.MANUAL finally: with contextlib.suppress(Exception): - await session.rpc.permissions.set_allow_all( - PermissionsSetAllowAllRequest(enabled=False) + await session.rpc.permissions.set_mode( + PermissionsSetModeRequest(mode=PermissionMode.MANUAL) ) async def test_should_get_context_attribution_and_heaviest_messages_after_turn( diff --git a/python/e2e/test_rpc_shell_and_fleet_e2e.py b/python/e2e/test_rpc_shell_and_fleet_e2e.py index d5a88456ab..422feab520 100644 --- a/python/e2e/test_rpc_shell_and_fleet_e2e.py +++ b/python/e2e/test_rpc_shell_and_fleet_e2e.py @@ -37,11 +37,10 @@ def _write_file_command(marker_path: Path, marker: str) -> str: if sys.platform == "win32": - return ( - f"powershell -NoLogo -NoProfile -Command " - f"\"Set-Content -LiteralPath '{marker_path}' -Value '{marker}'\"" - ) - return f"sh -c \"printf '%s' '{marker}' > '{marker_path}'\"" + # shell.exec already runs through cmd.exe on Windows. Use its built-in echo + # instead of spawning a nested PowerShell process just to write the marker. + return f'echo {marker}>"{marker_path.name}"' + return f"sh -c \"printf '%s' '{marker}' > '{marker_path.name}'\"" async def _wait_for_file_text(path: Path, expected: str, *, timeout: float = 30.0) -> None: @@ -57,19 +56,21 @@ async def _wait_for_file_text(path: Path, expected: str, *, timeout: float = 30. class TestRpcShellAndFleet: async def test_should_execute_shell_command(self, ctx: E2ETestContext): - session = await ctx.client.create_session( + async with await ctx.client.create_session( on_permission_request=PermissionHandler.approve_all, - ) - marker_path = Path(ctx.work_dir) / f"shell-rpc-{uuid.uuid4().hex}.txt" - marker = "copilot-sdk-shell-rpc" - - result = await session.rpc.shell.exec( - ShellExecRequest(command=_write_file_command(marker_path, marker), cwd=ctx.work_dir) - ) - assert (result.process_id or "").strip() - await _wait_for_file_text(marker_path, marker) - - await session.disconnect() + ) as session: + command_dir = Path(ctx.work_dir) / f"shell-rpc-{uuid.uuid4().hex}" + command_dir.mkdir() + marker_path = command_dir / "marker.txt" + marker = "copilot-sdk-shell-rpc" + + result = await session.rpc.shell.exec( + ShellExecRequest( + command=_write_file_command(marker_path, marker), cwd=str(command_dir) + ) + ) + assert (result.process_id or "").strip() + await _wait_for_file_text(marker_path, marker) async def test_should_kill_shell_process(self, ctx: E2ETestContext): session = await ctx.client.create_session( diff --git a/python/e2e/test_rpc_ui_ephemeral_query_e2e.py b/python/e2e/test_rpc_ui_ephemeral_query_e2e.py index 117fe083d2..b7f3b94d22 100644 --- a/python/e2e/test_rpc_ui_ephemeral_query_e2e.py +++ b/python/e2e/test_rpc_ui_ephemeral_query_e2e.py @@ -18,6 +18,12 @@ class TestRpcUiEphemeralQuery: + # TODO(cli-1.0.81-2): CLI 1.0.81-5 still fails session.ui.ephemeralQuery against the + # recorded snapshot on macOS ("Failed to get response from the AI model"). Re-enable + # once the runtime fix ships. + @pytest.mark.skip( + reason="blocked on CLI 1.0.81-5 session.ui.ephemeralQuery regression on macOS" + ) async def test_should_answer_ephemeral_query(self, ctx: E2ETestContext): async with await ctx.client.create_session( on_permission_request=PermissionHandler.approve_all, diff --git a/python/e2e/test_session_config_e2e.py b/python/e2e/test_session_config_e2e.py index 62dc671893..4fc78e645d 100644 --- a/python/e2e/test_session_config_e2e.py +++ b/python/e2e/test_session_config_e2e.py @@ -167,8 +167,8 @@ def _create_anthropic_provider() -> dict: "type": "anthropic", "base_url": "https://anthropic-citations.invalid/v1", "api_key": "test-provider-key", - "model_id": "claude-sonnet-4.5", - "wire_model": "claude-sonnet-4.5", + "model_id": "claude-sonnet-5", + "wire_model": "claude-sonnet-5", } @@ -201,6 +201,7 @@ async def test_vision_disabled_then_enabled_via_setmodel(self, ctx: E2ETestConte session = await ctx.client.create_session( on_permission_request=PermissionHandler.approve_all, + model="claude-sonnet-5", model_capabilities=ModelCapabilitiesOverride( supports=ModelSupportsOverride(vision=False) ), @@ -213,7 +214,7 @@ async def test_vision_disabled_then_enabled_via_setmodel(self, ctx: E2ETestConte # Switch vision on await session.set_model( - "claude-sonnet-4.5", + "claude-sonnet-5", model_capabilities=ModelCapabilitiesOverride( supports=ModelSupportsOverride(vision=True) ), @@ -234,6 +235,7 @@ async def test_vision_enabled_then_disabled_via_setmodel(self, ctx: E2ETestConte session = await ctx.client.create_session( on_permission_request=PermissionHandler.approve_all, + model="claude-sonnet-5", model_capabilities=ModelCapabilitiesOverride( supports=ModelSupportsOverride(vision=True) ), @@ -246,7 +248,7 @@ async def test_vision_enabled_then_disabled_via_setmodel(self, ctx: E2ETestConte # Switch vision off await session.set_model( - "claude-sonnet-4.5", + "claude-sonnet-5", model_capabilities=ModelCapabilitiesOverride( supports=ModelSupportsOverride(vision=False) ), @@ -295,7 +297,7 @@ async def test_should_forward_clientname_in_useragent(self, ctx: E2ETestContext) async def test_should_forward_custom_provider_headers_on_create(self, ctx: E2ETestContext): session = await ctx.client.create_session( on_permission_request=PermissionHandler.approve_all, - model="claude-sonnet-4.5", + model="claude-sonnet-5", provider=_make_proxy_provider(ctx.proxy_url, "create-provider-header"), ) @@ -319,7 +321,7 @@ async def test_should_forward_custom_provider_headers_on_resume(self, ctx: E2ETe session2 = await ctx.client.resume_session( session_id, on_permission_request=PermissionHandler.approve_all, - model="claude-sonnet-4.5", + model="claude-sonnet-5", provider=_make_proxy_provider(ctx.proxy_url, "resume-provider-header"), ) @@ -345,7 +347,7 @@ async def test_should_forward_provider_wire_model(self, ctx: E2ETestContext): # it directly (see unit tests for serialization coverage). session = await ctx.client.create_session( on_permission_request=PermissionHandler.approve_all, - model="claude-sonnet-4.5", + model="claude-sonnet-5", provider={ "type": "openai", "base_url": ctx.proxy_url, @@ -374,7 +376,7 @@ async def test_should_use_provider_model_id_as_wire_model(self, ctx: E2ETestCont "type": "openai", "base_url": ctx.proxy_url, "api_key": "test-provider-key", - "model_id": "claude-sonnet-4.5", + "model_id": "claude-sonnet-5", }, ) @@ -382,7 +384,7 @@ async def test_should_use_provider_model_id_as_wire_model(self, ctx: E2ETestCont exchanges = await ctx.get_exchanges() assert len(exchanges) == 1 - assert exchanges[0]["request"]["model"] == "claude-sonnet-4.5" + assert exchanges[0]["request"]["model"] == "claude-sonnet-5" await session.disconnect() @@ -473,7 +475,7 @@ async def test_should_enable_citations_for_anthropic_file_attachments_on_create( try: session = await client.create_session( on_permission_request=PermissionHandler.approve_all, - model="claude-sonnet-4.5", + model="claude-sonnet-5", enable_citations=True, provider=_create_anthropic_provider(), ) @@ -521,7 +523,7 @@ async def test_should_enable_citations_for_anthropic_file_attachments_on_resume( session2 = await resume_client.resume_session( session1.session_id, on_permission_request=PermissionHandler.approve_all, - model="claude-sonnet-4.5", + model="claude-sonnet-5", enable_citations=True, provider=_create_anthropic_provider(), ) diff --git a/python/e2e/test_session_e2e.py b/python/e2e/test_session_e2e.py index b6f173f759..a126eabc40 100644 --- a/python/e2e/test_session_e2e.py +++ b/python/e2e/test_session_e2e.py @@ -16,6 +16,7 @@ E2ETestContext, get_final_assistant_message, get_next_event_of_type, + wait_for_condition, ) pytestmark = pytest.mark.asyncio(loop_scope="module") @@ -24,7 +25,7 @@ class TestSessions: async def test_should_create_and_disconnect_sessions(self, ctx: E2ETestContext): session = await ctx.client.create_session( - on_permission_request=PermissionHandler.approve_all, model="claude-sonnet-4.5" + on_permission_request=PermissionHandler.approve_all, model="claude-sonnet-5" ) assert session.session_id @@ -32,7 +33,7 @@ async def test_should_create_and_disconnect_sessions(self, ctx: E2ETestContext): assert len(messages) > 0 assert messages[0].type.value == "session.start" assert messages[0].data.session_id == session.session_id - assert messages[0].data.selected_model == "claude-sonnet-4.5" + assert messages[0].data.selected_model == "claude-sonnet-5" await session.disconnect() @@ -396,19 +397,26 @@ async def test_should_delete_session(self, ctx: E2ETestContext): ) async def test_should_get_session_metadata(self, ctx: E2ETestContext): - import asyncio - # Create a session and send a message to persist it session = await ctx.client.create_session( on_permission_request=PermissionHandler.approve_all ) await session.send_and_wait("Say hello") - # Small delay to ensure session file is written to disk - await asyncio.sleep(0.2) + metadata = None + + async def metadata_is_available() -> bool: + nonlocal metadata + metadata = await ctx.client.get_session_metadata(session.session_id) + return metadata is not None + + await wait_for_condition( + metadata_is_available, + timeout=10.0, + timeout_message="Timed out waiting for session metadata to persist.", + ) # Get metadata for the session we just created - metadata = await ctx.client.get_session_metadata(session.session_id) assert metadata is not None assert metadata.session_id == session.session_id assert isinstance(metadata.start_time, datetime) diff --git a/python/e2e/test_session_fs_e2e.py b/python/e2e/test_session_fs_e2e.py index bb6e47af7b..035db125ba 100644 --- a/python/e2e/test_session_fs_e2e.py +++ b/python/e2e/test_session_fs_e2e.py @@ -174,6 +174,9 @@ def get_big_string() -> str: messages = await session.get_events() tool_result = find_tool_call_result(messages, "get_big_string") assert tool_result is not None + # The CLI joins the temp path using the host separator, so normalize before + # matching to keep the assertion valid on Windows. + tool_result = tool_result.replace("\\", "/") assert f"{SESSION_STATE_PATH}/temp/" in tool_result match = re.search(rf"({re.escape(SESSION_STATE_PATH)}/temp/[^\s]+)", tool_result) assert match is not None diff --git a/python/e2e/test_telemetry_e2e.py b/python/e2e/test_telemetry_e2e.py index 14c03ada30..8b9c82abef 100644 --- a/python/e2e/test_telemetry_e2e.py +++ b/python/e2e/test_telemetry_e2e.py @@ -118,9 +118,6 @@ def echo(invocation: ToolInvocation) -> ToolResult: scope = span.get("instrumentationScope") or {} assert scope.get("name") == source_name - trace_ids = {s.get("traceId") for s in spans if s.get("traceId")} - assert len(trace_ids) == 1 - for span in spans: status = (span.get("status") or {}).get("code", 0) assert status != 2, f"span in error state: {span}" @@ -132,11 +129,14 @@ def echo(invocation: ToolInvocation) -> ToolResult: assert _is_root_span(invoke_agent) invoke_agent_span_id = invoke_agent.get("spanId") assert invoke_agent_span_id + invoke_agent_trace_id = invoke_agent.get("traceId") + assert invoke_agent_trace_id chat_spans = [s for s in spans if _string_attribute(s, "gen_ai.operation.name") == "chat"] assert chat_spans for chat in chat_spans: assert chat.get("parentSpanId") == invoke_agent_span_id + assert chat.get("traceId") == invoke_agent_trace_id assert any( prompt in (_string_attribute(c, "gen_ai.input.messages") or "") for c in chat_spans ) @@ -149,6 +149,7 @@ def echo(invocation: ToolInvocation) -> ToolResult: s for s in spans if _string_attribute(s, "gen_ai.operation.name") == "execute_tool" ) assert tool_span.get("parentSpanId") == invoke_agent_span_id + assert tool_span.get("traceId") == invoke_agent_trace_id assert _string_attribute(tool_span, "gen_ai.tool.name") == tool_name assert (_string_attribute(tool_span, "gen_ai.tool.call.id") or "").strip() assert ( diff --git a/python/e2e/test_tool_results_e2e.py b/python/e2e/test_tool_results_e2e.py index b7b05b7af5..41d1967bc9 100644 --- a/python/e2e/test_tool_results_e2e.py +++ b/python/e2e/test_tool_results_e2e.py @@ -184,10 +184,11 @@ def on_event(event): unsubscribe = session.on(on_event) try: - asyncio.ensure_future( - session.send( + send_task = asyncio.create_task( + session.send_and_wait( "Use access_secret to get the API key." - " If access is denied, tell me it was 'access denied'." + " If access is denied, tell me it was 'access denied'.", + timeout=60.0, ) ) tool_evt = await asyncio.wait_for(tool_complete_future, timeout=60.0) @@ -201,7 +202,7 @@ def on_event(event): error_msg = error if isinstance(error, str) else getattr(error, "message", None) assert "Access denied" in (error_msg or "") - answer = await get_final_assistant_message(session, timeout=60.0) + answer = await send_task assert answer is not None finally: unsubscribe() diff --git a/python/e2e/testharness/context.py b/python/e2e/testharness/context.py index 2171e25f2d..616fa9bb0a 100644 --- a/python/e2e/testharness/context.py +++ b/python/e2e/testharness/context.py @@ -9,91 +9,47 @@ import os import re import shutil +import subprocess 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 _prepare_pinned_cli(repo_root: Path) -> str: + npm = "npm.cmd" if os.name == "nt" else "npm" + result = subprocess.run( + [npm, "run", "--silent", "prepare:runtime", "--", "--print-path"], + cwd=repo_root / "nodejs", + capture_output=True, + text=True, + check=False, + ) + output = result.stdout.strip() + if result.returncode != 0 or not output: + detail = result.stderr.strip() or output or f"exit code {result.returncode}" + raise RuntimeError(f"Failed to prepare the pinned Copilot CLI: {detail}") + cli_path = Path(output.splitlines()[-1]) + if not cli_path.is_file(): + raise RuntimeError(f"Pinned Copilot CLI was not created at {cli_path}") + return str(cli_path.resolve()) def get_cli_path_for_tests() -> str: """Get CLI path for E2E tests. - Uses COPILOT_CLI_PATH env var if set, otherwise the platform-specific CLI - package in the sibling nodejs directory's node_modules. + Uses COPILOT_CLI_PATH env var if set, otherwise prepares the release pinned + by the sibling Node.js SDK. """ env_path = os.environ.get("COPILOT_CLI_PATH") if env_path and Path(env_path).exists(): return str(Path(env_path).resolve()) - # 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), 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" - 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." - ) + return _prepare_pinned_cli(Path(__file__).parents[3]) CLI_PATH = get_cli_path_for_tests() @@ -191,7 +147,6 @@ def _apply_inprocess_environment(self) -> None: { "GH_TOKEN": DEFAULT_GITHUB_TOKEN, "GITHUB_TOKEN": DEFAULT_GITHUB_TOKEN, - "COPILOT_CLI_PATH": self.cli_path, "COPILOT_HMAC_KEY": "", "CAPI_HMAC_KEY": "", } @@ -274,11 +229,13 @@ async def configure_for_test(self, test_file: str, test_name: str): if self._proxy: await self._proxy.configure(abs_snapshot_path, self.work_dir) - # Clear temp directories between tests (but leave them in place) - # Use ignore_errors=True / suppress(OSError) to handle race conditions - # where files (e.g., SQLite session-store.db on Windows) may still be - # held open by a background process during cleanup. - for base_dir in (self.home_dir, self.work_dir): + # Keep the in-process runtime's isolated home intact until teardown stops + # the runtime. Removing its open state files on POSIX can leave later tests + # using unlinked database state. + cleanup_dirs = ( + (self.work_dir,) if self._client_inprocess else (self.home_dir, self.work_dir) + ) + for base_dir in cleanup_dirs: base_path = Path(base_dir) base_path.mkdir(parents=True, exist_ok=True) for item in base_path.iterdir(): diff --git a/python/scripts/inject-cli-version.mjs b/python/scripts/inject-cli-version.mjs index 359e7f680b..fef0f967b8 100644 --- a/python/scripts/inject-cli-version.mjs +++ b/python/scripts/inject-cli-version.mjs @@ -2,7 +2,7 @@ /** * inject-cli-version.mjs * - * Reads the pinned @github/copilot version from nodejs/package-lock.json and + * Reads the pinned Copilot CLI version from nodejs/package.json and * writes it into python/copilot/_cli_version.py, replacing the `CLI_VERSION = None` * sentinel with the concrete version string. * @@ -17,19 +17,13 @@ import { fileURLToPath } from "url"; const __dirname = dirname(fileURLToPath(import.meta.url)); const repoRoot = join(__dirname, "..", ".."); -// Read version from nodejs/package-lock.json -const lockPath = join(repoRoot, "nodejs", "package-lock.json"); -const lock = JSON.parse(readFileSync(lockPath, "utf-8")); - -// The version is in packages["node_modules/@github/copilot"].version -const copilotPkg = lock.packages?.["node_modules/@github/copilot"]; -if (!copilotPkg?.version) { - console.error( - "Error: Could not find @github/copilot version in nodejs/package-lock.json" - ); +const packagePath = join(repoRoot, "nodejs", "package.json"); +const packageJson = JSON.parse(readFileSync(packagePath, "utf-8")); +const version = packageJson.copilotCliVersion; +if (!version) { + console.error("Error: Could not find copilotCliVersion in nodejs/package.json"); process.exit(1); } -const version = copilotPkg.version; console.log(`Injecting CLI_VERSION = "${version}"`); // Patch _cli_version.py diff --git a/python/test_cli_download.py b/python/test_cli_download.py index 36952919df..aac652daf8 100644 --- a/python/test_cli_download.py +++ b/python/test_cli_download.py @@ -4,11 +4,14 @@ import base64 import hashlib +import io +import os +import tarfile from unittest.mock import patch import pytest -from copilot import _cli_download +from copilot import _cli_download, _ffi_runtime_host def _integrity(data: bytes, algo: str = "sha512") -> str: @@ -16,6 +19,28 @@ def _integrity(data: bytes, algo: str = "sha512") -> str: return f"{algo}-{base64.b64encode(digest).decode('ascii')}" +def _runtime_package(npm_platform: str) -> bytes: + wrapper_name = "copilot-runtime.exe" if os.name == "nt" else "copilot-runtime" + members = { + f"package/prebuilds/{npm_platform}/{wrapper_name}": b"wrapper", + f"package/prebuilds/{npm_platform}/runtime.node": b"runtime", + "package/copilot": b"excluded", + "package/copilot.exe": b"excluded", + f"package/ripgrep/bin/{npm_platform}/rg": b"ripgrep", + "package/definitions/future.json": b"{}", + "package/app.js": b"excluded", + "package/LICENSE.md": b"excluded", + "package/README.md": b"excluded", + } + buffer = io.BytesIO() + with tarfile.open(fileobj=buffer, mode="w:gz") as archive: + for name, content in members.items(): + info = tarfile.TarInfo(name) + info.size = len(content) + archive.addfile(info, io.BytesIO(content)) + return buffer.getvalue() + + class TestVerifyIntegrity: def test_accepts_matching_checksum(self): data = b"native-library-bytes" @@ -51,3 +76,101 @@ def test_raises_when_integrity_unavailable(self, tmp_path): # The library bytes must never be extracted/written when verification is impossible. extract.assert_not_called() + + +def test_resolve_library_path_accepts_adjacent_runtime_node(tmp_path): + wrapper = tmp_path / ("copilot-runtime.exe" if os.name == "nt" else "copilot-runtime") + wrapper.write_bytes(b"wrapper") + runtime_node = tmp_path / "runtime.node" + runtime_node.write_bytes(b"runtime") + + assert _ffi_runtime_host.resolve_library_path(str(wrapper)) == str(runtime_node) + + +class TestEnsureRuntimeWrapper: + def test_materializes_pair_from_absent_cache_with_stripped_environment( + self, tmp_path, monkeypatch + ): + npm_platform = "win32-x64" if os.name == "nt" else "linux-x64" + wrapper_name = "copilot-runtime.exe" if os.name == "nt" else "copilot-runtime" + data = _runtime_package(npm_platform) + cache_dir = tmp_path / "cache" + empty_path = tmp_path / "empty-path" + empty_path.mkdir() + assert not cache_dir.exists() + + for name in ( + "COPILOT_CLI_PATH", + "COPILOT_RUNTIME_HOST_COMMAND", + "COPILOT_RUNTIME_PROVIDER_LIB", + ): + monkeypatch.delenv(name, raising=False) + monkeypatch.setenv("PATH", str(empty_path)) + + with ( + patch.object(_cli_download, "get_cache_dir", return_value=cache_dir), + patch.object(_cli_download, "get_npm_platform", return_value=npm_platform), + patch.object(_cli_download, "_should_skip_download", return_value=False), + patch.object(_cli_download, "_fetch_url_bytes", return_value=data), + patch.object( + _cli_download, + "_fetch_runtime_integrity", + return_value=_integrity(data), + ), + ): + wrapper = _cli_download.ensure_runtime_wrapper(version="1.2.3") + + install_dir = cache_dir / "prebuilds" / npm_platform + assert wrapper == str(install_dir / wrapper_name) + assert (install_dir / wrapper_name).read_bytes() == b"wrapper" + assert (install_dir / "runtime.node").read_bytes() == b"runtime" + assert (install_dir / "ripgrep" / "bin" / npm_platform / "rg").read_bytes() == b"ripgrep" + assert (install_dir / "definitions" / "future.json").read_bytes() == b"{}" + assert not (install_dir / "app.js").exists() + assert not (install_dir / "copilot").exists() + assert not (install_dir / "copilot.exe").exists() + assert (install_dir / ".hostless-runtime-assets-v2").is_file() + if os.name != "nt": + assert (install_dir / wrapper_name).stat().st_mode & 0o111 + + def test_rejects_cached_wrapper_without_runtime_node(self, tmp_path): + npm_platform = "win32-x64" if os.name == "nt" else "linux-x64" + wrapper_name = "copilot-runtime.exe" if os.name == "nt" else "copilot-runtime" + cache_dir = tmp_path / "cache" + install_dir = cache_dir / "prebuilds" / npm_platform + install_dir.mkdir(parents=True) + (install_dir / wrapper_name).write_bytes(b"wrapper") + + with ( + patch.object(_cli_download, "get_cache_dir", return_value=cache_dir), + patch.object(_cli_download, "get_npm_platform", return_value=npm_platform), + ): + with pytest.raises(RuntimeError, match="Incomplete Copilot runtime bundle"): + _cli_download.ensure_runtime_wrapper(version="1.2.3") + + def test_upgrades_pair_only_cache_with_retained_assets(self, tmp_path): + npm_platform = "win32-x64" if os.name == "nt" else "linux-x64" + wrapper_name = "copilot-runtime.exe" if os.name == "nt" else "copilot-runtime" + cache_dir = tmp_path / "cache" + install_dir = cache_dir / "prebuilds" / npm_platform + install_dir.mkdir(parents=True) + (install_dir / wrapper_name).write_bytes(b"old-wrapper") + (install_dir / "runtime.node").write_bytes(b"old-runtime") + (install_dir / "copilot").write_bytes(b"legacy-sea") + (install_dir / ".hostless-runtime-assets-v1").write_text("1\n", encoding="ascii") + data = _runtime_package(npm_platform) + + with ( + patch.object(_cli_download, "get_cache_dir", return_value=cache_dir), + patch.object(_cli_download, "get_npm_platform", return_value=npm_platform), + patch.object(_cli_download, "_should_skip_download", return_value=False), + patch.object(_cli_download, "_fetch_url_bytes", return_value=data), + patch.object(_cli_download, "_fetch_runtime_integrity", return_value=_integrity(data)), + ): + wrapper = _cli_download.ensure_runtime_wrapper(version="1.2.3") + + assert wrapper == str(install_dir / wrapper_name) + assert (install_dir / wrapper_name).read_bytes() == b"wrapper" + assert not (install_dir / "copilot").exists() + assert (install_dir / ".hostless-runtime-assets-v2").is_file() + assert (install_dir / "ripgrep" / "bin" / npm_platform / "rg").is_file() diff --git a/python/test_client.py b/python/test_client.py index 0bba1ccd7f..b9c74294f3 100644 --- a/python/test_client.py +++ b/python/test_client.py @@ -6,6 +6,7 @@ import asyncio import inspect +import os from datetime import UTC, datetime from tempfile import TemporaryDirectory from unittest.mock import AsyncMock, Mock, patch @@ -16,6 +17,7 @@ CanvasProviderIdentity, CapiSessionOptions, CopilotClient, + DisableBypassPermissionsModes, ExtensionInfo, ModelBillingTokenPrices, ModelBillingTokenPricesLongContext, @@ -28,6 +30,8 @@ CloudSessionRepository, CopilotExpAssignmentResponse, ExpConfigEntry, + ManagedSettings, + ManagedSettingsPermissions, ModelBilling, ModelCapabilities, ModelInfo, @@ -55,6 +59,71 @@ def test_inprocess_connection_has_no_child_process_options(): assert not hasattr(connection, "args") +def test_explicit_child_process_path_does_not_require_runtime_bundle(tmp_path): + explicit = tmp_path / "copilot" + connection = RuntimeConnection.for_stdio(path=str(explicit)) + + CopilotClient(connection=connection, env={"PATH": str(tmp_path)}) + + assert connection.path == str(explicit) + + +def test_copilot_cli_path_does_not_require_runtime_bundle(tmp_path): + explicit = tmp_path / "copilot" + connection = RuntimeConnection.for_stdio() + + CopilotClient( + connection=connection, + env={"PATH": str(tmp_path), "COPILOT_CLI_PATH": str(explicit)}, + ) + + assert connection.path == str(explicit) + + +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): @@ -163,6 +232,61 @@ async def test_resume_session_allows_none_permission_handler(self): class TestCreateSessionConfig: + @pytest.mark.asyncio + async def test_ask_user_variant_forwarded_on_create_and_cold_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)) + result = {"sessionId": params["sessionId"], "workspacePath": None} + callback = kwargs.get("on_response_inline") + if callback is not None: + callback(result) + return result + + client._client.request = mock_request + await client.create_session( + session_id="ask-user-create", + ask_user_variant="elicitation", + ) + await client.resume_session( + "ask-user-cold-resume", + ask_user_variant="legacy", + ) + await client.create_session(session_id="ask-user-default-create") + await client.resume_session("ask-user-default-cold-resume") + + payloads = {(method, params["sessionId"]): params for method, params in captured} + assert ( + payloads[("session.create", "ask-user-create")]["askUserVariant"] == "elicitation" + ) + assert ( + payloads[("session.resume", "ask-user-cold-resume")]["askUserVariant"] == "legacy" + ) + assert "askUserVariant" not in payloads[("session.create", "ask-user-default-create")] + assert ( + "askUserVariant" not in payloads[("session.resume", "ask-user-default-cold-resume")] + ) + finally: + await client.force_stop() + + @pytest.mark.asyncio + @pytest.mark.parametrize("method", ["create", "resume"]) + async def test_ask_user_variant_rejects_unknown_values(self, method): + client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH)) + + with pytest.raises(ValueError, match="ask_user_variant"): + if method == "create": + await client.create_session(ask_user_variant="unknown") # type: ignore[arg-type] + else: + await client.resume_session( + "ask-user-cold-resume", + ask_user_variant="unknown", # type: ignore[arg-type] + ) + @pytest.mark.asyncio async def test_additional_directories_forwarded_on_create_and_resume(self): client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH)) @@ -651,6 +775,81 @@ async def mock_request(method, params, **kwargs): 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=DisableBypassPermissionsModes.ALLOW_AUTO_ONLY, + 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( + disable_bypass_permissions_mode="future-fail-closed-mode", + 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": "allow-auto-only", + "deny": ["Shell(git push)"], + "ask": ["Domain(publish.example)"], + "allow": ["Read(**)"], + } + } + assert captured["session.resume"]["managedSettings"] == { + "permissions": { + "disableBypassPermissionsMode": "future-fail-closed-mode", + "ask": ["Domain(publish.example)"], + } + } + + await client.create_session( + on_permission_request=PermissionHandler.approve_all, + managed_settings=ManagedSettings( + permissions=ManagedSettingsPermissions( + disable_bypass_permissions_mode=DisableBypassPermissionsModes.DISABLE, + ) + ), + ) + assert captured["session.create"]["managedSettings"] == { + "permissions": { + "disableBypassPermissionsMode": "disable", + } + } + 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: @@ -691,6 +890,7 @@ async def mock_request(method, params, **kwargs): 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: @@ -698,7 +898,7 @@ async def mock_request(method, params, **kwargs): async def mock_request(method, params, **kwargs): captured[method] = params - if method in ("session.create", "session.resume"): + if method == "session.create": result = {"sessionId": params.get("sessionId") or "session-1"} callback = kwargs.get("on_response_inline") if callback is not None: @@ -707,16 +907,42 @@ async def mock_request(method, params, **kwargs): return {} client._client.request = mock_request - session = await client.create_session( + await client.create_session( on_permission_request=PermissionHandler.approve_all, ) - await client.resume_session( - session.session_id, + + 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 "isExperimentalMode" not in captured["session.create"] - assert "isExperimentalMode" not in captured["session.resume"] + assert captured["session.create"]["managedSettings"] == { + "permissions": {"deny": [], "ask": [], "allow": []} + } finally: await client.force_stop() @@ -793,6 +1019,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)) @@ -857,6 +1122,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}, ) @@ -864,21 +1130,73 @@ 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: await client.force_stop() @pytest.mark.asyncio - async def test_create_and_resume_session_forward_capi_options(self): + @pytest.mark.parametrize( + ("create_capi", "resume_capi", "expected_create", "expected_resume"), + [ + (None, None, None, None), + ({}, {}, {}, {}), + ( + {"enable_web_socket_responses": False}, + {"enable_web_socket_responses": True}, + {"enableWebSocketResponses": False}, + {"enableWebSocketResponses": True}, + ), + ( + {"enable_web_socket_responses": True}, + {"enable_web_socket_responses": False}, + {"enableWebSocketResponses": True}, + {"enableWebSocketResponses": False}, + ), + ( + {"auto_tier": "efficiency"}, + {"auto_tier": "efficiency"}, + {"autoTier": "efficiency"}, + {"autoTier": "efficiency"}, + ), + ( + {"auto_tier": "balance"}, + {"auto_tier": "balance"}, + {"autoTier": "balance"}, + {"autoTier": "balance"}, + ), + ( + {"auto_tier": "intelligence"}, + {"auto_tier": "intelligence"}, + {"autoTier": "intelligence"}, + {"autoTier": "intelligence"}, + ), + ( + {"auto_tier": "balance", "enable_web_socket_responses": False}, + {"auto_tier": "balance", "enable_web_socket_responses": True}, + {"autoTier": "balance", "enableWebSocketResponses": False}, + {"autoTier": "balance", "enableWebSocketResponses": True}, + ), + ], + ) + async def test_create_and_resume_session_forward_capi_options( + self, + create_capi: CapiSessionOptions | None, + resume_capi: CapiSessionOptions | None, + expected_create: dict[str, object] | None, + expected_resume: dict[str, object] | None, + ): client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH)) await client.start() try: @@ -895,11 +1213,9 @@ async def mock_request(method, params, **kwargs): return {} client._client.request = mock_request - create_capi: CapiSessionOptions = {"enable_web_socket_responses": False} - resume_capi: CapiSessionOptions = {"enable_web_socket_responses": True} - session = await client.create_session( on_permission_request=PermissionHandler.approve_all, + model="auto", capi=create_capi, ) await client.resume_session( @@ -908,12 +1224,14 @@ async def mock_request(method, params, **kwargs): capi=resume_capi, ) - assert captured["session.create"]["capi"] == { - "enableWebSocketResponses": False, - } - assert captured["session.resume"]["capi"] == { - "enableWebSocketResponses": True, - } + for method, expected in ( + ("session.create", expected_create), + ("session.resume", expected_resume), + ): + if expected is None: + assert "capi" not in captured[method] + else: + assert captured[method]["capi"] == expected finally: await client.force_stop() @@ -937,6 +1255,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, @@ -951,19 +1270,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() @@ -1116,6 +1461,41 @@ async def mock_request(method, params, **kwargs): finally: await client.force_stop() + @pytest.mark.asyncio + async def test_create_and_resume_session_forward_feature_flags(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 + feature_flags = {"ENABLED_TEST_FLAG": True, "DISABLED_TEST_FLAG": False} + + session = await client.create_session( + on_permission_request=PermissionHandler.approve_all, + feature_flags=feature_flags, + ) + await client.resume_session( + session.session_id, + on_permission_request=PermissionHandler.approve_all, + feature_flags=feature_flags, + ) + + assert captured["session.create"]["featureFlags"] == feature_flags + assert captured["session.resume"]["featureFlags"] == feature_flags + finally: + await client.force_stop() + class TestURLParsing: def test_parse_port_only_url(self): @@ -1130,12 +1510,28 @@ def test_parse_host_port_url(self): assert client._actual_host == "127.0.0.1" assert client._is_external_server + def test_parse_bracketed_ipv6_host_port_url(self): + client = CopilotClient(connection=RuntimeConnection.for_uri("[::1]:9000")) + assert client._runtime_port == 9000 + assert client._actual_host == "::1" + assert client._is_external_server + def test_parse_http_url(self): client = CopilotClient(connection=RuntimeConnection.for_uri("http://localhost:7000")) assert client._runtime_port == 7000 assert client._actual_host == "localhost" assert client._is_external_server + def test_parse_http_ipv6_url(self): + client = CopilotClient(connection=RuntimeConnection.for_uri("http://[::1]:7000")) + assert client._runtime_port == 7000 + assert client._actual_host == "::1" + assert client._is_external_server + + def test_reject_bracketed_non_ipv6_host(self): + with pytest.raises(ValueError, match="Invalid cli_url format"): + CopilotClient(connection=RuntimeConnection.for_uri("[not-ipv6]:1234")) + def test_parse_https_url(self): client = CopilotClient(connection=RuntimeConnection.for_uri("https://example.com:443")) assert client._runtime_port == 443 @@ -1162,6 +1558,18 @@ def test_is_external_server_true(self): client = CopilotClient(connection=RuntimeConnection.for_uri("localhost:8080")) assert client._is_external_server + @pytest.mark.asyncio + async def test_connect_via_tcp_uses_family_independent_resolution(self): + client = CopilotClient(connection=RuntimeConnection.for_uri("[::1]:9000")) + fake_socket = Mock() + fake_socket.makefile.return_value = Mock() + + with patch("socket.create_connection", return_value=fake_socket) as create_connection: + await client._connect_via_tcp() + + create_connection.assert_called_once_with(("::1", 9000), timeout=10) + client._process.terminate() + class TestSessionFsConfig: def test_missing_initial_cwd(self): @@ -2771,6 +3179,105 @@ async def request(self, method, params, **kwargs): await client._verify_protocol_version() assert "enableGitHubTelemetryForwarding" not in captured["connect"] + @pytest.mark.asyncio + async def test_connect_forwards_client_info(self): + client = CopilotClient( + connection=RuntimeConnection.for_stdio(path=CLI_PATH), + client_info={ + "application_name": "acme-developer-portal", + "application_version": "2.4.0", + "integration_name": "copilot-assistant", + "integration_version": "1.5.0", + }, + ) + captured = {} + + class _FakeClient: + async def request(self, method, params, **kwargs): + captured[method] = params + return {"ok": True, "protocolVersion": 3, "version": "test"} + + client._client = _FakeClient() + await client._verify_protocol_version() + assert captured["connect"]["clientInfo"] == { + "editorName": "acme-developer-portal", + "editorVersion": "2.4.0", + "extensionName": "copilot-assistant", + "extensionVersion": "1.5.0", + } + + @pytest.mark.asyncio + async def test_connect_omits_client_info_when_unset(self): + client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH)) + captured = {} + + class _FakeClient: + async def request(self, method, params, **kwargs): + captured[method] = params + return {"ok": True, "protocolVersion": 3, "version": "test"} + + client._client = _FakeClient() + await client._verify_protocol_version() + assert "clientInfo" not in captured["connect"] + + @pytest.mark.asyncio + async def test_connect_forwards_partial_client_info_with_forwarding(self): + client = CopilotClient( + connection=RuntimeConnection.for_stdio(path=CLI_PATH), + client_info={"application_name": "example-app"}, + on_github_telemetry=lambda _notification: None, + ) + captured = {} + + class _FakeClient: + async def request(self, method, params, **kwargs): + captured[method] = params + return {"ok": True, "protocolVersion": 3, "version": "test"} + + client._client = _FakeClient() + await client._verify_protocol_version() + assert captured["connect"]["clientInfo"] == {"editorName": "example-app"} + assert captured["connect"]["enableGitHubTelemetryForwarding"] is True + + @pytest.mark.asyncio + async def test_connect_drops_empty_client_info_fields(self): + client = CopilotClient( + connection=RuntimeConnection.for_stdio(path=CLI_PATH), + client_info={"application_name": "example-app", "application_version": ""}, + ) + captured = {} + + class _FakeClient: + async def request(self, method, params, **kwargs): + captured[method] = params + return {"ok": True, "protocolVersion": 3, "version": "test"} + + client._client = _FakeClient() + await client._verify_protocol_version() + assert captured["connect"]["clientInfo"] == {"editorName": "example-app"} + + @pytest.mark.asyncio + async def test_connect_omits_all_empty_client_info(self): + client = CopilotClient( + connection=RuntimeConnection.for_stdio(path=CLI_PATH), + client_info={ + "application_name": "", + "application_version": "", + "integration_name": "", + "integration_version": "", + }, + ) + captured = {} + + class _FakeClient: + async def request(self, method, params, **kwargs): + captured[method] = params + return {"ok": True, "protocolVersion": 3, "version": "test"} + + client._client = _FakeClient() + await client._verify_protocol_version() + assert "clientInfo" not in captured["connect"] + @pytest.mark.asyncio async def test_event_routes_to_handler(self): from copilot.generated.rpc import GitHubTelemetryNotification diff --git a/python/test_commands_and_elicitation.py b/python/test_commands_and_elicitation.py index b1905b9350..5710d106bc 100644 --- a/python/test_commands_and_elicitation.py +++ b/python/test_commands_and_elicitation.py @@ -80,7 +80,7 @@ async def mock_request(method, params, **kwargs): payload = captured["session.create"] assert payload["commands"] == [ {"name": "deploy", "description": "Deploy the app"}, - {"name": "rollback", "description": None}, + {"name": "rollback", "description": ""}, ] finally: await client.force_stop() diff --git a/python/test_e2e_harness_cli_path.py b/python/test_e2e_harness_cli_path.py index 8a50ba7a51..83167ffd33 100644 --- a/python/test_e2e_harness_cli_path.py +++ b/python/test_e2e_harness_cli_path.py @@ -1,99 +1,12 @@ -"""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. -""" +"""Unit tests for the E2E harness's pinned CLI preparation.""" 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" @@ -101,46 +14,26 @@ def test_env_var_takes_precedence(self, tmp_path, monkeypatch): 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): + def test_prepares_the_pinned_runtime(self, tmp_path, 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 + cli = tmp_path / "copilot" + cli.write_text("runtime\n") - def test_error_names_the_searched_directory(self, monkeypatch): - monkeypatch.delenv("COPILOT_CLI_PATH", raising=False) - seen: list[Path] = [] + class Result: + returncode = 0 + stdout = f"{cli}\n" + stderr = "" - def fake_find(github_modules, package_names): - seen.append(github_modules) - return None + monkeypatch.setattr(context.subprocess, "run", lambda *args, **kwargs: Result()) + assert context._prepare_pinned_cli(tmp_path) == str(cli.resolve()) - 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_preparation_failure_includes_command_error(self, tmp_path, monkeypatch): + class Result: + returncode = 1 + stdout = "" + stderr = "download failed" - 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"] - ) + monkeypatch.setattr(context.subprocess, "run", lambda *args, **kwargs: Result()) 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 + context._prepare_pinned_cli(tmp_path) + assert "download failed" in str(excinfo.value) diff --git a/python/test_event_forward_compatibility.py b/python/test_event_forward_compatibility.py index 1ffbd59c54..a42b9994fc 100644 --- a/python/test_event_forward_compatibility.py +++ b/python/test_event_forward_compatibility.py @@ -14,14 +14,19 @@ from copilot.session_events import ( AttachmentGitHubReferenceType, + AutoTier, Data, ElicitationCompletedAction, ElicitationRequestedMode, ElicitationRequestedSchema, + ManagedSettingsResolvedSource, PermissionPromptRequestMemory, PermissionRequestMemory, PermissionRequestMemoryAction, SessionEventType, + SessionManagedSettingsResolvedData, + SessionResumeData, + SessionStartData, SessionTaskCompleteData, UserMessageAgentMode, session_event_from_dict, @@ -32,6 +37,40 @@ class TestEventForwardCompatibility: """Test forward compatibility for unknown event types.""" + @pytest.mark.parametrize("event_type", ["session.start", "session.resume"]) + @pytest.mark.parametrize("tier", ["efficiency", "balance", "intelligence", None]) + def test_auto_tier_lifecycle_events_round_trip(self, event_type, tier): + timestamp = "2026-08-28T00:00:00Z" + data = ( + { + "copilotVersion": "1.0.82-1", + "producer": "copilot-agent", + "sessionId": str(uuid4()), + "startTime": timestamp, + "version": 1, + } + if event_type == "session.start" + else {"eventCount": 1, "resumeTime": timestamp} + ) + if tier is not None: + data["autoTier"] = tier + event = session_event_from_dict( + { + "id": str(uuid4()), + "timestamp": timestamp, + "parentId": None, + "type": event_type, + "data": data, + } + ) + assert isinstance(event.data, (SessionStartData, SessionResumeData)) + assert event.data.auto_tier == (AutoTier(tier) if tier is not None else None) + serialized = session_event_to_dict(event)["data"] + if tier is None: + assert "autoTier" not in serialized + else: + assert serialized["autoTier"] == tier + def test_session_usage_info_is_recognized(self): """The session.usage_info event type should be in the enum.""" assert SessionEventType.SESSION_USAGE_INFO.value == "session.usage_info" @@ -136,6 +175,43 @@ 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", + "policyHelper", + "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_github_token_provider.py b/python/test_github_token_provider.py new file mode 100644 index 0000000000..5b203f3e34 --- /dev/null +++ b/python/test_github_token_provider.py @@ -0,0 +1,273 @@ +from __future__ import annotations + +import asyncio +from typing import Any, cast + +import pytest + +from copilot import ( + CopilotClient, + GitHubTokenAcquireReason, + RuntimeConnection, +) +from copilot._jsonrpc import JsonRpcClient, JsonRpcError +from copilot.rpc import GitHubTokenAcquireRequest + + +class FakeJsonRpcClient: + def __init__(self, *, fail_method: str | None = None) -> None: + self.fail_method = fail_method + self.requests: list[tuple[str, dict[str, Any]]] = [] + self.request_handlers: dict[str, Any] = {} + self.notification_method_handlers: dict[str, Any] = {} + + async def request(self, method: str, params: dict[str, Any], **kwargs: Any) -> dict[str, Any]: + self.requests.append((method, params)) + if method == self.fail_method: + raise RuntimeError(f"{method} failed") + if method in {"session.create", "session.resume"}: + response = {"sessionId": params["sessionId"]} + callback = kwargs.get("on_response_inline") + if callback is not None: + callback(response) + return response + if method == "session.destroy": + return {} + if method == "session.delete": + return {"success": True} + raise RuntimeError(f"Unexpected method: {method}") + + async def stop(self) -> None: + pass + + def set_request_handler(self, method: str, handler: Any) -> None: + self.request_handlers[method] = handler + + def set_notification_method_handler(self, method: str, handler: Any) -> None: + self.notification_method_handlers[method] = handler + + +class ConcurrentResumeJsonRpcClient(FakeJsonRpcClient): + def __init__(self) -> None: + super().__init__() + self.resume_responses: list[asyncio.Future[dict[str, Any]]] = [] + + async def request(self, method: str, params: dict[str, Any], **kwargs: Any) -> dict[str, Any]: + self.requests.append((method, params)) + if method == "session.create": + response = {"sessionId": params["sessionId"]} + callback = kwargs.get("on_response_inline") + if callback is not None: + callback(response) + return response + if method == "session.resume": + response = asyncio.get_running_loop().create_future() + self.resume_responses.append(response) + return await response + raise RuntimeError(f"Unexpected method: {method}") + + +def make_client(fake: FakeJsonRpcClient) -> CopilotClient: + client = CopilotClient(connection=RuntimeConnection.for_uri("localhost:1234")) + client._client = cast(JsonRpcClient, fake) + return client + + +class TestGitHubTokenProvider: + async def test_mutual_exclusion(self) -> None: + client = CopilotClient(connection=RuntimeConnection.for_uri("localhost:1234")) + + with pytest.raises( + ValueError, match="github_token and github_token_provider are mutually exclusive" + ): + await client.create_session( + github_token="static", + github_token_provider=lambda _: {"kind": "cancelled"}, + ) + + async def test_wire_mapping_token_and_cancelled(self) -> None: + fake = FakeJsonRpcClient() + client = make_client(fake) + observed: list[dict[str, Any]] = [] + + async def provider(args): + observed.append(dict(args)) + if len(observed) == 1: + return { + "kind": "token", + "accessToken": "secret-token", + "tokenType": "Bearer", + "expiresIn": 28_800, + } + return {"kind": "cancelled"} + + await client.create_session( + session_id="python-session", + github_token_provider=provider, + ) + create_payload = fake.requests[0][1] + registration_id = create_payload["gitHubTokenProviderRegistrationId"] + assert "github_token_provider" not in create_payload + assert "gitHubToken" not in create_payload + client._register_client_global_handlers() + get_token = fake.request_handlers["gitHubToken.getToken"] + + token = await get_token( + { + "registrationId": registration_id, + "host": "github.example.com", + "reason": "initial", + } + ) + cancelled = await get_token( + { + "registrationId": registration_id, + "host": "github.example.com", + "reason": "refresh", + "sessionId": "python-session", + } + ) + + assert token == { + "kind": "token", + "accessToken": "secret-token", + "tokenType": "Bearer", + "expiresIn": 28_800, + } + assert cancelled == {"kind": "cancelled"} + assert observed == [ + { + "host": "github.example.com", + "session_id": "python-session", + "reason": GitHubTokenAcquireReason.INITIAL, + }, + { + "host": "github.example.com", + "session_id": "python-session", + "reason": GitHubTokenAcquireReason.REFRESH, + }, + ] + + async def test_callback_and_unknown_registration_errors(self) -> None: + fake = FakeJsonRpcClient() + client = make_client(fake) + failure = RuntimeError("credential broker failed") + + def provider(_args): + raise failure + + await client.create_session( + session_id="error-session", + github_token_provider=provider, + ) + registration_id = fake.requests[0][1]["gitHubTokenProviderRegistrationId"] + + with pytest.raises(RuntimeError, match="credential broker failed") as exc: + await client._github_token_provider_adapter.get_token( + GitHubTokenAcquireRequest( + registration_id=registration_id, + host="github.com", + reason=GitHubTokenAcquireReason.INITIAL, + ) + ) + assert exc.value is failure + + with pytest.raises(JsonRpcError, match="No GitHub token provider registered"): + await client._github_token_provider_adapter.get_token( + GitHubTokenAcquireRequest( + registration_id="unknown", + host="github.com", + reason=GitHubTokenAcquireReason.REFRESH, + ) + ) + + async def test_failure_session_close_and_client_close_cleanup(self) -> None: + failing = make_client(FakeJsonRpcClient(fail_method="session.create")) + with pytest.raises(RuntimeError, match="session.create failed"): + await failing.create_session(github_token_provider=lambda _: {"kind": "cancelled"}) + assert failing._github_token_providers == {} + + fake = FakeJsonRpcClient() + client = make_client(fake) + first = await client.create_session( + session_id="first", + github_token_provider=lambda _: {"kind": "cancelled"}, + ) + await client.create_session( + session_id="second", + github_token_provider=lambda _: {"kind": "cancelled"}, + ) + assert len(client._github_token_providers) == 2 + + await first.disconnect() + assert len(client._github_token_providers) == 1 + await client.delete_session("second") + assert client._github_token_providers == {} + await client.force_stop() + assert client._github_token_providers == {} + + async def test_resume_rotates_provider(self) -> None: + fake = FakeJsonRpcClient() + client = make_client(fake) + calls: list[str] = [] + + await client.create_session( + session_id="resumed", + github_token_provider=lambda _: calls.append("first") or {"kind": "cancelled"}, + ) + first_registration = fake.requests[0][1]["gitHubTokenProviderRegistrationId"] + await client.resume_session( + "resumed", + github_token_provider=lambda _: calls.append("second") or {"kind": "cancelled"}, + ) + second_registration = fake.requests[1][1]["gitHubTokenProviderRegistrationId"] + + with pytest.raises(JsonRpcError): + await client._github_token_provider_adapter.get_token( + GitHubTokenAcquireRequest( + registration_id=first_registration, + host="github.com", + reason=GitHubTokenAcquireReason.REFRESH, + ) + ) + assert await client._github_token_provider_adapter.get_token( + GitHubTokenAcquireRequest( + registration_id=second_registration, + host="github.com", + reason=GitHubTokenAcquireReason.REFRESH, + ) + ) == {"kind": "cancelled"} + assert calls == ["second"] + + async def test_concurrent_resume_keeps_pending_registration(self) -> None: + fake = ConcurrentResumeJsonRpcClient() + client = make_client(fake) + await client.create_session( + session_id="concurrent", + github_token_provider=lambda _: {"kind": "cancelled"}, + ) + + first_resume = asyncio.create_task( + client.resume_session( + "concurrent", + github_token_provider=lambda _: {"kind": "cancelled"}, + ) + ) + second_resume = asyncio.create_task( + client.resume_session( + "concurrent", + github_token_provider=lambda _: {"kind": "cancelled"}, + ) + ) + while len(fake.resume_responses) < 2: + await asyncio.sleep(0) + + fake.resume_responses[0].set_result({"sessionId": "concurrent"}) + await first_resume + assert len(client._github_token_providers) == 2 + + fake.resume_responses[1].set_result({"sessionId": "concurrent"}) + await second_resume + assert len(client._github_token_providers) == 1 + second_registration = fake.requests[2][1]["gitHubTokenProviderRegistrationId"] + assert second_registration in client._github_token_providers diff --git a/python/test_jsonrpc.py b/python/test_jsonrpc.py index 56ce44e374..2f2ecafce9 100644 --- a/python/test_jsonrpc.py +++ b/python/test_jsonrpc.py @@ -5,6 +5,7 @@ of large payloads and short reads from pipes. """ +import asyncio import io import json import os @@ -13,7 +14,7 @@ import pytest -from copilot._jsonrpc import JsonRpcClient +from copilot._jsonrpc import JsonRpcClient, ProcessExitedError class MockProcess: @@ -162,6 +163,29 @@ def test_read_exact_partial_data_raises_eof(self): client._read_exact(100) +@pytest.mark.asyncio +async def test_process_exit_waits_for_stderr_reader(): + process = MockProcess() + process.returncode = 1 + client = JsonRpcClient(process) + future = asyncio.get_running_loop().create_future() + client.pending_requests["request-id"] = future + + def capture_stderr(): + time.sleep(0.01) + with client._stderr_lock: + client._stderr_output.append("unsupported argument\n") + + client._stderr_thread = threading.Thread(target=capture_stderr) + client._stderr_thread.start() + + client._fail_pending_requests() + + await asyncio.sleep(0) + with pytest.raises(ProcessExitedError, match=r"stderr: unsupported argument"): + future.result() + + class TestReadMessageWithLargePayloads: """Tests for _read_message() with large JSON-RPC messages""" diff --git a/python/test_permission_decision_context.py b/python/test_permission_decision_context.py new file mode 100644 index 0000000000..70b3a74de5 --- /dev/null +++ b/python/test_permission_decision_context.py @@ -0,0 +1,104 @@ +from unittest.mock import AsyncMock, MagicMock + +from copilot import PermissionResponseCapability +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 test_permission_response_capability_is_exported_from_package_root() -> None: + assert PermissionResponseCapability.INTERACTIVE.value == "interactive" + + +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 5556a77c38..a23173727a 100644 --- a/python/test_rpc_generated.py +++ b/python/test_rpc_generated.py @@ -6,6 +6,7 @@ import pytest from copilot.rpc import ( + BuiltinToolInputSchemaType, CommandsApi, CommandsInvokeRequest, CommandsRespondToQueuedCommandRequest, @@ -17,6 +18,8 @@ RemoteSessionMetadataValue, SessionList, SlashCommandTextResult, + TaskAgentInfo, + UIElicitationSchemaType, ) @@ -41,6 +44,10 @@ def test_remote_control_status_deserializes_string_discriminated_union(): assert result.status.to_dict() == {"state": "off"} +def test_ui_elicitation_schema_type_preserves_public_alias(): + assert UIElicitationSchemaType is BuiltinToolInputSchemaType + + def test_session_list_deserializes_boolean_discriminated_entries(): payload = { "sessions": [ @@ -73,6 +80,25 @@ def test_session_list_deserializes_boolean_discriminated_entries(): assert remote.repository.owner == "github" +def test_task_agent_info_deserializes_integral_float_milliseconds(): + task = TaskAgentInfo.from_dict( + { + "agentType": "general-purpose", + "description": "Example task", + "id": "agent-1", + "prompt": "Do the task", + "startedAt": "2026-08-19T12:00:00Z", + "status": "running", + "toolCallId": "tool-1", + "type": "agent", + "activeTimeMs": 43.0, + } + ) + + assert task.active_time_ms == 43 + assert task.to_dict()["activeTimeMs"] == 43 + + @pytest.mark.parametrize( ("handled", "expected_type"), [(True, QueuedCommandHandled), (False, QueuedCommandNotHandled)], diff --git a/python/test_rpc_timeout.py b/python/test_rpc_timeout.py index 7e85729cab..6a1535c301 100644 --- a/python/test_rpc_timeout.py +++ b/python/test_rpc_timeout.py @@ -54,7 +54,7 @@ async def test_custom_timeout_forwarded(self): @pytest.mark.asyncio async def test_timeout_on_session_params_method(self): client = AsyncMock() - client.request = AsyncMock(return_value={"mode": "plan"}) + client.request = AsyncMock(return_value={"modelChanged": False, "status": "completed"}) api = ModeApi(client, "sess-1") await api.set(ModeSetRequest(mode=SessionMode.PLAN), timeout=120.0) diff --git a/python/test_session.py b/python/test_session.py new file mode 100644 index 0000000000..dd2d0a72f6 --- /dev/null +++ b/python/test_session.py @@ -0,0 +1,69 @@ +"""CopilotSession unit tests.""" + +import asyncio +from datetime import UTC, datetime +from unittest.mock import AsyncMock, Mock +from uuid import uuid4 + +import pytest + +from copilot.session import CopilotSession +from copilot.session_events import ( + AssistantMessageData, + SessionEvent, + SessionEventType, + SessionIdleData, + SessionMode, +) + + +def _event(data, event_type: SessionEventType) -> SessionEvent: + return SessionEvent( + data=data, + id=uuid4(), + timestamp=datetime.now(UTC), + type=event_type, + ) + + +@pytest.mark.asyncio +async def test_send_and_wait_skips_autopilot_continuation_idle(): + client = Mock() + client.request = AsyncMock(return_value={"messageId": "message-1"}) + session = CopilotSession("session-1", client) + + pending = asyncio.create_task(session.send_and_wait("keep going")) + await asyncio.sleep(0) + client.request.assert_awaited_once() + + session._dispatch_event( + _event( + AssistantMessageData(content="intermediate", message_id="assistant-1"), + SessionEventType.ASSISTANT_MESSAGE, + ) + ) + session._dispatch_event( + _event( + SessionIdleData(mode=SessionMode.AUTOPILOT), + SessionEventType.SESSION_IDLE, + ) + ) + assert not pending.done() + + session._dispatch_event( + _event( + AssistantMessageData(content="final", message_id="assistant-2"), + SessionEventType.ASSISTANT_MESSAGE, + ) + ) + session._dispatch_event( + _event( + SessionIdleData(mode=SessionMode.INTERACTIVE), + SessionEventType.SESSION_IDLE, + ) + ) + + result = await asyncio.wait_for(pending, timeout=1) + assert result is not None + assert isinstance(result.data, AssistantMessageData) + assert result.data.content == "final" diff --git a/python/test_tool_set.py b/python/test_tool_set.py index 0674b488a2..3468ccee34 100644 --- a/python/test_tool_set.py +++ b/python/test_tool_set.py @@ -248,6 +248,7 @@ def test_empty_mode_defaults(self): "coauthorEnabled": False, "manageScheduleEnabled": False, "installedPlugins": [], + "includedBuiltinSkills": [], } def test_empty_mode_caller_wins(self): @@ -258,14 +259,105 @@ def test_empty_mode_caller_wins(self): "coauthorEnabled": True, "manageScheduleEnabled": True, "installedPlugins": [], + "includedBuiltinSkills": [], } + def test_empty_mode_preserves_explicit_builtin_skill_allowlist(self): + patch = _post_create_options_patch("empty", None, None, None, None, ["code-review"]) + assert patch is not None + assert patch["includedBuiltinSkills"] == ["code-review"] + def test_copilot_cli_returns_none_when_unset(self): assert _post_create_options_patch("copilot-cli", None, None, None, None) is None + # Non-empty mode never injects the built-in skill restriction. + assert "includedBuiltinSkills" not in ( + _post_create_options_patch("copilot-cli", True, None, False, None) or {} + ) def test_copilot_cli_passes_through_explicit_values(self): - patch = _post_create_options_patch("copilot-cli", True, None, False, None) - assert patch == {"skipCustomInstructions": True, "coauthorEnabled": False} + patch = _post_create_options_patch("copilot-cli", True, None, False, None, ["code-review"]) + assert patch == { + "skipCustomInstructions": True, + "coauthorEnabled": False, + "includedBuiltinSkills": ["code-review"], + } + + +class _CapturingOptions: + """Captures the params passed to ``session.rpc.options.update``.""" + + def __init__(self) -> None: + self.captured: list = [] + + async def update(self, params) -> None: + self.captured.append(params) + + +class _CapturingRpc: + def __init__(self) -> None: + self.options = _CapturingOptions() + + +class _FakeSession: + def __init__(self, session_id: str = "sid-1") -> None: + self.session_id = session_id + self.rpc = _CapturingRpc() + + async def disconnect(self) -> None: + pass + + +class TestApplyPostCreateOptionsPatch: + """Guards the translation from the patch dict to ``SessionUpdateOptionsParams``. + + This covers the wire request emitted on both create and resume, which both + funnel through ``_apply_post_create_options_patch``. + """ + + def _make_client(self): + return CopilotClient( + mode="empty", + connection=UriRuntimeConnection(url="http://localhost:1234"), + ) + + async def test_empty_mode_sends_included_builtin_skills_empty(self): + client = self._make_client() + session = _FakeSession() + await client._apply_post_create_options_patch(session, "empty", None, None, None, None) + assert len(session.rpc.options.captured) == 1 + params = session.rpc.options.captured[0] + # The Empty post-patch must reach the wire request as an empty list, not + # be silently dropped during translation. + assert params.included_builtin_skills == [] + assert params.installed_plugins == [] + + async def test_empty_mode_explicit_allowlist_reaches_wire(self): + client = self._make_client() + session = _FakeSession() + await client._apply_post_create_options_patch( + session, "empty", False, False, True, True, ["code-review"] + ) + params = session.rpc.options.captured[0] + assert params.included_builtin_skills == ["code-review"] + + async def test_copilot_cli_mode_omits_included_builtin_skills(self): + client = self._make_client() + session = _FakeSession() + # Non-empty mode with no overrides sends no patch at all. + await client._apply_post_create_options_patch( + session, "copilot-cli", None, None, None, None + ) + assert session.rpc.options.captured == [] + + async def test_copilot_cli_mode_never_sets_included_builtin_skills(self): + client = self._make_client() + session = _FakeSession() + await client._apply_post_create_options_patch( + session, "copilot-cli", True, None, False, None + ) + params = session.rpc.options.captured[0] + # copilot-cli mode must not inject the built-in skill restriction. + assert params.included_builtin_skills is None class TestClientConstruction: diff --git a/python/test_tools.py b/python/test_tools.py index 97de41df42..031f68c053 100644 --- a/python/test_tools.py +++ b/python/test_tools.py @@ -427,6 +427,39 @@ class Record(BaseModel): assert set(parsed["tags"]) == {"python", "sdk"} assert result.result_type == "success" + def test_plain_dict_with_non_primitive_fields_is_serialized(self): + from datetime import date, datetime, time + from decimal import Decimal + from enum import Enum + from uuid import UUID + + class Status(Enum): + ACTIVE = "active" + + result = _normalize_result( + { + "id": UUID("12345678-1234-5678-1234-567812345678"), + "created": datetime(2026, 1, 15, 10, 30, 0), + "day": date(2026, 1, 15), + "at": time(10, 30, 0), + "score": Decimal("99.5"), + "status": Status.ACTIVE, + "tags": {"python", "sdk"}, + } + ) + 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", + "at": "10:30:00", + "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"): diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 8de6797989..b91eebd06c 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -454,6 +454,7 @@ dependencies = [ "tracing", "ureq", "uuid", + "windows-sys 0.61.2", "zip", ] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 0f18a9b159..c66480d511 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -70,6 +70,12 @@ tokio-tungstenite = { version = "0.24", default-features = false, features = ["c [target.'cfg(windows)'.dependencies] zip = { version = "2", default-features = false, features = ["deflate"], optional = true } +windows-sys = { version = "0.61", default-features = false, features = [ + "Win32_Foundation", + "Win32_System_Diagnostics_ToolHelp", + "Win32_System_JobObjects", + "Win32_System_Threading", +] } [dev-dependencies] rusqlite = { version = "0.35", features = ["bundled"] } @@ -90,8 +96,29 @@ required-features = ["test-support"] name = "protocol_version_test" required-features = ["test-support"] +[[test]] +name = "extension_launch_provider_test" +required-features = ["test-support"] + +[[test]] +name = "extension_launch_provider_runtime_test" +required-features = ["test-support"] + +[[bin]] +name = "copilot-extension-test-fixture" +path = "tests/fixtures/extension_fixture.rs" +required-features = ["test-support"] +test = false +bench = false + +[[bin]] +name = "copilot-host-crash-fixture" +path = "tests/fixtures/host_crash_fixture.rs" +required-features = ["test-support"] +test = false +bench = false + [build-dependencies] -base64 = "0.22" dirs = "5" flate2 = "1" serde_json = "1" diff --git a/rust/README.md b/rust/README.md index 3140900447..a561e6da09 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 @@ -101,8 +101,63 @@ transports. | `env_remove` | `Vec` | Environment variables to remove | | `extra_args` | `Vec` | Extra CLI flags | | `transport` | `Transport` | `Default`, `Stdio`, `InProcess`, `Tcp`, or `External` | +| `extension_launch_provider` | `Option>` | Connection-global extension launch resolver | -With the default `CliProgram::Resolve`, `Client::start()` resolves the CLI in this order: an explicit `CliProgram::Path(path)`, the `COPILOT_CLI_PATH` env var, then the bundled CLI that was embedded at build time. There is no PATH scanning — if you've opted out of bundling (`default-features = false`) you must supply either `CliProgram::Path` or `COPILOT_CLI_PATH`. +With the default `CliProgram::Resolve`, managed stdio and TCP transports resolve an explicit `CliProgram::Path(path)`, `COPILOT_CLI_PATH`, then the bundled `copilot-runtime` wrapper and adjacent `runtime.node`. In-process transport retains its CLI-entrypoint resolution. There is no PATH scanning. + +#### Extension launch provider + +Hosts that own legacy extension process assets can supply a typed, asynchronous +launch resolver: + +```rust,ignore +use std::collections::HashMap; + +use async_trait::async_trait; +use github_copilot_sdk::extension_launch_provider::{ + ExtensionLaunchProfile, ExtensionLaunchProvider, ExtensionLaunchProviderResolveRequest, + ExtensionLaunchProviderResolveResult, +}; +use github_copilot_sdk::{Client, ClientOptions, Result}; + +struct AppExtensionLaunchProvider; + +#[async_trait] +impl ExtensionLaunchProvider for AppExtensionLaunchProvider { + async fn resolve( + &self, + request: ExtensionLaunchProviderResolveRequest, + ) -> Result { + Ok(ExtensionLaunchProviderResolveResult { + launch: Some(ExtensionLaunchProfile { + executable: "/app/copilot".to_string(), + args: vec!["/app/preloads/extension_bootstrap.mjs".to_string()], + env: HashMap::from([ + ("COPILOT_AUTO_UPDATE".to_string(), "false".to_string()), + ("EXTENSION_PATH".to_string(), request.module_path), + ]), + }), + }) + } +} + +let client = Client::start( + ClientOptions::new().with_extension_launch_provider(AppExtensionLaunchProvider), +).await?; +``` + +`Client::start` registers the provider before returning, and reverse requests +are routed at the connection level rather than through a session. The SDK +forwards the returned executable, arguments, and environment unchanged; it +does not discover or bundle an executable or bootstrap. The runtime owns and +overrides `COPILOT_SDK_PATH`, `SESSION_ID`, and +`COPILOT_EXTENSION_PARENT_PID`. + +`COPILOT_CLI_DIST_DIR` is only appropriate when the host supplies a complete +CLI distribution containing `index.js` and its matching preloads. When the +executable is a version-matched standalone Copilot binary, omit that variable +and set `COPILOT_AUTO_UPDATE=false` so its embedded distribution remains +selected. ### Session @@ -274,6 +329,63 @@ let config = SessionConfig { let session = client.create_session(config).await?; ``` +Use `with_ask_user_variant(AskUserVariant::Elicitation)` together with +`with_elicitation_handler(...)` to expose the structured form-based `ask_user` +tool. The default remains `AskUserVariant::Legacy`. Re-supply the option and +handler through `ResumeSessionConfig` on a cold resume. + +For rotating per-session GitHub credentials, install a `GitHubTokenProvider` +instead of setting `github_token`: + +```rust,ignore +use github_copilot_sdk::{ + GitHubToken, GitHubTokenProviderArgs, GitHubTokenProviderResult, SessionConfig, +}; + +let provider = Arc::new(|args: GitHubTokenProviderArgs| async move { + let access_token = acquire_for_host(&args.host).await?; + Ok(GitHubTokenProviderResult::Token(GitHubToken::new( + access_token, + 8 * 60 * 60, + ))) +}); +let config = SessionConfig::default().with_github_token_provider(provider); +``` + +The remaining lifetime is required and must be positive when the callback +completes; production GitHub tokens typically last eight hours. Static +`github_token` and a provider are mutually exclusive. The same provider API is +available on `ResumeSessionConfig`. + +Initial acquisition runs during session creation or resume. Cancellation, +provider errors, and invalid token responses reject that operation instead of +falling back to ambient authentication. Idle sessions refresh only before their +next credential-consuming operation; there is no background refresh timer. + +### Auto routing tiers + +Use `CapiSessionOptions::with_auto_tier` to select `AutoTier::Efficiency`, +`AutoTier::Balance`, or `AutoTier::Intelligence`. This option is meaningful only +with model `auto` (Auto mode V2). +It requires a runtime version that supports `capi.autoTier`. + +```rust +use github_copilot_sdk::{AutoTier, CapiSessionOptions, SessionConfig}; + +let config = SessionConfig::default() + .with_model("auto") + .with_capi(CapiSessionOptions::new().with_auto_tier(AutoTier::Balance)); +``` + +The same options work with `ResumeSessionConfig::with_capi` and can be combined +with `with_enable_web_socket_responses(false)`. The SDK omits an unset tier: +the runtime chooses its default on create and preserves the persisted/current +tier on resume. An explicit tier overrides the persisted tier on cold resume; +the runtime rejects a conflicting tier when the session is already resident +in memory. The SDK does not choose a default or manage tier persistence. +See [Auto tier persistence](../docs/features/session-persistence.md#auto-tier-persistence) +for the lifecycle rules. + ### Session Hooks Hooks intercept CLI behavior at lifecycle points — tool use, prompt submission, session start/end, and errors. Install a `SessionHooks` impl with [`SessionConfig::with_hooks`] — the SDK auto-enables `hooks` in `SessionConfig` when one is set. @@ -493,6 +605,7 @@ impl ElicitationHandler for MyElicitation { let config = SessionConfig::default() .with_permission_handler(Arc::new(ApproveAllHandler)) + .with_ask_user_variant(AskUserVariant::Elicitation) .with_elicitation_handler(Arc::new(MyElicitation)); ``` @@ -792,6 +905,7 @@ none of them are scheduled for removal. | File | Description | | ----------------- | -------------------------------------------------------------------------------------------------------------------------- | | `lib.rs` | `Client`, `ClientOptions`, `CliProgram`, `Transport`, `Error` | +| `extension_launch_provider.rs` | Connection-global `ExtensionLaunchProvider` trait and launch profile DTOs | | `session.rs` | `Session` struct, event loop, `send`/`send_and_wait`, `Client::create_session`/`resume_session` | | `subscription.rs` | `EventSubscription` / `LifecycleSubscription` (`Stream`-able observer handles for `subscribe()` / `subscribe_lifecycle()`) | | `handler.rs` | `PermissionHandler`, `ElicitationHandler`, `UserInputHandler`, `ExitPlanModeHandler`, `AutoModeSwitchHandler` traits; `ApproveAllHandler`, `DenyAllHandler` | @@ -801,13 +915,15 @@ none of them are scheduled for removal. | `types.rs` | CLI protocol types (`SessionId`, `SessionEvent`, `SessionConfig`, `Tool`, etc.) | | `resolve.rs` | Bundled-CLI resolution (`copilot_binary`) | | `embeddedcli.rs` | Embedded CLI extraction (gated on the default `bundled-cli` feature) | -| `router.rs` | Internal per-session event demux | +| `router.rs` | Internal connection-global request dispatch and per-session event demux | | `jsonrpc.rs` | Internal Content-Length framed JSON-RPC transport | -## Embedded CLI +## Bundled runtime artifacts The SDK provisions its runtime at build time. By default the `bundled-cli` -feature embeds the verified child-process runtime in your compiled crate. +feature embeds the verified `copilot-runtime` wrapper and adjacent +`runtime.node` in your compiled crate. The compatible CLI artifact remains +available separately for `install_bundled_cli` and in-process hosting. Enable `bundled-in-process` to additionally embed the native runtime library and use `Transport::InProcess`: @@ -825,15 +941,11 @@ For builds that prefer a smaller artifact, disable the `bundled-cli` feature: github-copilot-sdk = { version = "0.1", default-features = false } ``` -> **You become responsible for supplying the CLI at runtime.** With -> `bundled-cli` disabled, the produced binary does not contain the CLI -> and will not search the system for one. You must point it at a -> compatible CLI via [`CliProgram::Path`] (on `ClientOptions`) or the -> `COPILOT_CLI_PATH` environment variable, and you are responsible for -> guaranteeing the supplied CLI version is compatible with this SDK -> release. Do **not** assume that whatever CLI happens to be installed -> on the target system will work — the SDK and CLI are versioned -> together. +> **You become responsible for supplying the runtime at deployment.** With +> `bundled-cli` disabled, the produced binary does not contain these artifacts +> and will not search the system for them. For managed child-process transports, +> supply a compatible wrapper pair via an explicit [`CliProgram::Path`]. +> `COPILOT_CLI_PATH` remains a direct program override. > > **Convenience on the build machine only.** As a special case, > `build.rs` downloads and integrity-verifies the compatible CLI version and @@ -842,31 +954,31 @@ github-copilot-sdk = { version = "0.1", default-features = false } > makes local development and CI ergonomic, but it does **not** carry > over when you copy the built binary to another machine — distributed > builds (release artifacts, signed installers, container images, etc.) -> must either keep `bundled-cli` enabled or ship the CLI alongside and -> set `CliProgram::Path` / `COPILOT_CLI_PATH`. +> must either keep `bundled-cli` enabled or ship the runtime pair and set +> `CliProgram::Path`. ### How it works 1. **Version pin.** `build.rs` reads the CLI version from one of two sources: - `cli-version.txt` at the crate root (present in published crate tarballs and vendored slots). - - Otherwise, `../nodejs/package-lock.json` (contributor build inside the github/copilot-sdk repo — matches the .NET and Go SDK conventions here). + - Otherwise, `../nodejs/package.json` (contributor build inside the github/copilot-sdk repo). The resolved version is baked into the crate via `cargo:rustc-env=COPILOT_SDK_CLI_VERSION` regardless of mode. The runtime resolver consumes it to recompute the on-disk path by convention, so no absolute paths leak into the rlib. -2. **Build time:** `build.rs` downloads the platform-specific npm package and - verifies its `sha512` integrity against the lockfile or publish snapshot. +2. **Build time:** `build.rs` downloads the platform-specific release archive and + verifies its SHA-256 against the release's `SHA256SUMS.txt` or the publish snapshot. Then: - - **`bundled-cli` on (default):** creates and embeds a minimal archive containing only the CLI executable. - - **`bundled-in-process` on:** the minimal archive additionally contains the platform-native runtime library (`.dll`, `.so`, or `.dylib`); no other npm package files are embedded. - - **`bundled-cli` off:** extracts the binary directly into the platform cache (staging file + atomic rename), idempotent across rebuilds. If the extracted binary is already present at the expected path, the download is skipped entirely — the extracted binary *is* the cache. + - **`bundled-cli` on (default):** creates and embeds a minimal archive containing the CLI executable, `copilot-runtime[.exe]`, and `runtime.node`. + - **`bundled-in-process` on:** the archive additionally contains the platform-native runtime library (`.dll`, `.so`, or `.dylib`). + - **`bundled-cli` off:** extracts the same artifacts directly into the platform cache using staging files and atomic renames. -3. **Runtime:** in both modes the binary lives at: +3. **Runtime:** in both modes the artifacts share one versioned directory: | OS | Path | |----|------| - | macOS | `~/Library/Caches/github-copilot-sdk/cli//copilot` | - | Linux | `${XDG_CACHE_HOME:-~/.cache}/github-copilot-sdk/cli//copilot` | - | Windows | `%LOCALAPPDATA%\github-copilot-sdk\cli\\copilot.exe` | + | macOS | `~/Library/Caches/github-copilot-sdk/cli//` | + | Linux | `${XDG_CACHE_HOME:-~/.cache}/github-copilot-sdk/cli//` | + | Windows | `%LOCALAPPDATA%\github-copilot-sdk\cli\\` | Old version directories accumulate in siblings; clean them up at your leisure. @@ -895,18 +1007,20 @@ COPILOT_CLI_EXTRACT_DIR = { value = "vendor/copilot", relative = true, force = t ### Skipping the bundle entirely -Set `COPILOT_SKIP_CLI_DOWNLOAD=1` at build time to disable the entire download / bundle / cache mechanism — `build.rs` returns immediately without touching the network. Use this when you always supply the CLI at runtime via `ClientOptions::program = CliProgram::Path(...)` or `COPILOT_CLI_PATH`. Works regardless of the `bundled-cli` feature state; runtime resolution falls through to `Error::BinaryNotFound` unless one of those explicit sources resolves. +Set `COPILOT_SKIP_CLI_DOWNLOAD=1` at build time to disable the entire download / bundle / cache mechanism — `build.rs` returns immediately without touching the network. Use this when you always supply the managed runtime via `ClientOptions::program = CliProgram::Path(...)`. Works regardless of the `bundled-cli` feature state; runtime resolution falls through to `Error::BinaryNotFound` unless an applicable explicit source resolves. ### Resolution priority -`Client::start` resolves the CLI in this order: +For managed child-process transports, `Client::start` resolves the program in this order: 1. Explicit `CliProgram::Path(path)` on `ClientOptions::program`. 2. `COPILOT_CLI_PATH` environment variable, if it points at a real file. -3. **`bundled-cli` on:** the embedded archive, lazily extracted on first call. -4. **`bundled-cli` off:** the build-time-extracted binary in the per-user cache, located by recomputing the convention from `COPILOT_SDK_CLI_VERSION` + OS + optional `COPILOT_CLI_EXTRACT_DIR`. +3. **`bundled-cli` on:** the embedded wrapper pair, lazily extracted on first call. +4. **`bundled-cli` off:** the build-time-extracted wrapper pair in the per-user cache. -There is no PATH scanning. If none of the above resolves, `Client::start` returns `Error::BinaryNotFound`. +In-process transport resolves the compatible CLI artifact from +`COPILOT_CLI_PATH`, the embedded archive, or the build-time cache. There is no +PATH scanning. ### Reaching the bundled binary without a `Client` @@ -926,12 +1040,24 @@ if HAS_BUNDLED_CLI { } ``` -This returns the same path `Client::start` would resolve to for -`CliProgram::Resolve` with no `COPILOT_CLI_PATH` override and no -`ClientOptions::bundled_cli_extract_dir` configured. It returns `None` -when `bundled-cli` is off or the target is unsupported, and (unlike the -full resolver) does not fall back to the build-time-extracted dev-cache -path. +This returns the bundled CLI artifact, preserving the public API's original +meaning. Managed child-process transports resolve `copilot-runtime` instead. +The function returns `None` when `bundled-cli` is off or the target is +unsupported and does not fall back to the build-time extraction cache. + +Use [`install_bundled_runtime`] when a health check or intermediate launcher +needs the managed runtime executable: + +```rust,no_run +use github_copilot_sdk::install_bundled_runtime; + +if let Some(path) = install_bundled_runtime() { + println!("bundled runtime at {}", path.display()); +} +``` + +This extracts `copilot-runtime` together with adjacent `runtime.node`, then +returns the wrapper path. ### Download cache (build-time, embed mode) @@ -945,8 +1071,8 @@ Supported: `darwin-arm64`, `darwin-x64`, `linux-x64`, `linux-arm64`, `win32-x64` | Feature | Default | Description | | ------- | ------- | ----------- | -| `bundled-cli` | ✓ | Embeds only the CLI executable. Disable via `default-features = false` when supplying the CLI via `CliProgram::Path` or `COPILOT_CLI_PATH`. | -| `bundled-in-process` | — | Enables `Transport::InProcess`, implies `bundled-cli`, and additionally embeds only the platform-native runtime library. | +| `bundled-cli` | ✓ | Embeds the managed wrapper pair and compatible CLI artifact. Disable via `default-features = false` when supplying the runtime explicitly. | +| `bundled-in-process` | — | Enables `Transport::InProcess`, implies `bundled-cli`, and additionally embeds the platform-native runtime library. | | `derive` | — | `schema_for::()` for generating JSON Schema from Rust types (adds `schemars`). | ```toml diff --git a/rust/RELEASING.md b/rust/RELEASING.md index de0252de8b..06e362f54e 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/build.rs b/rust/build.rs index d04cf2870b..c01464bb4a 100644 --- a/rust/build.rs +++ b/rust/build.rs @@ -1,11 +1,6 @@ -#[cfg(feature = "bundled-in-process")] #[path = "build/in_process.rs"] mod implementation; -#[cfg(not(feature = "bundled-in-process"))] -#[path = "build/out_of_process.rs"] -mod implementation; - fn main() { implementation::main(); } diff --git a/rust/build/in_process.rs b/rust/build/in_process.rs index 5826fbfa76..c01e7ffc4f 100644 --- a/rust/build/in_process.rs +++ b/rust/build/in_process.rs @@ -2,7 +2,6 @@ use std::io::{Read, Write}; use std::path::{Path, PathBuf}; use std::time::Duration; -use base64::Engine; use sha2::Digest; pub(crate) fn main() { @@ -14,20 +13,20 @@ pub(crate) fn main() { println!("cargo::rustc-check-cfg=cfg(has_extracted_cli)"); println!("cargo:rerun-if-changed=cli-version-in-process.txt"); - // Only declare the lockfile rerun when the lockfile actually exists. + // Only declare the package metadata rerun when it actually exists. // Cargo treats `rerun-if-changed` for a missing path as "always rerun" // — so unconditionally declaring this on consumers without a sibling // `nodejs/` (vendored slots, published crates) would force build.rs // to re-run on every `cargo build` even when nothing has changed. - // The lockfile path is only the source-of-truth in this repo's + // The package file is only the source-of-truth in this repo's // contributor builds; everywhere else `cli-version-in-process.txt` is canonical. let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is set"); - let lockfile = Path::new(&manifest_dir) + let package_json = Path::new(&manifest_dir) .join("..") .join("nodejs") - .join("package-lock.json"); - if lockfile.is_file() { - println!("cargo:rerun-if-changed={}", lockfile.display()); + .join("package.json"); + if package_json.is_file() { + println!("cargo:rerun-if-changed={}", package_json.display()); } // Hard opt-out: disable the entire download / bundle / cache mechanism @@ -40,7 +39,7 @@ pub(crate) fn main() { // path source resolves first. if std::env::var_os("COPILOT_SKIP_CLI_DOWNLOAD").is_some() { println!( - "cargo:warning=COPILOT_SKIP_CLI_DOWNLOAD is set — skipping CLI download/bundle/cache" + "cargo:warning=COPILOT_SKIP_CLI_DOWNLOAD is set — skipping runtime download/bundle/cache" ); return; } @@ -60,17 +59,13 @@ pub(crate) fn main() { let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR is always set by cargo"); let out = Path::new(&out_dir); - // Resolve version + npm integrity from one of two sources, in order: + // Resolve version and, when available locally, the release SHA-256 from + // one of two sources, in order: // 1. `cli-version-in-process.txt` snapshot at the crate root (published-crate - // consumer; generated by the publish workflow). Combined format: - // `version=X` line + per-package integrity lines. Committing these - // makes the publish workflow the trust boundary — an attacker who - // later re-points the release tag can't silently poison consumer - // builds. - // 2. Sibling `../nodejs/package-lock.json` (contributor build inside - // the github/copilot-sdk repo), whose platform-package integrity is - // the same trust source npm uses. - let (version, expected_integrity) = resolve_version_and_integrity(platform.package_name); + // consumer; generated by the publish workflow from SHA256SUMS.txt). + // 2. Sibling `../nodejs/package.json` plus the release SHA256SUMS.txt + // (contributor build inside the github/copilot-sdk repo). + let (version, local_expected_hash) = resolve_version_and_optional_hash(platform.package_name); // Bake the version into the crate regardless of mode. This is the // single source of truth for "what CLI version did build.rs target", @@ -81,10 +76,13 @@ pub(crate) fn main() { // `target/` reuse stays cache-coherent. println!("cargo:rustc-env=COPILOT_SDK_CLI_VERSION={version}"); - let archive_name = format!("{}-{version}.tgz", platform.package_name); + let asset_platform = platform + .package_name + .strip_prefix("copilot-") + .expect("platform package names start with copilot-"); + let archive_name = format!("github-copilot-{version}-{asset_platform}.tgz"); let download_url = format!( - "https://registry.npmjs.org/@github/{}/-/{}", - platform.package_name, archive_name + "https://github.com/github/copilot-cli/releases/download/v{version}/{archive_name}" ); let cache_dir = std::env::var("BUNDLED_CLI_CACHE_DIR") .ok() @@ -94,39 +92,72 @@ pub(crate) fn main() { let include_runtime = std::env::var_os("CARGO_FEATURE_BUNDLED_IN_PROCESS").is_some(); if std::env::var_os("CARGO_FEATURE_BUNDLED_CLI").is_some() { - let archive = cached_download(&download_url, &cache_key, &expected_integrity, &cache_dir); - verify_binary_present_in_archive(&archive, platform.binary_name, &archive_name); + let expected_hash = local_expected_hash + .clone() + .unwrap_or_else(|| fetch_in_process_release_hash(&version, platform.package_name)); + let archive = cached_download(&download_url, &cache_key, &expected_hash, &cache_dir); + verify_runtime_package(&archive, platform, &archive_name); emit_embedded(out, &archive, platform, include_runtime); println!("cargo:rustc-cfg=has_bundled_cli"); } else { - // With `bundled-cli` off the extracted binary *is* the cache. - // Skip the upstream download entirely when it already exists at - // the expected path. No two separate caches. + // With `bundled-cli` off the extracted runtime pair *is* the cache. + // Skip the upstream download entirely when both files already exist. // - // Runtime resolution (see `src/resolve.rs::extracted_cli_path`) + // Runtime resolution (see `src/resolve.rs::extracted_program`) // recomputes this same path from `COPILOT_SDK_CLI_VERSION` + the // OS-derived binary name + optional `COPILOT_CLI_EXTRACT_DIR`, // so we don't bake an absolute path into the crate. let install_dir = extracted_install_dir(&version); - let final_path = install_dir.join(platform.binary_name); - - // Invalidate build.rs whenever the cached binary disappears (cache GC, - // manual rm, OS reset, switching extract dir). Without this, cargo + let required_paths = [ + install_dir.join(platform.runtime_wrapper_name()), + install_dir.join("runtime.node"), + install_dir.join(".hostless-runtime-assets-v1"), + ]; + // Invalidate build.rs whenever either cached artifact disappears (cache + // GC, manual rm, OS reset, switching extract dir). Without this, cargo // replays the saved `has_extracted_cli` cfg from its build-script // output cache even when the file is gone, and runtime resolution // fails with BinaryNotFound. - println!("cargo:rerun-if-changed={}", final_path.display()); + for path in &required_paths { + println!("cargo:rerun-if-changed={}", path.display()); + } - if !final_path.is_file() { - let archive = - cached_download(&download_url, &cache_key, &expected_integrity, &cache_dir); - verify_binary_present_in_archive(&archive, platform.binary_name, &archive_name); - extract_to_cache(&archive, &install_dir, platform); + let marker = std::fs::read_to_string(&required_paths[2]).ok(); + let cache_is_current = required_paths.iter().all(|path| path.is_file()) + && match local_expected_hash.as_deref() { + Some(expected_hash) => { + marker.as_deref() == Some(&format!("{version}\n{expected_hash}\n")) + } + None => marker + .as_deref() + .is_some_and(|contents| marker_matches_version(contents, &version)), + }; + if !cache_is_current { + let expected_hash = local_expected_hash + .unwrap_or_else(|| fetch_in_process_release_hash(&version, platform.package_name)); + let expected_marker = format!("{version}\n{expected_hash}\n"); + if install_dir.exists() { + std::fs::remove_dir_all(&install_dir).unwrap_or_else(|e| { + panic!( + "failed to clear stale runtime bundle {}: {e}", + install_dir.display() + ) + }); + } + let archive = cached_download(&download_url, &cache_key, &expected_hash, &cache_dir); + verify_runtime_package(&archive, platform, &archive_name); + extract_to_cache( + &archive, + &install_dir, + platform, + include_runtime, + &expected_marker, + ); } // Re-check after potential download+extract above; not an `else` // because we need to verify the extraction actually produced the file. - if final_path.is_file() { + if required_paths.iter().all(|path| path.is_file()) { println!("cargo:rustc-cfg=has_extracted_cli"); } } @@ -176,19 +207,9 @@ fn build_embedded_archive(package: &[u8], platform: Platform, include_runtime: b .mtime(0) .write(Vec::new(), flate2::Compression::default()); let mut archive = tar::Builder::new(encoder); - append_archive_file( - &mut archive, - platform.binary_name, - &extract_binary_bytes(package, platform), - 0o755, - ); + let (runtime, wrapper) = append_hostless_runtime_tree(&mut archive, package, platform); + append_archive_file(&mut archive, platform.binary_name, &wrapper, 0o755); if include_runtime { - let runtime = extract_runtime_library_bytes(package).unwrap_or_else(|| { - panic!( - "package `{}` does not contain the native runtime library required by the `bundled-in-process` feature", - platform.package_name - ) - }); append_archive_file( &mut archive, platform.runtime_library_name(), @@ -204,6 +225,113 @@ fn build_embedded_archive(package: &[u8], platform: Platform, include_runtime: b .expect("failed to compress minimal embedded CLI archive") } +fn append_hostless_runtime_tree( + archive: &mut tar::Builder, + package: &[u8], + platform: Platform, +) -> (Vec, Vec) { + let decoder = flate2::read::GzDecoder::new(package); + let mut source = tar::Archive::new(decoder); + let mut runtime = None; + let mut wrapper = None; + for entry in source + .entries() + .unwrap_or_else(|e| panic!("failed to read npm package entries: {e}")) + { + let mut entry = entry.unwrap_or_else(|e| panic!("failed to read npm package entry: {e}")); + if !entry.header().entry_type().is_file() { + continue; + } + let source_path = entry + .path() + .unwrap_or_else(|e| panic!("failed to read npm package path: {e}")); + let Some(destination) = hostless_runtime_path(&source_path.to_string_lossy(), platform) + else { + continue; + }; + let mut bytes = Vec::with_capacity(entry.size() as usize); + entry + .read_to_end(&mut bytes) + .unwrap_or_else(|e| panic!("failed to read npm package entry bytes: {e}")); + let mode = entry.header().mode().unwrap_or(0o644); + if destination == Path::new("runtime.node") { + runtime = Some(bytes.clone()); + } + if destination == Path::new(platform.runtime_wrapper_name()) { + wrapper = Some(bytes.clone()); + } + append_archive_file( + archive, + destination + .to_str() + .expect("npm package paths are valid UTF-8"), + &bytes, + mode, + ); + } + ( + runtime.unwrap_or_else(|| { + panic!( + "package `{}` does not contain prebuilds//runtime.node", + platform.package_name + ) + }), + wrapper.unwrap_or_else(|| { + panic!( + "package `{}` does not contain prebuilds//{}", + platform.package_name, + platform.runtime_wrapper_name() + ) + }), + ) +} + +fn hostless_runtime_path(source: &str, platform: Platform) -> Option { + let relative = source.strip_prefix("package/")?; + let parts: Vec<&str> = relative.split('/').collect(); + if parts.iter().any(|part| part.is_empty() || *part == "..") { + return None; + } + let top_level = *parts.first()?; + let file_name = *parts.last()?; + const EXCLUDED_TOP_LEVEL: &[&str] = &[ + "app.js", + "assets", + "changelog.json", + "foundry-local-sdk", + "index.js", + "LICENSE.md", + "napi-oop-runtime", + "npm-loader.js", + "package.json", + "pvrecorder", + "queries", + "README.md", + "sea-loader.js", + "webview", + ]; + if EXCLUDED_TOP_LEVEL.contains(&top_level) + || (top_level.starts_with("tree-sitter") && top_level.ends_with(".wasm")) + || (top_level.starts_with("voice-") && top_level.ends_with(".js")) + || file_name == "cli-native.node" + || parts.contains(&"mediaremote-adapter") + || file_name.starts_with("copilot-runtime-bin") + { + return None; + } + if top_level == "prebuilds" { + let npm_platform = platform + .package_name + .strip_prefix("copilot-") + .expect("platform package name has copilot- prefix"); + if parts.get(1) != Some(&npm_platform) || parts.len() < 3 { + return None; + } + return Some(parts[2..].iter().copied().collect()); + } + Some(parts.iter().copied().collect()) +} + fn append_archive_file( archive: &mut tar::Builder, path: &str, @@ -222,10 +350,10 @@ fn append_archive_file( .unwrap_or_else(|e| panic!("failed to add `{path}` to embedded CLI archive: {e}")); } -/// Resolve the CLI version and npm integrity for the current target's -/// platform package. Picks one of two sources in order. Panics with a clear -/// error if neither is available. -fn resolve_version_and_integrity(package_name: &str) -> (String, String) { +/// Resolve the CLI version and any locally snapshotted release hash for the +/// current target's platform package. Contributor builds defer fetching the +/// checksum until a download is actually required. +fn resolve_version_and_optional_hash(package_name: &str) -> (String, Option) { let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is set"); // 1. Snapshot file at the crate root (published-crate consumer, @@ -234,17 +362,19 @@ fn resolve_version_and_integrity(package_name: &str) -> (String, String) { if snapshot.is_file() { let contents = std::fs::read_to_string(&snapshot) .unwrap_or_else(|e| panic!("failed to read {}: {e}", snapshot.display())); - return parse_snapshot(&contents, package_name) + let (version, hash) = parse_snapshot(&contents, package_name) .unwrap_or_else(|e| panic!("invalid {}: {e}", snapshot.display())); + return (version, Some(hash)); } - // 2. Lockfile fallback (contributor build inside github/copilot-sdk). - let lockfile = Path::new(&manifest_dir) + // 2. Package version plus release checksums (contributor build). + let package_json = Path::new(&manifest_dir) .join("..") .join("nodejs") - .join("package-lock.json"); - if lockfile.is_file() { - return read_version_and_integrity_from_package_lock(&lockfile, package_name); + .join("package.json"); + if package_json.is_file() { + let version = read_version_from_package_json(&package_json); + return (version, None); } panic!( @@ -253,19 +383,36 @@ fn resolve_version_and_integrity(package_name: &str) -> (String, String) { - {} (missing)\n\ - {} (missing)\n\ In a published crate or vendored slot, `cli-version-in-process.txt` should be present.\n\ - Inside the github/copilot-sdk repo, `../nodejs/package-lock.json` is the source.", + Inside the github/copilot-sdk repo, `../nodejs/package.json` is the version source.", snapshot.display(), - lockfile.display(), + package_json.display(), ); } +fn fetch_in_process_release_hash(version: &str, package_name: &str) -> String { + let platform = package_name + .strip_prefix("copilot-") + .expect("platform package names start with copilot-"); + let asset_name = format!("github-copilot-{version}-{platform}.tgz"); + fetch_release_hash(version, &asset_name) +} + +fn marker_matches_version(contents: &str, version: &str) -> bool { + let mut lines = contents.lines(); + lines.next() == Some(version) + && lines.next().is_some_and(|hash| { + hash.len() == 64 && hash.bytes().all(|byte| byte.is_ascii_hexdigit()) + }) + && lines.next().is_none() +} + /// Parse the `cli-version-in-process.txt` snapshot file. Format is one `key=value` per /// line. The first non-comment line is `version=X.Y.Z`; subsequent lines map -/// platform package name to npm integrity. Blank lines and lines starting with `#` +/// platform package name to SHA-256. Blank lines and lines starting with `#` /// are skipped. fn parse_snapshot(contents: &str, package_name: &str) -> Result<(String, String), String> { let mut version: Option = None; - let mut integrity: Option = None; + let mut hash: Option = None; for (line_no, raw) in contents.lines().enumerate() { let line = raw.trim(); if line.is_empty() || line.starts_with('#') { @@ -279,33 +426,42 @@ fn parse_snapshot(contents: &str, package_name: &str) -> Result<(String, String) }; match key.trim() { "version" => version = Some(value.trim().to_string()), - k if k == package_name => integrity = Some(value.trim().to_string()), + k if k == package_name => hash = Some(value.trim().to_string()), _ => {} } } let version = version.ok_or("missing `version=` line")?; - let integrity = - integrity.ok_or_else(|| format!("missing integrity for package `{package_name}`"))?; - Ok((version, integrity)) + let hash = hash.ok_or_else(|| format!("missing hash for package `{package_name}`"))?; + Ok((version, hash)) } -fn read_version_and_integrity_from_package_lock( - path: &Path, - package_name: &str, -) -> (String, String) { +fn read_version_from_package_json(path: &Path) -> String { let contents = std::fs::read_to_string(path) .unwrap_or_else(|e| panic!("failed to read {}: {e}", path.display())); - let lock: serde_json::Value = serde_json::from_str(&contents) + let package_json: serde_json::Value = serde_json::from_str(&contents) .unwrap_or_else(|e| panic!("failed to parse {}: {e}", path.display())); - let cli_key = "node_modules/@github/copilot"; - let version = lock["packages"][cli_key]["version"] + package_json["copilotCliVersion"] .as_str() - .unwrap_or_else(|| panic!("{cli_key} has no version in {}", path.display())); - let platform_key = format!("node_modules/@github/{package_name}"); - let integrity = lock["packages"][&platform_key]["integrity"] - .as_str() - .unwrap_or_else(|| panic!("{platform_key} has no integrity in {}", path.display())); - (version.to_string(), integrity.to_string()) + .unwrap_or_else(|| panic!("copilotCliVersion is missing in {}", path.display())) + .to_string() +} + +fn fetch_release_hash(version: &str, asset_name: &str) -> String { + let url = format!( + "https://github.com/github/copilot-cli/releases/download/v{version}/SHA256SUMS.txt" + ); + let checksums = download_with_retry(&url); + let checksums = std::str::from_utf8(&checksums).expect("SHA256SUMS.txt is not valid UTF-8"); + find_sha256_for_asset(checksums, asset_name) +} + +fn find_sha256_for_asset(sums: &str, asset_name: &str) -> String { + sums.lines() + .find_map(|line| { + let (hash, name) = line.split_once(char::is_whitespace)?; + (name.trim_start().trim_start_matches('*') == asset_name).then(|| hash.to_string()) + }) + .unwrap_or_else(|| panic!("SHA256SUMS.txt does not contain {asset_name}")) } #[derive(Clone, Copy)] @@ -315,6 +471,14 @@ struct Platform { } impl Platform { + fn runtime_wrapper_name(&self) -> &'static str { + if self.package_name.contains("win32") { + "copilot-runtime.exe" + } else { + "copilot-runtime" + } + } + fn runtime_library_name(&self) -> &'static str { if self.package_name.contains("win32") { "copilot_runtime.dll" @@ -368,8 +532,8 @@ fn target_platform() -> Option { } } -/// Write the single binary entry from `archive` to -/// `/` and return the resulting path. +/// Write the runtime wrapper pair from `archive` to `install_dir` and return +/// the wrapper path. /// Idempotent — returns the existing path if a previous build already /// populated the target. /// @@ -378,15 +542,13 @@ fn target_platform() -> Option { /// binary. `fs::rename` for files is atomic on both Unix and Windows /// (Windows uses `MoveFileExW` with `MOVEFILE_REPLACE_EXISTING`); for /// directories it is not, which is why we stage at file granularity. -fn extract_to_cache(archive: &[u8], install_dir: &Path, platform: Platform) -> PathBuf { - let final_path = install_dir.join(platform.binary_name); - - // Caller already gated on `final_path.is_file()`; this is a safety - // net for any future caller that forgets. - if final_path.is_file() { - return final_path; - } - +fn extract_to_cache( + archive: &[u8], + install_dir: &Path, + platform: Platform, + include_runtime: bool, + marker: &str, +) -> PathBuf { std::fs::create_dir_all(install_dir).unwrap_or_else(|e| { panic!( "failed to create install dir {}: {e}", @@ -394,8 +556,96 @@ fn extract_to_cache(archive: &[u8], install_dir: &Path, platform: Platform) -> P ) }); - let bytes = extract_binary_bytes(archive, platform); + let decoder = flate2::read::GzDecoder::new(archive); + let mut source = tar::Archive::new(decoder); + let mut runtime = None; + for entry in source + .entries() + .unwrap_or_else(|e| panic!("failed to read npm package entries: {e}")) + { + let mut entry = entry.unwrap_or_else(|e| panic!("failed to read npm package entry: {e}")); + if !entry.header().entry_type().is_file() { + continue; + } + let source_path = entry + .path() + .unwrap_or_else(|e| panic!("failed to read npm package path: {e}")); + let Some(destination) = hostless_runtime_path(&source_path.to_string_lossy(), platform) + else { + continue; + }; + if destination == Path::new(platform.binary_name) { + continue; + } + let mut bytes = Vec::with_capacity(entry.size() as usize); + entry + .read_to_end(&mut bytes) + .unwrap_or_else(|e| panic!("failed to read npm package entry bytes: {e}")); + let executable = entry.header().mode().unwrap_or(0o644) & 0o111 != 0; + if destination == Path::new("runtime.node") { + runtime = Some(bytes.clone()); + } + install_cached_file_path(install_dir, &destination, &bytes, executable); + } + let runtime = runtime.expect("verified runtime.node is present"); + if include_runtime { + install_cached_file( + install_dir, + platform.runtime_library_name(), + &runtime, + false, + ); + } + install_cached_file( + install_dir, + ".hostless-runtime-assets-v1", + marker.as_bytes(), + false, + ); + + let final_path = install_dir.join(platform.runtime_wrapper_name()); + println!( + "cargo:warning=Extracted Copilot runtime bundle to {}", + install_dir.display() + ); + final_path +} + +fn install_cached_file(install_dir: &Path, file_name: &str, bytes: &[u8], executable: bool) { + install_cached_file_path(install_dir, Path::new(file_name), bytes, executable); +} +fn install_cached_file_path( + install_dir: &Path, + relative_path: &Path, + bytes: &[u8], + executable: bool, +) { + // `executable` only affects file permissions on Unix (see the `#[cfg(unix)]` + // block below); explicitly mark it used elsewhere so non-Unix targets don't + // warn about an unused parameter under `-D warnings`. + #[cfg(not(unix))] + let _ = executable; + + assert!( + !relative_path.is_absolute() + && !relative_path.components().any(|component| { + matches!( + component, + std::path::Component::Prefix(_) + | std::path::Component::RootDir + | std::path::Component::ParentDir + ) + }), + "unsafe runtime package path: {}", + relative_path.display() + ); + let final_path = install_dir.join(relative_path); + if final_path.is_file() { + return; + } + std::fs::create_dir_all(final_path.parent().expect("runtime asset has parent")) + .unwrap_or_else(|e| panic!("failed to create runtime asset directory: {e}")); // Staging file is a sibling of the final binary so the rename stays // on the same filesystem (cross-fs rename is not atomic). PID + nanos // disambiguate concurrent builds racing on the same cache. @@ -405,7 +655,10 @@ fn extract_to_cache(archive: &[u8], install_dir: &Path, platform: Platform) -> P .unwrap_or(0); let staging_path = install_dir.join(format!( ".{}.staging-{}-{nanos}", - platform.binary_name, + relative_path + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or("runtime-asset"), std::process::id(), )); @@ -418,7 +671,7 @@ fn extract_to_cache(archive: &[u8], install_dir: &Path, platform: Platform) -> P ); }); - if let Err(e) = f.write_all(&bytes) { + if let Err(e) = f.write_all(bytes) { let _ = std::fs::remove_file(&staging_path); panic!( "failed to write staging file {}: {e}", @@ -427,7 +680,7 @@ fn extract_to_cache(archive: &[u8], install_dir: &Path, platform: Platform) -> P } #[cfg(unix)] - { + if executable { use std::os::unix::fs::PermissionsExt; if let Err(e) = f.set_permissions(std::fs::Permissions::from_mode(0o755)) { let _ = std::fs::remove_file(&staging_path); @@ -472,32 +725,6 @@ fn extract_to_cache(archive: &[u8], install_dir: &Path, platform: Platform) -> P final_path.display() ); } - - // Surface where the binary landed so contributors can find it. Quiet - // on the hot path: the caller's `is_file()` short-circuit (and the - // safety net at the top of this function) means this only fires on a - // true cache miss. - println!( - "cargo:warning=Extracted Copilot CLI to {}", - final_path.display() - ); - - final_path -} - -fn extract_runtime_library_bytes(archive: &[u8]) -> Option> { - let gz = flate2::read::GzDecoder::new(archive); - let mut tar = tar::Archive::new(gz); - for entry in tar.entries().ok()? { - let mut entry = entry.ok()?; - let name = entry.path().ok()?.to_string_lossy().into_owned(); - if name == "runtime.node" || name.ends_with("/runtime.node") { - let mut bytes = Vec::with_capacity(entry.size() as usize); - entry.read_to_end(&mut bytes).ok()?; - return Some(bytes); - } - } - None } /// Replace characters outside `[a-zA-Z0-9._-]` with `_` so the version @@ -514,52 +741,21 @@ fn sanitize_version(version: &str) -> String { .collect() } -/// Extract the single `binary_name` entry from the npm package archive. Reused -/// between embed mode's `verify_binary_present_in_archive` and the -/// `extract_to_cache` path used when `bundled-cli` is off. Panics if the -/// entry isn't found — callers have already invoked -/// `verify_binary_present_in_archive`. -fn extract_binary_bytes(archive: &[u8], platform: Platform) -> Vec { - let gz = flate2::read::GzDecoder::new(archive); - let mut tar = tar::Archive::new(gz); - for entry in tar - .entries() - .unwrap_or_else(|e| panic!("failed to read tar entries: {e}")) - { - let mut entry = entry.unwrap_or_else(|e| panic!("failed to read tar entry: {e}")); - let path = entry - .path() - .unwrap_or_else(|e| panic!("failed to read tar entry path: {e}")); - let name = path.to_string_lossy().into_owned(); - if name == platform.binary_name || name.ends_with(&format!("/{}", platform.binary_name)) { - let mut bytes = Vec::with_capacity(entry.size() as usize); - entry - .read_to_end(&mut bytes) - .unwrap_or_else(|e| panic!("failed to read tar entry bytes: {e}")); - return bytes; - } - } - panic!( - "binary `{}` not found in package `{}`", - platform.binary_name, platform.package_name - ); -} - /// Read a file from the download cache, or download it (with retries) and save -/// to cache. Verifies npm integrity on every path. Evicts stale/corrupt cache entries +/// to cache. Verifies SHA-256 on every path. Evicts stale/corrupt cache entries /// automatically. Cache I/O failures are treated as cache misses — they never /// break the build. fn cached_download( url: &str, cache_key: &str, - expected_integrity: &str, + expected_hash: &str, cache_dir: &Option, ) -> Vec { if let Some(dir) = cache_dir { let cached_path = dir.join(cache_key); if cached_path.is_file() { match std::fs::read(&cached_path) { - Ok(data) if verify_integrity(&data, expected_integrity) => { + Ok(data) if verify_hash(&data, expected_hash) => { // Silent cache hit — nothing to surface. return data; } @@ -579,9 +775,9 @@ fn cached_download( println!("cargo:warning=Downloading {url}"); let data = download_with_retry(url); - if !verify_integrity(&data, expected_integrity) { + if !verify_hash(&data, expected_hash) { panic!( - "Archive integrity check failed for {url}!\n expected: {expected_integrity}\n \ + "Archive integrity check failed for {url}!\n expected: {expected_hash}\n \ This could indicate a corrupted download or a supply-chain attack." ); } @@ -682,15 +878,13 @@ fn try_download(url: &str) -> Result, DownloadError> { } } -/// Walks the downloaded archive at build time to confirm an entry matching -/// `binary_name` exists. Panics with a clear message if not. -fn verify_binary_present_in_archive(archive: &[u8], binary_name: &str, package_name: &str) { - let found = archive_contains_tar_entry(archive, binary_name); - if !found { +fn verify_runtime_package(archive: &[u8], platform: Platform, package_name: &str) { + for file_name in ["runtime.node", platform.runtime_wrapper_name()] { + if archive_contains_tar_entry(archive, file_name) { + continue; + } panic!( - "Copilot CLI package `{package_name}` does not contain an entry named `{binary_name}`. \ - The package layout may have changed; runtime extraction would fail. \ - Update `verify_binary_present_in_archive` in build.rs and the matching `extract_binary` in src/embeddedcli.rs." + "Copilot runtime package `{package_name}` does not contain an entry named `{file_name}`" ); } } @@ -713,14 +907,8 @@ fn archive_contains_tar_entry(targz: &[u8], binary_name: &str) -> bool { false } -fn verify_integrity(data: &[u8], integrity: &str) -> bool { - let Some(encoded) = integrity.strip_prefix("sha512-") else { - return false; - }; - let Ok(expected) = base64::engine::general_purpose::STANDARD.decode(encoded) else { - return false; - }; - let mut hasher = sha2::Sha512::new(); +fn verify_hash(data: &[u8], expected: &str) -> bool { + let mut hasher = sha2::Sha256::new(); hasher.update(data); - hasher.finalize().as_slice() == expected + format!("{:x}", hasher.finalize()) == expected } diff --git a/rust/build/out_of_process.rs b/rust/build/out_of_process.rs index b8cd3acc3f..c0a9dd3050 100644 --- a/rust/build/out_of_process.rs +++ b/rust/build/out_of_process.rs @@ -13,20 +13,20 @@ pub(crate) fn main() { println!("cargo::rustc-check-cfg=cfg(has_extracted_cli)"); println!("cargo:rerun-if-changed=cli-version.txt"); - // Only declare the lockfile rerun when the lockfile actually exists. + // Only declare the package metadata rerun when it actually exists. // Cargo treats `rerun-if-changed` for a missing path as "always rerun" // — so unconditionally declaring this on consumers without a sibling // `nodejs/` (vendored slots, published crates) would force build.rs // to re-run on every `cargo build` even when nothing has changed. - // The lockfile path is only the source-of-truth in this repo's + // The package file is only the source-of-truth in this repo's // contributor builds; everywhere else `cli-version.txt` is canonical. let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is set"); - let lockfile = Path::new(&manifest_dir) + let package_json = Path::new(&manifest_dir) .join("..") .join("nodejs") - .join("package-lock.json"); - if lockfile.is_file() { - println!("cargo:rerun-if-changed={}", lockfile.display()); + .join("package.json"); + if package_json.is_file() { + println!("cargo:rerun-if-changed={}", package_json.display()); } // Hard opt-out: disable the entire download / bundle / cache mechanism @@ -61,15 +61,9 @@ pub(crate) fn main() { // Resolve version + per-asset SHA-256 from one of two sources, in order: // 1. `cli-version.txt` snapshot at the crate root (published-crate - // consumer; generated by the publish workflow). Combined format: - // `version=X` line + per-asset hash lines. Committing the hashes - // makes the publish workflow the trust boundary — an attacker who - // later re-points the release tag can't silently poison consumer - // builds. - // 2. Sibling `../nodejs/package-lock.json` (contributor build inside - // the github/copilot-sdk repo; live SHA256SUMS.txt fetch). Matches - // the .NET `_GetCopilotCliVersion` MSBuild target and the Go - // `cmd/bundler` tool. + // consumer; generated by the publish workflow from SHA256SUMS.txt). + // 2. Sibling `../nodejs/package.json` plus the release SHA256SUMS.txt + // (contributor build inside the github/copilot-sdk repo). let (version, expected_hash) = resolve_version_and_hash(platform.asset_name); // Bake the version into the crate regardless of mode. This is the @@ -194,15 +188,14 @@ fn resolve_version_and_hash(asset_name: &str) -> (String, String) { .unwrap_or_else(|e| panic!("invalid {}: {e}", snapshot.display())); } - // 2. Lockfile fallback (contributor build inside github/copilot-sdk) — - // read version, fetch live SHA256SUMS. - let lockfile = Path::new(&manifest_dir) + // 2. Package version plus release checksums (contributor build). + let package_json = Path::new(&manifest_dir) .join("..") .join("nodejs") - .join("package-lock.json"); - if lockfile.is_file() { - let version = read_version_from_package_lock(&lockfile); - let hash = fetch_live_sha256(&version, asset_name); + .join("package.json"); + if package_json.is_file() { + let version = read_version_from_package_json(&package_json); + let hash = fetch_release_hash(&version, asset_name); return (version, hash); } @@ -212,9 +205,9 @@ fn resolve_version_and_hash(asset_name: &str) -> (String, String) { - {} (missing)\n\ - {} (missing)\n\ In a published crate or vendored slot, `cli-version.txt` should be present.\n\ - Inside the github/copilot-sdk repo, `../nodejs/package-lock.json` is the source.", + Inside the github/copilot-sdk repo, `../nodejs/package.json` is the version source.", snapshot.display(), - lockfile.display(), + package_json.display(), ); } @@ -247,39 +240,33 @@ fn parse_snapshot(contents: &str, asset_name: &str) -> Result<(String, String), Ok((version, hash)) } -/// Read the `@github/copilot` version from `nodejs/package-lock.json`. -fn read_version_from_package_lock(path: &Path) -> String { +fn read_version_from_package_json(path: &Path) -> String { let contents = std::fs::read_to_string(path) .unwrap_or_else(|e| panic!("failed to read {}: {e}", path.display())); - // Minimal JSON walk: find `"node_modules/@github/copilot"` object and - // its `"version"` field. Full JSON parsing keeps build.rs dep-light by - // using a regex; the file is generated by npm and we're matching an - // exact key path. - let key = "\"node_modules/@github/copilot\""; - let key_pos = contents - .find(key) - .unwrap_or_else(|| panic!("{} does not contain {key}", path.display())); - let after_key = &contents[key_pos + key.len()..]; - let version_key = "\"version\""; - let v_pos = after_key - .find(version_key) - .unwrap_or_else(|| panic!("no `version` field found near {key} in {}", path.display())); - let after_v = &after_key[v_pos + version_key.len()..]; - let q1 = after_v.find('"').expect("malformed version"); - let after_q1 = &after_v[q1 + 1..]; - let q2 = after_q1.find('"').expect("malformed version"); - after_q1[..q2].to_string() + let package_json: serde_json::Value = serde_json::from_str(&contents) + .unwrap_or_else(|e| panic!("failed to parse {}: {e}", path.display())); + package_json["copilotCliVersion"] + .as_str() + .unwrap_or_else(|| panic!("copilotCliVersion is missing in {}", path.display())) + .to_string() } -/// Fetch the live `SHA256SUMS.txt` for the given version from GitHub Releases -/// and pluck out the entry for `asset_name`. -fn fetch_live_sha256(version: &str, asset_name: &str) -> String { - let base_url = format!("https://github.com/github/copilot-cli/releases/download/v{version}"); - let checksums_url = format!("{base_url}/SHA256SUMS.txt"); - let checksums = download_with_retry(&checksums_url); - let checksums_text = - std::str::from_utf8(&checksums).expect("checksums file is not valid UTF-8"); - find_sha256_for_asset(checksums_text, asset_name) +fn fetch_release_hash(version: &str, asset_name: &str) -> String { + let url = + format!("https://github.com/github/copilot-cli/releases/download/v{version}/SHA256SUMS.txt"); + let checksums = download_with_retry(&url); + let checksums = + std::str::from_utf8(&checksums).expect("SHA256SUMS.txt is not valid UTF-8"); + find_sha256_for_asset(checksums, asset_name) +} + +fn find_sha256_for_asset(sums: &str, asset_name: &str) -> String { + sums.lines() + .find_map(|line| { + let (hash, name) = line.split_once(char::is_whitespace)?; + (name.trim_start().trim_start_matches('*') == asset_name).then(|| hash.to_string()) + }) + .unwrap_or_else(|| panic!("SHA256SUMS.txt does not contain {asset_name}")) } #[derive(Clone, Copy)] @@ -641,18 +628,6 @@ fn try_download(url: &str) -> Result, DownloadError> { } } -fn find_sha256_for_asset(sums: &str, asset_name: &str) -> String { - for line in sums.lines() { - // Format: " " (two spaces) - if let Some((hash, name)) = line.split_once(" ") - && name.trim() == asset_name - { - return hash.trim().to_string(); - } - } - panic!("SHA256SUMS.txt does not contain an entry for {asset_name}"); -} - fn sha256(data: &[u8]) -> [u8; 32] { let mut hasher = sha2::Sha256::new(); hasher.update(data); diff --git a/rust/examples/manual_tool_resume.rs b/rust/examples/manual_tool_resume.rs index 85add798a1..ad8ad5a044 100644 --- a/rust/examples/manual_tool_resume.rs +++ b/rust/examples/manual_tool_resume.rs @@ -113,6 +113,7 @@ 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, diff --git a/rust/scripts/snapshot-bundled-cli-version.sh b/rust/scripts/snapshot-bundled-cli-version.sh index 7f78d529b0..08b19ebc2b 100755 --- a/rust/scripts/snapshot-bundled-cli-version.sh +++ b/rust/scripts/snapshot-bundled-cli-version.sh @@ -5,10 +5,9 @@ # how .NET's _GenerateVersionProps BeforeTargets="Pack" target writes # GitHub.Copilot.SDK.props before NuGet packing. # -# Inputs: -# - ../nodejs/package-lock.json (sibling) - source of the pinned version. -# - https://github.com/github/copilot-cli/releases/v{version}/SHA256SUMS.txt - -# authoritative per-platform hashes. +# Input: +# - ../nodejs/package.json - pinned release version. +# - The release's SHA256SUMS.txt - per-platform hashes. # # Output: # - cli-version.txt (in the rust crate root). Gitignored. @@ -18,23 +17,21 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" RUST_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" REPO_ROOT="$(cd "${RUST_DIR}/.." && pwd)" -LOCKFILE="${REPO_ROOT}/nodejs/package-lock.json" +PACKAGE_FILE="${REPO_ROOT}/nodejs/package.json" OUTPUT="${RUST_DIR}/cli-version.txt" -if [[ ! -f "${LOCKFILE}" ]]; then - echo "error: ${LOCKFILE} not found" >&2 +if [[ ! -f "${PACKAGE_FILE}" ]]; then + echo "error: ${PACKAGE_FILE} not found" >&2 exit 1 fi -VERSION="$(node -e "console.log(require('${LOCKFILE}').packages['node_modules/@github/copilot'].version)")" +VERSION="$(node -e "console.log(require('${PACKAGE_FILE}').copilotCliVersion)")" if [[ -z "${VERSION}" ]]; then - echo "error: could not read @github/copilot version from ${LOCKFILE}" >&2 + echo "error: could not read copilotCliVersion from ${PACKAGE_FILE}" >&2 exit 1 fi - CHECKSUMS_URL="https://github.com/github/copilot-cli/releases/download/v${VERSION}/SHA256SUMS.txt" -echo "Fetching ${CHECKSUMS_URL}" -SHA256SUMS="$(curl -fsSL --retry 3 --retry-delay 2 "${CHECKSUMS_URL}")" +SHA256SUMS="$(curl --fail --silent --show-error --location --retry 3 "${CHECKSUMS_URL}")" ASSETS=( "copilot-darwin-arm64.tar.gz" @@ -45,23 +42,22 @@ ASSETS=( "copilot-win32-x64.zip" ) -declare -A HASHES -for asset in "${ASSETS[@]}"; do - hash="$(printf '%s\n' "${SHA256SUMS}" | awk -v a="${asset}" '$2 == a { print $1 }')" - if [[ -z "${hash}" ]]; then - echo "error: SHA256SUMS.txt missing entry for ${asset}" >&2 - exit 1 - fi - HASHES[$asset]="${hash}" -done - +TEMP_OUTPUT="${OUTPUT}.tmp.$$" +trap 'rm -f "${TEMP_OUTPUT}"' EXIT { echo "# Auto-generated by rust/scripts/snapshot-bundled-cli-version.sh" echo "# Do not edit. Regenerated by the publish workflow on every release." echo "version=${VERSION}" for asset in "${ASSETS[@]}"; do - echo "${asset}=${HASHES[$asset]}" + hash="$(printf '%s\n' "${SHA256SUMS}" | awk -v asset="${asset}" '$2 == asset || $2 == "*" asset { print $1; exit }')" + if [[ -z "${hash}" ]]; then + echo "error: SHA256SUMS.txt does not contain ${asset}" >&2 + exit 1 + fi + echo "${asset}=${hash}" done -} > "${OUTPUT}" +} > "${TEMP_OUTPUT}" +mv "${TEMP_OUTPUT}" "${OUTPUT}" +trap - EXIT echo "Wrote ${OUTPUT} (version=${VERSION}, ${#ASSETS[@]} hashes)" \ No newline at end of file diff --git a/rust/scripts/snapshot-bundled-in-process-version.sh b/rust/scripts/snapshot-bundled-in-process-version.sh index 8743f9d17a..9fe2298c78 100755 --- a/rust/scripts/snapshot-bundled-in-process-version.sh +++ b/rust/scripts/snapshot-bundled-in-process-version.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # -# Snapshot the Copilot CLI version + per-platform npm integrity values for the +# Snapshot the Copilot CLI version + per-platform release hashes for the # rust crate's bundled-in-process build path. set -euo pipefail @@ -8,19 +8,21 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" RUST_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" REPO_ROOT="$(cd "${RUST_DIR}/.." && pwd)" -LOCKFILE="${REPO_ROOT}/nodejs/package-lock.json" +PACKAGE_FILE="${REPO_ROOT}/nodejs/package.json" OUTPUT="${RUST_DIR}/cli-version-in-process.txt" -if [[ ! -f "${LOCKFILE}" ]]; then - echo "error: ${LOCKFILE} not found" >&2 +if [[ ! -f "${PACKAGE_FILE}" ]]; then + echo "error: ${PACKAGE_FILE} not found" >&2 exit 1 fi -VERSION="$(node -e "console.log(require('${LOCKFILE}').packages['node_modules/@github/copilot'].version)")" +VERSION="$(node -e "console.log(require('${PACKAGE_FILE}').copilotCliVersion)")" if [[ -z "${VERSION}" ]]; then - echo "error: could not read @github/copilot version from ${LOCKFILE}" >&2 + echo "error: could not read copilotCliVersion from ${PACKAGE_FILE}" >&2 exit 1 fi +CHECKSUMS_URL="https://github.com/github/copilot-cli/releases/download/v${VERSION}/SHA256SUMS.txt" +SHA256SUMS="$(curl --fail --silent --show-error --location --retry 3 "${CHECKSUMS_URL}")" PACKAGES=( "copilot-darwin-arm64" @@ -33,23 +35,24 @@ PACKAGES=( "copilot-win32-x64" ) -declare -A INTEGRITIES -for package in "${PACKAGES[@]}"; do - integrity="$(node -e "console.log(require('${LOCKFILE}').packages['node_modules/@github/${package}'].integrity)")" - if [[ -z "${integrity}" ]]; then - echo "error: package-lock.json missing integrity for @github/${package}" >&2 - exit 1 - fi - INTEGRITIES[$package]="${integrity}" -done - +TEMP_OUTPUT="${OUTPUT}.tmp.$$" +trap 'rm -f "${TEMP_OUTPUT}"' EXIT { echo "# Auto-generated by rust/scripts/snapshot-bundled-in-process-version.sh" echo "# Do not edit. Regenerated by the publish workflow on every release." echo "version=${VERSION}" for package in "${PACKAGES[@]}"; do - echo "${package}=${INTEGRITIES[$package]}" + platform="${package#copilot-}" + asset="github-copilot-${VERSION}-${platform}.tgz" + hash="$(printf '%s\n' "${SHA256SUMS}" | awk -v asset="${asset}" '$2 == asset || $2 == "*" asset { print $1; exit }')" + if [[ -z "${hash}" ]]; then + echo "error: SHA256SUMS.txt does not contain ${asset}" >&2 + exit 1 + fi + echo "${package}=${hash}" done -} > "${OUTPUT}" +} > "${TEMP_OUTPUT}" +mv "${TEMP_OUTPUT}" "${OUTPUT}" +trap - EXIT -echo "Wrote ${OUTPUT} (version=${VERSION}, ${#PACKAGES[@]} integrity values)" +echo "Wrote ${OUTPUT} (version=${VERSION}, ${#PACKAGES[@]} hashes)" diff --git a/rust/src/embeddedcli.rs b/rust/src/embeddedcli.rs index 40900a4d22..3cc527a2e2 100644 --- a/rust/src/embeddedcli.rs +++ b/rust/src/embeddedcli.rs @@ -3,10 +3,10 @@ //! feature set). //! //! Normal builds embed the platform release archive from GitHub Releases. -//! Builds with `bundled-in-process` instead embed a minimal archive from the -//! platform npm package containing the CLI executable and native runtime -//! library. Extraction to a real on-disk path is deferred until the first call -//! to [`path`] / [`install_at`]. +//! Builds with `bundled-in-process` instead embed a filtered archive from the +//! platform npm package containing the CLI executable, runtime wrapper, native +//! runtime artifacts, and auxiliary runtime assets. Extraction to a real +//! on-disk path is deferred until the relevant installer is called. //! //! The embedded bytes are part of the consumer's signed binary and therefore //! trusted *as the source of truth* — but the bytes that land on disk are not. @@ -28,7 +28,7 @@ // off but still needs to exercise them. #[cfg(any(has_bundled_cli, test))] use std::fs; -#[cfg(all(has_bundled_cli, any(feature = "bundled-in-process", not(windows))))] +#[cfg(has_bundled_cli)] use std::io::Read; #[cfg(any(has_bundled_cli, test))] use std::io::Write; @@ -65,9 +65,19 @@ const CLI_VERSION: &str = env!("COPILOT_SDK_CLI_VERSION"); const CLI_BINARY_NAME: &str = "copilot.exe"; #[cfg(all(has_bundled_cli, not(windows)))] const CLI_BINARY_NAME: &str = "copilot"; +#[cfg(all(has_bundled_cli, windows))] +const RUNTIME_BINARY_NAME: &str = "copilot-runtime.exe"; +#[cfg(all(has_bundled_cli, not(windows)))] +const RUNTIME_BINARY_NAME: &str = "copilot-runtime"; +#[cfg(has_bundled_cli)] +const RUNTIME_NODE_NAME: &str = "runtime.node"; +#[cfg(has_bundled_cli)] +const RUNTIME_VERSION_MARKER: &str = ".copilot-runtime-version"; #[cfg(feature = "bundled-cli")] static INSTALLED_PATH: OnceLock> = OnceLock::new(); +#[cfg(feature = "bundled-cli")] +static INSTALLED_RUNTIME_PATH: OnceLock> = OnceLock::new(); /// Returns the path to the installed CLI binary, lazily extracting the /// embedded archive on first call. @@ -91,7 +101,7 @@ pub(crate) fn path() -> Option { #[cfg(has_bundled_cli)] { let dir = default_install_dir(CLI_VERSION); - match install(&dir, build_time::CLI_ARCHIVE) { + match install_cli_bundle(&dir, build_time::CLI_ARCHIVE) { Ok(path) => { info!(path = %path.display(), version = CLI_VERSION, "embedded CLI installed"); return Some(path); @@ -119,7 +129,7 @@ pub(crate) fn path() -> Option { pub(crate) fn install_at(extract_dir: &Path) -> Option { #[cfg(has_bundled_cli)] { - match install(extract_dir, build_time::CLI_ARCHIVE) { + match install_cli_bundle(extract_dir, build_time::CLI_ARCHIVE) { Ok(path) => { info!(path = %path.display(), version = CLI_VERSION, "embedded CLI installed"); return Some(path); @@ -136,6 +146,93 @@ pub(crate) fn install_at(extract_dir: &Path) -> Option { None } +/// Returns the path to the bundled runtime wrapper, extracting the wrapper and +/// adjacent `runtime.node` on first call. +#[cfg(feature = "bundled-cli")] +pub(crate) fn runtime_path() -> Option { + INSTALLED_RUNTIME_PATH + .get_or_init(|| { + #[cfg(has_bundled_cli)] + { + let dir = default_install_dir(CLI_VERSION); + match install_runtime(&dir, build_time::CLI_ARCHIVE) { + Ok(path) => { + info!(path = %path.display(), version = CLI_VERSION, "embedded runtime installed"); + return Some(path); + } + Err(e) => { + warn!(error = %e, "embedded runtime installation failed"); + } + } + } + None + }) + .clone() +} + +/// Installs the bundled runtime wrapper and adjacent `runtime.node` into a +/// caller-specified directory. +#[cfg(feature = "bundled-cli")] +pub(crate) fn install_runtime_at(extract_dir: &Path) -> Option { + #[cfg(has_bundled_cli)] + { + let install_dir = match runtime_install_dir(extract_dir, CLI_VERSION) { + Ok(dir) => dir, + Err(e) => { + warn!(error = %e, "embedded runtime install directory selection failed"); + return None; + } + }; + match install_runtime(&install_dir, build_time::CLI_ARCHIVE) { + Ok(path) => { + info!(path = %path.display(), version = CLI_VERSION, "embedded runtime installed"); + return Some(path); + } + Err(e) => { + warn!(error = %e, "embedded runtime installation failed"); + } + } + } + #[cfg(not(has_bundled_cli))] + { + let _ = extract_dir; + } + None +} + +#[cfg(has_bundled_cli)] +fn runtime_install_dir(base_dir: &Path, version: &str) -> Result { + fs::create_dir_all(base_dir) + .map_err(|e| EmbeddedCliError::new(EmbeddedCliErrorKind::CreateDir, e))?; + let marker = base_dir.join(RUNTIME_VERSION_MARKER); + match fs::OpenOptions::new() + .write(true) + .create_new(true) + .open(&marker) + { + Ok(mut file) => { + if let Err(error) = file + .write_all(version.as_bytes()) + .and_then(|()| file.sync_all()) + { + drop(file); + let _ = fs::remove_file(&marker); + return Err(EmbeddedCliError::new(EmbeddedCliErrorKind::Io, error)); + } + Ok(base_dir.to_path_buf()) + } + Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => { + let installed_version = fs::read_to_string(marker).unwrap_or_default(); + if installed_version == version { + Ok(base_dir.to_path_buf()) + } else { + Ok(base_dir.join(version)) + } + } + Err(error) => Err(EmbeddedCliError::new(EmbeddedCliErrorKind::Io, error)), + } +} + #[cfg(has_bundled_cli)] fn default_install_dir(version: &str) -> PathBuf { let cache = dirs::cache_dir().unwrap_or_else(std::env::temp_dir); @@ -168,34 +265,151 @@ const RUNTIME_LIBRARY_NAME: &str = "libcopilot_runtime.dylib"; const RUNTIME_LIBRARY_NAME: &str = "libcopilot_runtime.so"; #[cfg(has_bundled_cli)] -fn install(install_dir: &Path, archive: &[u8]) -> Result { - let final_path = install_cli(install_dir, archive)?; +fn install_cli_bundle(install_dir: &Path, archive: &[u8]) -> Result { + install_cli(install_dir, archive)?; + install_hostless_assets(install_dir, archive)?; #[cfg(feature = "bundled-in-process")] { install_runtime_library(install_dir, archive)?; } - Ok(final_path) + Ok(install_dir.join(CLI_BINARY_NAME)) +} + +#[cfg(has_bundled_cli)] +fn install_runtime(install_dir: &Path, archive: &[u8]) -> Result { + fs::create_dir_all(install_dir) + .map_err(|e| EmbeddedCliError::new(EmbeddedCliErrorKind::CreateDir, e))?; + install_hostless_assets(install_dir, archive)?; + install_runtime_pair(install_dir, archive)?; + Ok(install_dir.join(RUNTIME_BINARY_NAME)) +} + +#[cfg(has_bundled_cli)] +fn install_hostless_assets(install_dir: &Path, archive: &[u8]) -> Result<(), EmbeddedCliError> { + let gz = flate2::read::GzDecoder::new(archive); + let mut tar = tar::Archive::new(gz); + for entry in tar + .entries() + .map_err(|e| EmbeddedCliError::new(EmbeddedCliErrorKind::Archive, e))? + { + let mut entry = + entry.map_err(|e| EmbeddedCliError::new(EmbeddedCliErrorKind::Archive, e))?; + if !entry.header().entry_type().is_file() { + continue; + } + let path = entry + .path() + .map_err(|e| EmbeddedCliError::new(EmbeddedCliErrorKind::Archive, e))? + .into_owned(); + let file_name = path.file_name().and_then(|name| name.to_str()); + if path == Path::new(CLI_BINARY_NAME) + || matches!( + file_name, + Some("copilot_runtime.dll") + | Some("libcopilot_runtime.dylib") + | Some("libcopilot_runtime.so") + ) + { + continue; + } + if path.is_absolute() + || path.components().any(|component| { + matches!( + component, + std::path::Component::Prefix(_) + | std::path::Component::RootDir + | std::path::Component::ParentDir + ) + }) + { + return Err(EmbeddedCliError::with_message( + EmbeddedCliErrorKind::Archive, + format!("unsafe embedded runtime asset path: {}", path.display()), + )); + } + let mut bytes = Vec::with_capacity(entry.size() as usize); + entry + .read_to_end(&mut bytes) + .map_err(|e| EmbeddedCliError::new(EmbeddedCliErrorKind::Archive, e))?; + let target = install_dir.join(&path); + if fs::read(&target) + .map(|installed| installed == bytes) + .unwrap_or(false) + { + continue; + } + let parent = target.parent().ok_or_else(|| { + EmbeddedCliError::with_message( + EmbeddedCliErrorKind::Archive, + format!("embedded runtime asset has no parent: {}", path.display()), + ) + })?; + fs::create_dir_all(parent) + .map_err(|e| EmbeddedCliError::new(EmbeddedCliErrorKind::CreateDir, e))?; + let tmp = write_temp_file(parent, &bytes)?; + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let mode = entry.header().mode().unwrap_or(0o644) & 0o777; + fs::set_permissions(&tmp, fs::Permissions::from_mode(mode)) + .map_err(|e| EmbeddedCliError::new(EmbeddedCliErrorKind::Io, e))?; + } + if let Err(error) = publish(&tmp, &target) { + let _ = fs::remove_file(&tmp); + return Err(error); + } + } + Ok(()) +} + +#[cfg(has_bundled_cli)] +fn install_runtime_pair(install_dir: &Path, archive: &[u8]) -> Result<(), EmbeddedCliError> { + install_adjacent_file(install_dir, archive, RUNTIME_NODE_NAME, "runtime.node")?; + install_adjacent_file( + install_dir, + archive, + RUNTIME_BINARY_NAME, + "copilot runtime wrapper", + ) } #[cfg(all(has_bundled_cli, feature = "bundled-in-process"))] fn install_runtime_library(install_dir: &Path, archive: &[u8]) -> Result<(), EmbeddedCliError> { - let target = install_dir.join(RUNTIME_LIBRARY_NAME); - if fs::metadata(&target).map(|m| m.len() > 0).unwrap_or(false) { - return Ok(()); - } - let bytes = extract_binary(archive, RUNTIME_LIBRARY_NAME)?; + install_adjacent_file( + install_dir, + archive, + RUNTIME_LIBRARY_NAME, + "in-process FFI runtime library", + ) +} + +#[cfg(has_bundled_cli)] +fn install_adjacent_file( + install_dir: &Path, + archive: &[u8], + file_name: &str, + label: &str, +) -> Result<(), EmbeddedCliError> { + let target = install_dir.join(file_name); + let bytes = extract_binary(archive, file_name)?; if bytes.is_empty() { return Err(EmbeddedCliError::with_message( EmbeddedCliErrorKind::Verification, - "embedded runtime library is empty", + format!("embedded {label} is empty"), )); } + if fs::read(&target) + .map(|installed| installed == bytes) + .unwrap_or(false) + { + return Ok(()); + } let tmp = write_temp_file(install_dir, &bytes)?; if let Err(e) = publish(&tmp, &target) { let _ = fs::remove_file(&tmp); return Err(e); } - tracing::debug!(path = %target.display(), "in-process FFI runtime library installed"); + tracing::debug!(path = %target.display(), %label, "embedded runtime artifact installed"); Ok(()) } @@ -480,7 +694,7 @@ fn read_marker_len(marker_path: &Path) -> Option { .ok() } -#[cfg(all(has_bundled_cli, any(feature = "bundled-in-process", not(windows))))] +#[cfg(has_bundled_cli)] fn extract_binary(archive: &[u8], binary_name: &str) -> Result, EmbeddedCliError> { let gz = flate2::read::GzDecoder::new(archive); let mut tar = tar::Archive::new(gz); @@ -505,26 +719,6 @@ fn extract_binary(archive: &[u8], binary_name: &str) -> Result, Embedded Err(EmbeddedCliErrorKind::BinaryNotFoundInArchive.into()) } -#[cfg(all(has_bundled_cli, not(feature = "bundled-in-process"), windows))] -fn extract_binary(archive: &[u8], binary_name: &str) -> Result, EmbeddedCliError> { - let cursor = std::io::Cursor::new(archive); - let mut zip = zip::ZipArchive::new(cursor) - .map_err(|e| EmbeddedCliError::new(EmbeddedCliErrorKind::Zip, e))?; - for i in 0..zip.len() { - let mut entry = zip - .by_index(i) - .map_err(|e| EmbeddedCliError::new(EmbeddedCliErrorKind::Zip, e))?; - let name = entry.name().to_string(); - if name == binary_name || name.ends_with(&format!("/{binary_name}")) { - let mut bytes = Vec::with_capacity(entry.size() as usize); - std::io::copy(&mut entry, &mut bytes) - .map_err(|e| EmbeddedCliError::new(EmbeddedCliErrorKind::Io, e))?; - return Ok(bytes); - } - } - Err(EmbeddedCliErrorKind::BinaryNotFoundInArchive.into()) -} - #[cfg(has_bundled_cli)] fn sanitize_version(version: &str) -> String { version @@ -541,10 +735,7 @@ fn sanitize_version(version: &str) -> String { #[allow(dead_code)] enum EmbeddedCliErrorKind { CreateDir, - #[cfg(any(feature = "bundled-in-process", not(windows)))] Archive, - #[cfg(all(not(feature = "bundled-in-process"), windows))] - Zip, BinaryNotFoundInArchive, Io, /// Atomically renaming the staged temp file onto the final path failed. @@ -561,10 +752,7 @@ impl std::fmt::Display for EmbeddedCliErrorKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { EmbeddedCliErrorKind::CreateDir => f.write_str("failed to create install directory"), - #[cfg(any(feature = "bundled-in-process", not(windows)))] EmbeddedCliErrorKind::Archive => f.write_str("failed to read archive entry"), - #[cfg(all(not(feature = "bundled-in-process"), windows))] - EmbeddedCliErrorKind::Zip => f.write_str("failed to read zip archive"), EmbeddedCliErrorKind::BinaryNotFoundInArchive => { f.write_str("CLI binary not found in embedded archive") } @@ -670,7 +858,7 @@ mod tests { #[cfg(all(has_bundled_cli, feature = "bundled-in-process"))] #[test] - fn embedded_archive_contains_only_expected_files() { + fn embedded_archive_contains_runtime_assets_and_excludes_cli_only_files() { let gz = flate2::read::GzDecoder::new(build_time::CLI_ARCHIVE); let mut archive = tar::Archive::new(gz); let mut names: Vec = archive @@ -687,12 +875,13 @@ mod tests { .collect(); names.sort(); - let mut expected = vec![ - CLI_BINARY_NAME.to_string(), - RUNTIME_LIBRARY_NAME.to_string(), - ]; - expected.sort(); - assert_eq!(names, expected); + assert!(names.contains(&CLI_BINARY_NAME.to_string())); + assert!(names.contains(&RUNTIME_LIBRARY_NAME.to_string())); + assert!(names.contains(&RUNTIME_BINARY_NAME.to_string())); + assert!(names.contains(&RUNTIME_NODE_NAME.to_string())); + assert!(names.iter().any(|name| name.starts_with("ripgrep/"))); + assert!(names.iter().any(|name| name.starts_with("definitions/"))); + assert!(!names.contains(&"app.js".to_string())); } /// Bytes whose header looks like a valid executable image on the host @@ -841,4 +1030,42 @@ mod tests { assert_eq!(mode & 0o777, 0o755, "temp binary should be executable"); } } + + #[cfg(has_bundled_cli)] + #[test] + fn runtime_install_replaces_stale_pair() { + let dir = tempfile::tempdir().expect("tempdir"); + fs::write(dir.path().join(RUNTIME_NODE_NAME), b"stale runtime").expect("seed runtime"); + fs::write(dir.path().join(RUNTIME_BINARY_NAME), b"stale wrapper").expect("seed wrapper"); + + install_runtime(dir.path(), build_time::CLI_ARCHIVE).expect("install runtime"); + + assert_eq!( + fs::read(dir.path().join(RUNTIME_NODE_NAME)).expect("read runtime"), + extract_binary(build_time::CLI_ARCHIVE, RUNTIME_NODE_NAME).expect("extract runtime") + ); + assert_eq!( + fs::read(dir.path().join(RUNTIME_BINARY_NAME)).expect("read wrapper"), + extract_binary(build_time::CLI_ARCHIVE, RUNTIME_BINARY_NAME).expect("extract wrapper") + ); + } + + #[cfg(has_bundled_cli)] + #[test] + fn custom_runtime_install_dir_isolated_by_version() { + let dir = tempfile::tempdir().expect("tempdir"); + + assert_eq!( + runtime_install_dir(dir.path(), "1.0.0").expect("claim directory"), + dir.path() + ); + assert_eq!( + runtime_install_dir(dir.path(), "1.0.0").expect("reuse directory"), + dir.path() + ); + assert_eq!( + runtime_install_dir(dir.path(), "2.0.0").expect("isolate directory"), + dir.path().join("2.0.0") + ); + } } diff --git a/rust/src/errors.rs b/rust/src/errors.rs index 6e05bbfae1..70f4c14ff1 100644 --- a/rust/src/errors.rs +++ b/rust/src/errors.rs @@ -218,6 +218,8 @@ pub enum ErrorKind { }, /// Invalid combination of options or configuration. InvalidConfig, + /// A session-scoped GitHub token provider failed or returned invalid data. + GitHubTokenProvider, } impl fmt::Display for ErrorKind { @@ -238,6 +240,7 @@ impl fmt::Display for ErrorKind { write!(f, "binary not found: {name}") } ErrorKind::InvalidConfig => write!(f, "invalid configuration"), + ErrorKind::GitHubTokenProvider => write!(f, "GitHub token provider error"), } } } diff --git a/rust/src/extension_launch_provider.rs b/rust/src/extension_launch_provider.rs new file mode 100644 index 0000000000..63e2284144 --- /dev/null +++ b/rust/src/extension_launch_provider.rs @@ -0,0 +1,158 @@ +//! Connection-level extension launch profile resolution. + +use std::sync::{Arc, OnceLock, Weak}; + +use async_trait::async_trait; +use parking_lot::RwLock; +use serde::Serialize; +use serde_json::Value; +use tracing::warn; + +pub use crate::rpc::{ + ExtensionLaunchProfile, ExtensionLaunchProviderResolveRequest, + ExtensionLaunchProviderResolveResult, +}; +use crate::{ + Client, ClientInner, JsonRpcError, JsonRpcRequest, JsonRpcResponse, Result, error_codes, +}; + +pub(crate) const RESOLVE_METHOD: &str = "extensionLaunchProvider.resolve"; +const MISSING_HANDLER_MESSAGE: &str = "No extensionLaunchProvider client-global handler registered"; + +/// Resolves process launch profiles for extension entrypoints discovered by the runtime. +/// +/// Configure an implementation with +/// [`ClientOptions::with_extension_launch_provider`](crate::ClientOptions::with_extension_launch_provider). +/// The SDK registers the provider before [`Client::start`](crate::Client::start) +/// returns, so extension resolution cannot race session creation. +/// +/// The returned executable, arguments, and environment are forwarded unchanged. +/// The runtime remains authoritative for its reserved `COPILOT_SDK_PATH`, +/// `SESSION_ID`, and `COPILOT_EXTENSION_PARENT_PID` environment variables. +#[async_trait] +pub trait ExtensionLaunchProvider: Send + Sync + 'static { + /// Resolve a launch profile for one discovered extension entrypoint. + /// + /// Return a result with `launch: None` when the provider does not support + /// the entrypoint. + async fn resolve( + &self, + request: ExtensionLaunchProviderResolveRequest, + ) -> Result; +} + +pub(crate) struct ExtensionLaunchProviderDispatcher { + handler: RwLock>>, + client: OnceLock>, +} + +impl ExtensionLaunchProviderDispatcher { + pub(crate) fn new(handler: Option>) -> Self { + Self { + handler: RwLock::new(handler), + client: OnceLock::new(), + } + } + + pub(crate) fn set_client(&self, client: Weak) { + let _ = self.client.set(client); + } + + pub(crate) fn is_configured(&self) -> bool { + self.handler.read().is_some() + } + + pub(crate) fn clear(&self) { + self.handler.write().take(); + } + + pub(crate) async fn dispatch(&self, request: JsonRpcRequest) { + let request_id = request.id; + let Some(handler) = self.handler.read().clone() else { + self.send_error( + request_id, + error_codes::INTERNAL_ERROR, + MISSING_HANDLER_MESSAGE, + ) + .await; + return; + }; + + let params = request + .params + .unwrap_or_else(|| Value::Object(serde_json::Map::new())); + let request = match serde_json::from_value(params) { + Ok(request) => request, + Err(error) => { + self.send_error( + request_id, + error_codes::INVALID_PARAMS, + &format!("invalid params: {error}"), + ) + .await; + return; + } + }; + + match handler.resolve(request).await { + Ok(result) => self.respond(request_id, result).await, + Err(error) => { + self.send_error(request_id, error_codes::INTERNAL_ERROR, &error.to_string()) + .await; + } + } + } + + fn client(&self) -> Option { + self.client + .get() + .and_then(Weak::upgrade) + .map(Client::from_inner) + } + + async fn respond(&self, request_id: u64, result: T) { + let value = match serde_json::to_value(result) { + Ok(value) => value, + Err(error) => { + warn!(error = %error, "failed to serialize extension launch provider response"); + self.send_error( + request_id, + error_codes::INTERNAL_ERROR, + "serialization failure", + ) + .await; + return; + } + }; + + let Some(client) = self.client() else { + return; + }; + let _ = client + .send_response(&JsonRpcResponse { + jsonrpc: "2.0".to_string(), + id: request_id, + result: Some(value), + error: None, + }) + .await; + } + + async fn send_error(&self, request_id: u64, code: i32, message: &str) { + let Some(client) = self.client() else { + return; + }; + let _ = client + .send_response(&JsonRpcResponse { + jsonrpc: "2.0".to_string(), + id: request_id, + result: None, + error: Some(JsonRpcError { + code, + message: message.to_string(), + data: None, + }), + }) + .await; + } +} diff --git a/rust/src/ffi.rs b/rust/src/ffi.rs index f784b1a6d1..a25990062c 100644 --- a/rust/src/ffi.rs +++ b/rust/src/ffi.rs @@ -2,12 +2,11 @@ //! library and speaking JSON-RPC over its C ABI, //! instead of spawning a CLI child process and communicating over stdio/TCP. //! -//! The runtime's `host_start` export spawns the residual TypeScript worker -//! itself — the packaged single-file CLI (`copilot --embedded-host`) or, for -//! dev, `node dist-cli/index.js --embedded-host`. JSON-RPC frames are pumped -//! across the ABI: writes go to `connection_write`; inbound frames arrive on a -//! native callback that feeds an async reader. The framing is unchanged — the -//! same LSP `Content-Length:` frames the stdio transport uses. +//! The runtime's `host_start` export constructs the Rust server synchronously in +//! this process. JSON-RPC frames are pumped across the ABI: writes go to +//! `connection_write`; inbound frames arrive on a native callback that feeds an +//! async reader. The framing is unchanged — the same LSP `Content-Length:` +//! frames the stdio transport uses. use std::collections::HashMap; use std::ffi::c_void; @@ -204,12 +203,11 @@ impl AsyncWrite for FfiWriter { } } -/// Prepared FFI host: the bound cdylib exports plus the spawn arguments needed -/// to start the runtime worker. The cdylib is loaded process-globally and never -/// unloaded (see [`load_library`]). +/// Prepared FFI host. The cdylib is loaded process-globally and never unloaded +/// (see [`load_library`]). pub(crate) struct FfiHost { library_path: PathBuf, - entrypoint: PathBuf, + cli_entrypoint: Option, environment: Vec<(String, String)>, args: Vec, host_start: HostStartFn, @@ -224,30 +222,34 @@ pub(crate) struct FfiHost { unsafe impl Send for FfiHost {} impl FfiHost { - /// Load the cdylib next to `entrypoint` and bind its exports. - /// - /// `entrypoint` is the packaged single-file CLI binary or, for dev, a - /// `.js` file launched via `node`. The native library is resolved relative - /// to the entrypoint directory, supporting both packaged and development - /// layouts. + /// Load the cdylib next to `runtime_entrypoint` and bind its exports. pub(crate) fn create( - entrypoint: &Path, + runtime_entrypoint: &Path, + cli_entrypoint: Option<&Path>, environment: Vec<(String, String)>, args: Vec, ) -> Result { - let entrypoint = std::fs::canonicalize(entrypoint) - .map(path_for_child_process) + let runtime_entrypoint = std::fs::canonicalize(runtime_entrypoint).map_err(|e| { + Error::with_message( + ErrorKind::InvalidConfig, + format!( + "failed to resolve in-process runtime entrypoint '{}': {e}", + runtime_entrypoint.display() + ), + ) + })?; + let cli_entrypoint = cli_entrypoint + .map(std::fs::canonicalize) + .transpose() .map_err(|e| { Error::with_message( ErrorKind::InvalidConfig, - format!( - "failed to resolve in-process CLI entrypoint '{}': {e}", - entrypoint.display() - ), + format!("failed to resolve explicit in-process CLI entrypoint: {e}"), ) - })?; - let library_path = - std::fs::canonicalize(resolve_library_path(&entrypoint)?).map_err(|e| { + })? + .map(path_for_child_process); + let library_path = std::fs::canonicalize(resolve_library_path(&runtime_entrypoint)?) + .map_err(|e| { Error::with_message( ErrorKind::InvalidConfig, format!("failed to resolve in-process runtime library: {e}"), @@ -267,7 +269,7 @@ impl FfiHost { Ok(Self { library_path, - entrypoint, + cli_entrypoint, environment, args, host_start, @@ -278,11 +280,7 @@ impl FfiHost { }) } - /// Start the runtime worker and open the FFI JSON-RPC connection. - /// - /// `host_start` blocks until the worker connects back and signals - /// readiness (up to ~30s), and must not run on an async executor thread, so - /// the blocking handshake is offloaded to [`tokio::task::spawn_blocking`]. + /// Start the native runtime and open the FFI JSON-RPC connection. pub(crate) async fn start(self) -> Result<(FfiReader, FfiWriter, Arc), Error> { tokio::task::spawn_blocking(move || self.start_blocking()) .await @@ -295,7 +293,7 @@ impl FfiHost { } fn start_blocking(self) -> Result<(FfiReader, FfiWriter, Arc), Error> { - let argv = build_argv_json(&self.entrypoint, &self.args); + let argv = build_argv_json(self.cli_entrypoint.as_deref(), &self.args); let env = build_env_json(&self.environment); let (env_ptr, env_len) = match &env { @@ -309,9 +307,8 @@ impl FfiHost { return Err(Error::with_message( ErrorKind::InvalidConfig, format!( - "copilot_runtime_host_start failed (library '{}', entrypoint '{}')", - self.library_path.display(), - self.entrypoint.display() + "copilot_runtime_host_start failed (library '{}')", + self.library_path.display() ), )); } @@ -440,6 +437,8 @@ pub(crate) fn prebuilds_folder() -> Option { "win32" } else if cfg!(target_os = "macos") { "darwin" + } else if cfg!(all(target_os = "linux", target_env = "musl")) { + "linuxmusl" } else if cfg!(target_os = "linux") { "linux" } else { @@ -472,6 +471,11 @@ fn resolve_library_path(entrypoint: &Path) -> Result { return Ok(flat); } + let adjacent = dir.join("runtime.node"); + if adjacent.is_file() { + return Ok(adjacent); + } + // Development package layout. let prebuilds = prebuilds_folder().map(|folder| dir.join("prebuilds").join(folder).join("runtime.node")); @@ -521,28 +525,23 @@ fn path_for_child_process(path: PathBuf) -> PathBuf { path } -fn build_argv_json(entrypoint: &Path, extra_args: &[String]) -> Vec { - // A `.js` entrypoint (dev / dist-cli) is launched via node; the packaged - // single-file CLI binary embeds its own Node and is invoked directly. - let entrypoint_str = entrypoint.to_string_lossy().into_owned(); - let is_js = entrypoint - .extension() - .and_then(|ext| ext.to_str()) - .is_some_and(|ext| ext.eq_ignore_ascii_case("js")); - let mut argv: Vec = if is_js { - vec![ - "node".to_string(), - entrypoint_str, - "--embedded-host".to_string(), - "--no-auto-update".to_string(), - ] - } else { - vec![ +fn build_argv_json(entrypoint: Option<&Path>, extra_args: &[String]) -> Vec { + let mut argv = Vec::new(); + if let Some(entrypoint) = entrypoint { + let entrypoint_str = entrypoint.to_string_lossy().into_owned(); + if entrypoint + .extension() + .and_then(|ext| ext.to_str()) + .is_some_and(|ext| ext.eq_ignore_ascii_case("js")) + { + argv.push("node".to_string()); + } + argv.extend([ entrypoint_str, "--embedded-host".to_string(), "--no-auto-update".to_string(), - ] - }; + ]); + } argv.extend_from_slice(extra_args); serde_json::to_vec(&argv).expect("argv serializes") } @@ -563,29 +562,20 @@ mod tests { use super::*; #[test] - fn argv_pins_worker_and_appends_client_options() { + fn argv_without_entrypoint_contains_only_client_options() { let argv: Vec = serde_json::from_slice(&build_argv_json( - Path::new("copilot"), + None, &["--log-level".into(), "debug".into()], )) .unwrap(); - assert_eq!( - argv, - [ - "copilot", - "--embedded-host", - "--no-auto-update", - "--log-level", - "debug" - ] - ); + assert_eq!(argv, ["--log-level", "debug"]); } #[test] - fn javascript_entrypoint_uses_node() { + fn explicit_javascript_entrypoint_uses_node() { let argv: Vec = - serde_json::from_slice(&build_argv_json(Path::new("index.js"), &[])).unwrap(); + serde_json::from_slice(&build_argv_json(Some(Path::new("index.js")), &[])).unwrap(); assert_eq!( argv, diff --git a/rust/src/generated/api_types.rs b/rust/src/generated/api_types.rs index b64a13c4a5..68d8b7d054 100644 --- a/rust/src/generated/api_types.rs +++ b/rust/src/generated/api_types.rs @@ -10,9 +10,11 @@ use std::collections::HashMap; use serde::{Deserialize, Serialize}; use super::session_events::{ - AbortReason, ContextTier, McpServerSource, McpServerStatus, PermissionPromptRequest, - PermissionRule, ReasoningSummary, SessionLimitsConfig, SessionMode, ShutdownType, SkillSource, - UserToolSessionApproval, Verbosity, + AbortReason, AgentModelPolicy, AutoTier, ContextTier, McpOauthHttpResponse, + McpOauthWWWAuthenticateParams, McpServerSource, McpServerStatus, ModelChangeSource, + OmittedBinaryOmittedReason, PermissionMode, PermissionPromptRequest, PermissionRule, + ReasoningSummary, SessionLimitsConfig, SessionMode, ShutdownType, SkillSource, + TaskCompletionOutcome, UserToolSessionApproval, Verbosity, }; use crate::types::{RequestId, SessionEvent, SessionId}; @@ -22,6 +24,8 @@ pub mod rpc_methods { pub const PING: &str = "ping"; /// `connect` pub const CONNECT: &str = "connect"; + /// `hooks.discover` + pub const HOOKS_DISCOVER: &str = "hooks.discover"; /// `models.list` pub const MODELS_LIST: &str = "models.list"; /// `models.getBuiltInCatalog` @@ -56,12 +60,18 @@ pub mod rpc_methods { pub const MCP_CONFIG_RELOAD: &str = "mcp.config.reload"; /// `mcp.discover` pub const MCP_DISCOVER: &str = "mcp.discover"; + /// `mcp.planInstall` + pub const MCP_PLANINSTALL: &str = "mcp.planInstall"; /// `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"; + /// `catalog.search` + pub const CATALOG_SEARCH: &str = "catalog.search"; /// `plugins.list` pub const PLUGINS_LIST: &str = "plugins.list"; /// `plugins.install` @@ -76,6 +86,8 @@ pub mod rpc_methods { pub const PLUGINS_ENABLE: &str = "plugins.enable"; /// `plugins.disable` pub const PLUGINS_DISABLE: &str = "plugins.disable"; + /// `plugins.builtin.set` + pub const PLUGINS_BUILTIN_SET: &str = "plugins.builtin.set"; /// `plugins.marketplaces.list` pub const PLUGINS_MARKETPLACES_LIST: &str = "plugins.marketplaces.list"; /// `plugins.marketplaces.add` @@ -88,6 +100,8 @@ pub mod rpc_methods { pub const PLUGINS_MARKETPLACES_REFRESH: &str = "plugins.marketplaces.refresh"; /// `skills.config.setDisabledSkills` pub const SKILLS_CONFIG_SETDISABLEDSKILLS: &str = "skills.config.setDisabledSkills"; + /// `skills.config.setSkillDisabled` + pub const SKILLS_CONFIG_SETSKILLDISABLED: &str = "skills.config.setSkillDisabled"; /// `skills.discover` pub const SKILLS_DISCOVER: &str = "skills.discover"; /// `skills.getDiscoveryPaths` @@ -108,6 +122,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` @@ -128,6 +144,8 @@ pub mod rpc_methods { pub const SESSIONS_LIST: &str = "sessions.list"; /// `sessions.getMetadata` pub const SESSIONS_GETMETADATA: &str = "sessions.getMetadata"; + /// `sessions.readPersistedEvents` + pub const SESSIONS_READPERSISTEDEVENTS: &str = "sessions.readPersistedEvents"; /// `sessions.listNonEmptySessionIds` pub const SESSIONS_LISTNONEMPTYSESSIONIDS: &str = "sessions.listNonEmptySessionIds"; /// `sessions.findByTaskId` @@ -189,6 +207,8 @@ pub mod rpc_methods { pub const SESSION_SEND: &str = "session.send"; /// `session.sendMessages` pub const SESSION_SENDMESSAGES: &str = "session.sendMessages"; + /// `session.sandbox.getEnforcementStatus` + pub const SESSION_SANDBOX_GETENFORCEMENTSTATUS: &str = "session.sandbox.getEnforcementStatus"; /// `session.sendSystemNotification` pub const SESSION_SENDSYSTEMNOTIFICATION: &str = "session.sendSystemNotification"; /// `session.abort` @@ -203,6 +223,23 @@ pub mod rpc_methods { pub const SESSION_GITHUBAUTH_GETSTATUS: &str = "session.gitHubAuth.getStatus"; /// `session.gitHubAuth.setCredentials` pub const SESSION_GITHUBAUTH_SETCREDENTIALS: &str = "session.gitHubAuth.setCredentials"; + /// `session.gitHubAuth.getCurrentAuthInfo` + pub const SESSION_GITHUBAUTH_GETCURRENTAUTHINFO: &str = "session.gitHubAuth.getCurrentAuthInfo"; + /// `session.gitHubAuth.getAllAuthAvailable` + pub const SESSION_GITHUBAUTH_GETALLAUTHAVAILABLE: &str = + "session.gitHubAuth.getAllAuthAvailable"; + /// `session.gitHubAuth.refreshCopilotUser` + pub const SESSION_GITHUBAUTH_REFRESHCOPILOTUSER: &str = "session.gitHubAuth.refreshCopilotUser"; + /// `session.gitHubAuth.login` + pub const SESSION_GITHUBAUTH_LOGIN: &str = "session.gitHubAuth.login"; + /// `session.gitHubAuth.switchToAuth` + pub const SESSION_GITHUBAUTH_SWITCHTOAUTH: &str = "session.gitHubAuth.switchToAuth"; + /// `session.gitHubAuth.logout` + pub const SESSION_GITHUBAUTH_LOGOUT: &str = "session.gitHubAuth.logout"; + /// `session.gitHubAuth.logoutUser` + pub const SESSION_GITHUBAUTH_LOGOUTUSER: &str = "session.gitHubAuth.logoutUser"; + /// `session.gitHubAuth.lastAuthErrors` + pub const SESSION_GITHUBAUTH_LASTAUTHERRORS: &str = "session.gitHubAuth.lastAuthErrors"; /// `session.debug.collectLogs` pub const SESSION_DEBUG_COLLECTLOGS: &str = "session.debug.collectLogs"; /// `session.canvas.list` @@ -215,10 +252,18 @@ pub mod rpc_methods { pub const SESSION_CANVAS_CLOSE: &str = "session.canvas.close"; /// `session.canvas.action.invoke` pub const SESSION_CANVAS_ACTION_INVOKE: &str = "session.canvas.action.invoke"; + /// `session.canvas.provider.register` + pub const SESSION_CANVAS_PROVIDER_REGISTER: &str = "session.canvas.provider.register"; + /// `session.canvas.provider.unregister` + pub const SESSION_CANVAS_PROVIDER_UNREGISTER: &str = "session.canvas.provider.unregister"; /// `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.runFromTool` + pub const SESSION_FACTORY_RUNFROMTOOL: &str = "session.factory.runFromTool"; + /// `session.factory.resumeFromTool` + pub const SESSION_FACTORY_RESUMEFROMTOOL: &str = "session.factory.resumeFromTool"; /// `session.factory.getRun` pub const SESSION_FACTORY_GETRUN: &str = "session.factory.getRun"; /// `session.factory.listRuns` @@ -241,6 +286,8 @@ pub mod rpc_methods { pub const SESSION_MODEL_GETCURRENT: &str = "session.model.getCurrent"; /// `session.model.switchTo` pub const SESSION_MODEL_SWITCHTO: &str = "session.model.switchTo"; + /// `session.model.applyStartupOverlay` + pub const SESSION_MODEL_APPLYSTARTUPOVERLAY: &str = "session.model.applyStartupOverlay"; /// `session.model.setReasoningEffort` pub const SESSION_MODEL_SETREASONINGEFFORT: &str = "session.model.setReasoningEffort"; /// `session.model.list` @@ -368,6 +415,8 @@ pub mod rpc_methods { pub const SESSION_MCP_DISABLE: &str = "session.mcp.disable"; /// `session.mcp.reload` pub const SESSION_MCP_RELOAD: &str = "session.mcp.reload"; + /// `session.mcp.moveLoadingToBackground` + pub const SESSION_MCP_MOVELOADINGTOBACKGROUND: &str = "session.mcp.moveLoadingToBackground"; /// `session.mcp.reloadWithConfig` pub const SESSION_MCP_RELOADWITHCONFIG: &str = "session.mcp.reloadWithConfig"; /// `session.mcp.executeSampling` @@ -400,6 +449,8 @@ pub mod rpc_methods { "session.mcp.oauth.authenticationStateChanged"; /// `session.mcp.oauth.login` pub const SESSION_MCP_OAUTH_LOGIN: &str = "session.mcp.oauth.login"; + /// `session.mcp.oauth.probe` + pub const SESSION_MCP_OAUTH_PROBE: &str = "session.mcp.oauth.probe"; /// `session.mcp.oauth.respond` pub const SESSION_MCP_OAUTH_RESPOND: &str = "session.mcp.oauth.respond"; /// `session.mcp.headers.handlePendingHeadersRefreshRequest` @@ -446,18 +497,29 @@ pub mod rpc_methods { /// `session.extensions.sendAttachmentsToMessage` pub const SESSION_EXTENSIONS_SENDATTACHMENTSTOMESSAGE: &str = "session.extensions.sendAttachmentsToMessage"; + /// `session.tools.execute` + pub const SESSION_TOOLS_EXECUTE: &str = "session.tools.execute"; + /// `session.tools.getBuiltinDescriptors` + pub const SESSION_TOOLS_GETBUILTINDESCRIPTORS: &str = "session.tools.getBuiltinDescriptors"; + /// `session.tools.taskCompleteEventData` + pub const SESSION_TOOLS_TASKCOMPLETEEVENTDATA: &str = "session.tools.taskCompleteEventData"; /// `session.tools.handlePendingToolCall` pub const SESSION_TOOLS_HANDLEPENDINGTOOLCALL: &str = "session.tools.handlePendingToolCall"; /// `session.tools.initializeAndValidate` pub const SESSION_TOOLS_INITIALIZEANDVALIDATE: &str = "session.tools.initializeAndValidate"; /// `session.tools.getCurrentMetadata` pub const SESSION_TOOLS_GETCURRENTMETADATA: &str = "session.tools.getCurrentMetadata"; + /// `session.tools.set` + pub const SESSION_TOOLS_SET: &str = "session.tools.set"; /// `session.tools.updateSubagentSettings` pub const SESSION_TOOLS_UPDATESUBAGENTSETTINGS: &str = "session.tools.updateSubagentSettings"; /// `session.commands.list` pub const SESSION_COMMANDS_LIST: &str = "session.commands.list"; /// `session.commands.invoke` pub const SESSION_COMMANDS_INVOKE: &str = "session.commands.invoke"; + /// `session.commands.finalizeInvocationEffect` + pub const SESSION_COMMANDS_FINALIZEINVOCATIONEFFECT: &str = + "session.commands.finalizeInvocationEffect"; /// `session.commands.handlePendingCommand` pub const SESSION_COMMANDS_HANDLEPENDINGCOMMAND: &str = "session.commands.handlePendingCommand"; /// `session.commands.execute` @@ -504,10 +566,10 @@ pub mod rpc_methods { pub const SESSION_PERMISSIONS_PENDINGREQUESTS: &str = "session.permissions.pendingRequests"; /// `session.permissions.setApproveAll` pub const SESSION_PERMISSIONS_SETAPPROVEALL: &str = "session.permissions.setApproveAll"; - /// `session.permissions.setAllowAll` - pub const SESSION_PERMISSIONS_SETALLOWALL: &str = "session.permissions.setAllowAll"; - /// `session.permissions.getAllowAll` - pub const SESSION_PERMISSIONS_GETALLOWALL: &str = "session.permissions.getAllowAll"; + /// `session.permissions.setMode` + pub const SESSION_PERMISSIONS_SETMODE: &str = "session.permissions.setMode"; + /// `session.permissions.getMode` + pub const SESSION_PERMISSIONS_GETMODE: &str = "session.permissions.getMode"; /// `session.permissions.modifyRules` pub const SESSION_PERMISSIONS_MODIFYRULES: &str = "session.permissions.modifyRules"; /// `session.permissions.setRequired` @@ -679,6 +741,10 @@ pub mod rpc_methods { pub const SESSION_SCHEDULE_REARMSELFPACED: &str = "session.schedule.rearmSelfPaced"; /// `session.schedule.stop` pub const SESSION_SCHEDULE_STOP: &str = "session.schedule.stop"; + /// `skillProvider.list` + pub const SKILLPROVIDER_LIST: &str = "skillProvider.list"; + /// `skillProvider.read` + pub const SKILLPROVIDER_READ: &str = "skillProvider.read"; /// `providerToken.getToken` pub const PROVIDERTOKEN_GETTOKEN: &str = "providerToken.getToken"; /// `factory.execute` @@ -753,7 +819,7 @@ pub struct AbortResult { pub success: bool, } -/// Authenticated account entry returned by `account.getAllUsers`, with auth info and an optional associated token. +/// Endpoint URLs from the raw Copilot `/copilot_internal/v2/token` user-response passthrough. /// ///

/// @@ -763,50 +829,25 @@ pub struct AbortResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct AccountAllUsers { - /// Authentication information for this user - pub auth_info: serde_json::Value, - /// Associated token, if available +pub struct CopilotUserResponseEndpoints { + /// Copilot API endpoint URL. #[serde(skip_serializing_if = "Option::is_none")] - pub token: Option, -} - -/// Current authentication 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 AccountGetCurrentAuthResult { - /// Authentication errors from the last auth attempt, if any + pub api: Option, + /// Experimental-service endpoint URL. #[serde(skip_serializing_if = "Option::is_none")] - pub auth_errors: Option>, - /// Current authentication information, if authenticated + pub exp: Option, + /// Origin-tracker endpoint URL. + #[serde(rename = "origin-tracker", skip_serializing_if = "Option::is_none")] + pub origin_tracker: Option, + /// Copilot proxy endpoint URL. #[serde(skip_serializing_if = "Option::is_none")] - pub auth_info: Option, -} - -/// Optional GitHub token used to look up quota for a specific user instead of the global auth context. -/// -///
-/// -/// **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 AccountGetQuotaRequest { - /// GitHub token for per-user quota lookup. When provided, resolves this token to determine the user's quota instead of using the global auth. + pub proxy: Option, + /// Copilot telemetry endpoint URL. #[serde(skip_serializing_if = "Option::is_none")] - pub git_hub_token: Option, + pub telemetry: Option, } -/// Quota usage snapshot for a Copilot quota type, including entitlement, used requests, overage, reset date, and remaining percentage. +/// Chat quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. /// ///
/// @@ -816,61 +857,49 @@ pub struct AccountGetQuotaRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct AccountQuotaSnapshot { - /// Number of requests included in the entitlement, or -1 for unlimited entitlements - pub entitlement_requests: i64, - /// Whether the user has an unlimited usage entitlement - pub is_unlimited_entitlement: bool, - /// Number of additional usage requests made this period - pub overage: f64, - /// Whether additional usage is allowed when quota is exhausted - pub overage_allowed_with_exhausted_quota: bool, - /// Percentage of entitlement remaining - pub remaining_percentage: f64, - /// Date when the quota resets (ISO 8601 string) +pub struct CopilotUserResponseQuotaSnapshotsChat { + /// Number of requests/units included in the entitlement for this period; `-1` denotes an unlimited entitlement. #[serde(skip_serializing_if = "Option::is_none")] - pub reset_date: Option, - /// Whether usage is still permitted after quota exhaustion - pub usage_allowed_with_exhausted_quota: bool, - /// Number of requests used so far this period - pub used_requests: i64, -} - -/// Quota usage snapshots for the resolved user, keyed by quota 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 AccountGetQuotaResult { - /// Quota snapshots keyed by type (e.g., chat, completions, premium_interactions) - pub quota_snapshots: HashMap, -} - -/// Credentials to store after successful authentication -/// -///
-/// -/// **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 AccountLoginRequest { - /// GitHub host URL - pub host: String, - /// User login/username - pub login: String, - /// GitHub authentication token - pub token: String, + pub entitlement: Option, + /// Whether the user currently has quota available; when `false` and not unlimited, further requests are blocked until the quota resets. + #[serde(rename = "has_quota", skip_serializing_if = "Option::is_none")] + pub has_quota: Option, + /// Count of additional pay-per-request usage consumed this period beyond the entitlement. + #[serde(rename = "overage_count", skip_serializing_if = "Option::is_none")] + pub overage_count: Option, + /// Whether usage may continue at pay-per-request rates once the entitlement is exhausted. + #[serde(rename = "overage_permitted", skip_serializing_if = "Option::is_none")] + pub overage_permitted: Option, + /// Percentage of the entitlement remaining at the snapshot timestamp. + #[serde(rename = "percent_remaining", skip_serializing_if = "Option::is_none")] + pub percent_remaining: Option, + /// Identifier of the quota bucket this snapshot describes. + #[serde(rename = "quota_id", skip_serializing_if = "Option::is_none")] + pub quota_id: Option, + /// Amount of quota remaining at the snapshot timestamp. + #[serde(rename = "quota_remaining", skip_serializing_if = "Option::is_none")] + pub quota_remaining: Option, + /// Unix epoch time, in seconds, when this quota next resets. + #[serde(rename = "quota_reset_at", skip_serializing_if = "Option::is_none")] + pub quota_reset_at: Option, + /// Remaining entitlement/quota amount at the snapshot timestamp. + #[serde(skip_serializing_if = "Option::is_none")] + pub remaining: Option, + /// UTC timestamp when this snapshot was captured. + #[serde(rename = "timestamp_utc", skip_serializing_if = "Option::is_none")] + pub timestamp_utc: Option, + /// Whether this category uses usage-based (token/AI-credit) billing rather than a fixed premium-request count. + #[serde( + rename = "token_based_billing", + skip_serializing_if = "Option::is_none" + )] + pub token_based_billing: Option, + /// Whether the entitlement for this category is unlimited. + #[serde(skip_serializing_if = "Option::is_none")] + pub unlimited: Option, } -/// Result of a successful login; throws on failure +/// Completions quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. /// ///
/// @@ -880,12 +909,49 @@ pub struct AccountLoginRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct AccountLoginResult { - /// Whether the credential was persisted to a secure store (system keychain, or the config file when plaintext storage is enabled). False when no secure store was available and the token was not saved, so the consumer can decide how to proceed. - pub stored_in_vault: bool, +pub struct CopilotUserResponseQuotaSnapshotsCompletions { + /// Number of requests/units included in the entitlement for this period; `-1` denotes an unlimited entitlement. + #[serde(skip_serializing_if = "Option::is_none")] + pub entitlement: Option, + /// Whether the user currently has quota available; when `false` and not unlimited, further requests are blocked until the quota resets. + #[serde(rename = "has_quota", skip_serializing_if = "Option::is_none")] + pub has_quota: Option, + /// Count of additional pay-per-request usage consumed this period beyond the entitlement. + #[serde(rename = "overage_count", skip_serializing_if = "Option::is_none")] + pub overage_count: Option, + /// Whether usage may continue at pay-per-request rates once the entitlement is exhausted. + #[serde(rename = "overage_permitted", skip_serializing_if = "Option::is_none")] + pub overage_permitted: Option, + /// Percentage of the entitlement remaining at the snapshot timestamp. + #[serde(rename = "percent_remaining", skip_serializing_if = "Option::is_none")] + pub percent_remaining: Option, + /// Identifier of the quota bucket this snapshot describes. + #[serde(rename = "quota_id", skip_serializing_if = "Option::is_none")] + pub quota_id: Option, + /// Amount of quota remaining at the snapshot timestamp. + #[serde(rename = "quota_remaining", skip_serializing_if = "Option::is_none")] + pub quota_remaining: Option, + /// Unix epoch time, in seconds, when this quota next resets. + #[serde(rename = "quota_reset_at", skip_serializing_if = "Option::is_none")] + pub quota_reset_at: Option, + /// Remaining entitlement/quota amount at the snapshot timestamp. + #[serde(skip_serializing_if = "Option::is_none")] + pub remaining: Option, + /// UTC timestamp when this snapshot was captured. + #[serde(rename = "timestamp_utc", skip_serializing_if = "Option::is_none")] + pub timestamp_utc: Option, + /// Whether this category uses usage-based (token/AI-credit) billing rather than a fixed premium-request count. + #[serde( + rename = "token_based_billing", + skip_serializing_if = "Option::is_none" + )] + pub token_based_billing: Option, + /// Whether the entitlement for this category is unlimited. + #[serde(skip_serializing_if = "Option::is_none")] + pub unlimited: Option, } -/// User to log out +/// Premium-interactions quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. /// ///
/// @@ -895,12 +961,49 @@ pub struct AccountLoginResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct AccountLogoutRequest { - /// Authentication information for the user to log out - pub auth_info: serde_json::Value, +pub struct CopilotUserResponseQuotaSnapshotsPremiumInteractions { + /// Number of requests/units included in the entitlement for this period; `-1` denotes an unlimited entitlement. + #[serde(skip_serializing_if = "Option::is_none")] + pub entitlement: Option, + /// Whether the user currently has quota available; when `false` and not unlimited, further requests are blocked until the quota resets. + #[serde(rename = "has_quota", skip_serializing_if = "Option::is_none")] + pub has_quota: Option, + /// Count of additional pay-per-request usage consumed this period beyond the entitlement. + #[serde(rename = "overage_count", skip_serializing_if = "Option::is_none")] + pub overage_count: Option, + /// Whether usage may continue at pay-per-request rates once the entitlement is exhausted. + #[serde(rename = "overage_permitted", skip_serializing_if = "Option::is_none")] + pub overage_permitted: Option, + /// Percentage of the entitlement remaining at the snapshot timestamp. + #[serde(rename = "percent_remaining", skip_serializing_if = "Option::is_none")] + pub percent_remaining: Option, + /// Identifier of the quota bucket this snapshot describes. + #[serde(rename = "quota_id", skip_serializing_if = "Option::is_none")] + pub quota_id: Option, + /// Amount of quota remaining at the snapshot timestamp. + #[serde(rename = "quota_remaining", skip_serializing_if = "Option::is_none")] + pub quota_remaining: Option, + /// Unix epoch time, in seconds, when this quota next resets. + #[serde(rename = "quota_reset_at", skip_serializing_if = "Option::is_none")] + pub quota_reset_at: Option, + /// Remaining entitlement/quota amount at the snapshot timestamp. + #[serde(skip_serializing_if = "Option::is_none")] + pub remaining: Option, + /// UTC timestamp when this snapshot was captured. + #[serde(rename = "timestamp_utc", skip_serializing_if = "Option::is_none")] + pub timestamp_utc: Option, + /// Whether this category uses usage-based (token/AI-credit) billing rather than a fixed premium-request count. + #[serde( + rename = "token_based_billing", + skip_serializing_if = "Option::is_none" + )] + pub token_based_billing: Option, + /// Whether the entitlement for this category is unlimited. + #[serde(skip_serializing_if = "Option::is_none")] + pub unlimited: Option, } -/// Logout result indicating if more users remain +/// Quota snapshot map from the raw Copilot user-response passthrough, with chat, completions, premium-interactions, and other entries. /// ///
/// @@ -910,12 +1013,22 @@ pub struct AccountLogoutRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct AccountLogoutResult { - /// Whether other authenticated users remain after logout - pub has_more_users: bool, +pub struct CopilotUserResponseQuotaSnapshots { + /// Chat quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. + #[serde(skip_serializing_if = "Option::is_none")] + pub chat: Option, + /// Completions quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. + #[serde(skip_serializing_if = "Option::is_none")] + pub completions: Option, + /// Premium-interactions quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. + #[serde( + rename = "premium_interactions", + skip_serializing_if = "Option::is_none" + )] + pub premium_interactions: Option, } -/// Canonical directory where custom agents can be discovered or created, with scope, preference, and optional project path. +/// 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. /// ///
/// @@ -925,34 +1038,146 @@ pub struct AccountLogoutResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct AgentDiscoveryPath { - /// Absolute path of the search/create directory (may not exist on disk yet) - pub path: String, - /// Whether this is the canonical directory to create a new agent in its tier. At most one entry per tier is 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: AgentDiscoveryPathScope, -} - -/// Canonical locations where custom agents can be created so the runtime will recognize them. -/// -///
-/// +pub struct CopilotUserResponse { + /// Copilot access SKU identifier (e.g. `free_limited_copilot`, `copilot_for_business_seat_quota`) used to gate model and feature access. + #[serde(rename = "access_type_sku", skip_serializing_if = "Option::is_none")] + pub access_type_sku: Option, + /// Opaque analytics tracking identifier for the user, forwarded from the Copilot API. + #[serde( + rename = "analytics_tracking_id", + skip_serializing_if = "Option::is_none" + )] + pub analytics_tracking_id: Option, + /// Date the Copilot seat was assigned to the user, if applicable. + #[serde(rename = "assigned_date", skip_serializing_if = "Option::is_none")] + pub assigned_date: Option, + /// Whether the user is eligible to sign up for the free/limited Copilot tier. + #[serde( + rename = "can_signup_for_limited", + skip_serializing_if = "Option::is_none" + )] + pub can_signup_for_limited: Option, + /// Whether the user is able to upgrade their Copilot plan. + #[serde(rename = "can_upgrade_plan", skip_serializing_if = "Option::is_none")] + pub can_upgrade_plan: Option, + /// Whether Copilot chat is enabled for the user. + #[serde(rename = "chat_enabled", skip_serializing_if = "Option::is_none")] + pub chat_enabled: Option, + /// Whether CLI remote control is enabled for the user. + #[serde( + rename = "cli_remote_control_enabled", + skip_serializing_if = "Option::is_none" + )] + pub cli_remote_control_enabled: Option, + /// Whether cloud session storage is enabled for the user. + #[serde( + rename = "cloud_session_storage_enabled", + skip_serializing_if = "Option::is_none" + )] + pub cloud_session_storage_enabled: Option, + /// Whether the Codex agent is enabled for the user. + #[serde( + rename = "codex_agent_enabled", + skip_serializing_if = "Option::is_none" + )] + pub codex_agent_enabled: Option, + /// Copilot plan name for the user (e.g. `individual`, `business`, `enterprise`). + #[serde(rename = "copilot_plan", skip_serializing_if = "Option::is_none")] + pub copilot_plan: Option, + /// Whether `.copilotignore` content-exclusion support is enabled for the user. + #[serde( + rename = "copilotignore_enabled", + skip_serializing_if = "Option::is_none" + )] + pub copilotignore_enabled: Option, + /// Endpoint URLs from the raw Copilot `/copilot_internal/v2/token` user-response passthrough. + #[serde(skip_serializing_if = "Option::is_none")] + pub endpoints: Option, + /// Whether MCP (Model Context Protocol) support is enabled for the user. + #[serde(rename = "is_mcp_enabled", skip_serializing_if = "Option::is_none")] + pub is_mcp_enabled: Option, + /// Whether the user is a GitHub/Microsoft staff member. + #[serde(rename = "is_staff", skip_serializing_if = "Option::is_none")] + pub is_staff: Option, + /// Per-category quota allotments for free/limited-tier users, keyed by quota category. + #[serde( + rename = "limited_user_quotas", + skip_serializing_if = "Option::is_none" + )] + pub limited_user_quotas: Option>, + /// Date the free/limited-tier user's quotas next reset, as a raw string from the Copilot API. + #[serde( + rename = "limited_user_reset_date", + skip_serializing_if = "Option::is_none" + )] + pub limited_user_reset_date: Option, + /// GitHub login of the authenticated user. + #[serde(skip_serializing_if = "Option::is_none")] + pub login: Option, + /// Per-category monthly quota allotments, keyed by quota category. + #[serde(rename = "monthly_quotas", skip_serializing_if = "Option::is_none")] + pub monthly_quotas: Option>, + /// Organizations the user belongs to, each with an optional login and display name. + #[serde(rename = "organization_list", skip_serializing_if = "Option::is_none")] + pub organization_list: Option, + /// Logins of the organizations the user belongs to. + #[serde( + rename = "organization_login_list", + skip_serializing_if = "Option::is_none" + )] + pub organization_login_list: Option>, + /// Date the user's usage quota next resets, as a raw string from the Copilot API; see `quota_reset_date_utc` for the UTC-normalized value. + #[serde(rename = "quota_reset_date", skip_serializing_if = "Option::is_none")] + pub quota_reset_date: Option, + /// UTC-normalized form of `quota_reset_date` (the date the user's usage quota next resets). + #[serde( + rename = "quota_reset_date_utc", + skip_serializing_if = "Option::is_none" + )] + pub quota_reset_date_utc: Option, + /// Quota snapshot map from the raw Copilot user-response passthrough, with chat, completions, premium-interactions, and other entries. + #[serde(rename = "quota_snapshots", skip_serializing_if = "Option::is_none")] + pub quota_snapshots: Option, + /// Whether the user's telemetry is subject to restricted-data handling. + #[serde( + rename = "restricted_telemetry", + skip_serializing_if = "Option::is_none" + )] + pub restricted_telemetry: Option, + /// Raw passthrough of the Copilot API `te` flag for the user (an opaque server-side eligibility signal surfaced in telemetry); not otherwise interpreted by the runtime. + #[serde(skip_serializing_if = "Option::is_none")] + pub te: Option, + /// Whether the account is on usage-based (token/AI-credit) billing rather than a fixed premium-request quota. + #[serde( + rename = "token_based_billing", + skip_serializing_if = "Option::is_none" + )] + pub token_based_billing: Option, +} + +/// Authentication-info input variant for GitHub-internal HMAC auth, carrying the public GitHub host and HMAC secret. +/// +///
+/// /// **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 AgentDiscoveryPathList { - /// Canonical agent create/discovery directories, in priority order - pub paths: Vec, +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, } -/// Agent metadata, including identifiers, display details, source, tools, model, MCP servers, skills, and file path. +/// Authentication-info input variant for a token sourced from an environment variable, with host, optional login, token, and env var name. /// ///
/// @@ -962,49 +1187,49 @@ pub struct AgentDiscoveryPathList { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct AgentInfo { - /// Description of the agent's purpose - pub description: String, - /// Human-readable display name - pub display_name: String, - /// Stable identifier for selection. For most agents this is the same as `name`; for plugin/builtin agents it may differ. Always populated; defaults to `name` when no distinct id was assigned. - pub id: String, - /// MCP server configurations attached to this agent, keyed by server name. Server config shape mirrors the MCP `mcpServers` schema. - /// - ///
- /// - /// **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 mcp_servers: Option>, - /// 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, - /// 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. +pub struct EnvAuthInfo { + /// 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 skills: Option>, - /// Where the agent definition was loaded from + pub copilot_user: Option, + /// Name of the environment variable the token was sourced from. + pub env_var: String, + /// Authentication host (e.g. https://github.com or a GHES host). + pub host: String, + /// User login associated with the token. Undefined for server-to-server tokens (those starting with `ghs_`). #[serde(skip_serializing_if = "Option::is_none")] - pub source: Option, - /// Allowed tool names for this agent. Empty array means none; omitted means inherit defaults. + pub login: Option, + /// The token value itself. Treat as a secret. + pub token: String, + /// Personal access token (PAT) or server-to-server token sourced from an environment variable. + pub r#type: EnvAuthInfoType, +} + +/// Authentication-info input variant for SDK-configured token authentication, carrying host and the secret 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 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 tools: Option>, - /// Whether the agent can be selected directly by the user. Agents marked `false` are subagent-only. + pub copilot_user: Option, + /// Authentication host. + pub host: String, + /// Opaque native GitHub credential registration backing this token identity, when applicable. #[serde(skip_serializing_if = "Option::is_none")] - pub user_invocable: Option, + pub registration_id: Option, + /// 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, } -/// The currently selected custom agent, or null when using the default agent. +/// Authentication-info variant backed by an SDK GitHub token callback. It carries routing metadata but never a plaintext token. /// ///
/// @@ -1014,12 +1239,19 @@ pub struct AgentInfo { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct AgentGetCurrentResult { - /// Currently selected custom agent, or null if using the default agent - pub agent: AgentInfo, +pub struct TokenProviderAuthInfo { + /// Snapshot of the authenticated user's Copilot subscription info, if known. + #[serde(skip_serializing_if = "Option::is_none")] + pub copilot_user: Option, + /// Authentication host. + pub host: String, + /// Opaque SDK callback registration identifier. + pub registration_id: String, + /// SDK callback-backed GitHub token authentication. + pub r#type: TokenProviderAuthInfoType, } -/// Agents available to the session. +/// Authentication-info variant for direct Copilot API token auth sourced from environment variables, with public GitHub host. /// ///
/// @@ -1029,12 +1261,17 @@ pub struct AgentGetCurrentResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct AgentList { - /// Available agents - pub agents: Vec, +pub struct CopilotApiTokenAuthInfo { + /// 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 (always the public GitHub host). + pub host: CopilotApiTokenAuthInfoHost, + /// Direct Copilot API authentication via the `GITHUB_COPILOT_API_TOKEN` + `COPILOT_API_URL` environment-variable pair. The token itself is read from the environment by the runtime, not carried in this struct. + pub r#type: CopilotApiTokenAuthInfoType, } -/// Controls whether built-in agents and authored prompt text are included. +/// Authentication-info variant for OAuth user auth, with host and login; the token remains in the runtime secret store. /// ///
/// @@ -1044,16 +1281,19 @@ pub struct AgentList { ///
#[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. +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 include_prompt: Option, + 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, } -/// Full registry entry for the spawned child. Lets the controller call `handleLiveTargetSelected(entry)` directly without re-reading the registry (avoids a TOCTOU window). +/// Authentication-info input variant for GitHub CLI credentials, carrying host, login, and the `gh auth token` value. /// ///
/// @@ -1063,57 +1303,21 @@ pub struct AgentListRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct AgentRegistryLiveTargetEntry { - /// Kind of attention required when status === "attention". Meaningful only when status === "attention". - #[serde(skip_serializing_if = "Option::is_none")] - pub attention_kind: Option, - /// Git branch of the session (when known) - #[serde(skip_serializing_if = "Option::is_none")] - pub branch: Option, - /// Copilot CLI version that wrote the entry - pub copilot_version: String, - /// Working directory of the session (when known) +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 cwd: Option, - /// Bind host for the entry's JSON-RPC server - pub host: String, - /// Process kind tag for the registry entry - pub kind: AgentRegistryLiveTargetEntryKind, - /// Wall-clock milliseconds since the watcher last observed this entry (heartbeat freshness) - pub last_seen_ms: i64, - /// How the most recent turn ended (clean vs aborted). Lets the renderer distinguish done from done_cancelled. - #[serde(skip_serializing_if = "Option::is_none")] - pub last_terminal_event: Option, - /// Model identifier currently selected for the session - #[serde(skip_serializing_if = "Option::is_none")] - pub model: Option, - /// Operating-system pid of the process owning this entry - pub pid: i64, - /// TCP port the entry's JSON-RPC server is listening on - pub port: i64, - /// Registry entry schema version (1 = ui-server, 2 = managed-server) - pub schema_version: i64, - /// Session ID of the foreground session for this entry - #[serde(skip_serializing_if = "Option::is_none")] - pub session_id: Option, - /// Friendly session name (when set) - #[serde(skip_serializing_if = "Option::is_none")] - pub session_name: Option, - /// ISO 8601 timestamp captured at registration - pub started_at: String, - /// Coarse lifecycle status of the foreground session - #[serde(skip_serializing_if = "Option::is_none")] - pub status: Option, - /// Monotonic per-publisher revision counter incremented on every status update. Lets watchers detect transient flips. - #[serde(skip_serializing_if = "Option::is_none")] - pub status_revision: Option, - /// Connection token (null when the target is unauthenticated) - #[doc(hidden)] - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) token: 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, } -/// Per-spawn log-capture outcome; populated from spawnLiveTarget. +/// Authentication-info input variant for API-key authentication to a non-GitHub LLM provider, carrying the secret `apiKey` and host. /// ///
/// @@ -1123,21 +1327,19 @@ pub struct AgentRegistryLiveTargetEntry { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct AgentRegistryLogCapture { - /// Whether per-spawn log capture is on (false when env-disabled or open failed) - pub enabled: bool, - /// Human-readable open failure message (only set when enabled === false AND the env-disable opt-out was NOT used) - #[serde(skip_serializing_if = "Option::is_none")] - pub open_error: Option, - /// Categorized reason for log-open failure - #[serde(skip_serializing_if = "Option::is_none")] - pub open_error_reason: Option, - /// Absolute path to the per-spawn log file (only set when enabled) +pub struct ApiKeyAuthInfo { + /// The API key. Treat as a secret. + pub api_key: String, + /// 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 path: Option, + pub copilot_user: Option, + /// Authentication host. + pub host: String, + /// API-key authentication for non-GitHub LLM providers (e.g. when running BYOM-style). + pub r#type: ApiKeyAuthInfoType, } -/// `child_process.spawn` itself failed before the child entered the registry. +/// Authenticated account entry returned by `account.getAllUsers`. /// ///
/// @@ -1145,39 +1347,20 @@ pub struct AgentRegistryLogCapture { /// 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 AgentRegistrySpawnError { - /// Underlying errno code (e.g. ENOENT, EACCES) when available +pub struct AccountAllUsers { + /// Authentication information for this user + pub auth_info: AuthInfo, + /// Opaque identifier accepted by account and model selection APIs #[serde(skip_serializing_if = "Option::is_none")] - pub code: Option, - /// Discriminator: child_process.spawn itself failed - pub kind: AgentRegistrySpawnErrorKind, - /// Human-readable error message - pub message: String, -} - -/// Spawn succeeded but the child did not publish a matching managed-server entry within the timeout. -/// -///
-/// -/// **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 AgentRegistrySpawnRegistryTimeout { - /// Process ID of the orphaned child (so the caller can offer 'kill the pid' guidance) - pub child_pid: i64, - /// Discriminator: spawn succeeded but child never registered - pub kind: AgentRegistrySpawnRegistryTimeoutKind, - /// Per-spawn log-capture outcome; populated from spawnLiveTarget. + pub selection_id: Option, + /// Associated token, if available #[serde(skip_serializing_if = "Option::is_none")] - pub log_capture: Option, + pub token: Option, } -/// Inputs to spawn a managed-server child via the controller's spawn delegate. +/// Current authentication state /// ///
/// @@ -1187,27 +1370,16 @@ pub struct AgentRegistrySpawnRegistryTimeout { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct AgentRegistrySpawnRequest { - /// Custom or built-in agent name (e.g. 'explore'). When omitted, the child uses its own default. - #[serde(skip_serializing_if = "Option::is_none")] - pub agent_name: Option, - /// Working directory for the spawned child (must be an existing directory) - pub cwd: String, - /// Optional first user message. Forwarded to the caller (the CLI's spawn wrapper sends it post-attach via the standard LocalRpcSession.send path). - #[serde(skip_serializing_if = "Option::is_none")] - pub initial_prompt: Option, - /// Model identifier to apply to the new session - #[serde(skip_serializing_if = "Option::is_none")] - pub model: Option, - /// Friendly session name. Must satisfy validateSessionName: non-empty, no leading/trailing whitespace, <=100 chars, no control chars, no double quotes. +pub struct AccountGetCurrentAuthResult { + /// Authentication errors from the last auth attempt, if any #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, - /// Permission posture for the new session. 'yolo' requires the controller-local session to currently be in allow-all mode. + pub auth_errors: Option>, + /// Current authentication information, if authenticated #[serde(skip_serializing_if = "Option::is_none")] - pub permission_mode: Option, + pub auth_info: Option, } -/// Managed-server child was spawned and registered successfully. +/// Optional opaque account selection or compatibility GitHub token used to look up quota. /// ///
/// @@ -1217,23 +1389,16 @@ pub struct AgentRegistrySpawnRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct AgentRegistrySpawnSpawned { - /// Full registry entry for the spawned child. Lets the controller call `handleLiveTargetSelected(entry)` directly without re-reading the registry (avoids a TOCTOU window). - pub entry: AgentRegistryLiveTargetEntry, - /// If the delegate attempted to send the initial prompt and failed, the categorized error message. - #[serde(skip_serializing_if = "Option::is_none")] - pub initial_prompt_error: Option, - /// Whether the delegate already sent the initial prompt. Always omitted in the current wiring: the controller sends the prompt post-attach via the standard LocalRpcSession.send path. +pub struct AccountGetQuotaRequest { + /// GitHub token accepted for compatibility with existing SDK clients. When provided, resolves this token instead of using the current account. #[serde(skip_serializing_if = "Option::is_none")] - pub initial_prompt_sent: Option, - /// Discriminator: managed-server child spawned successfully - pub kind: AgentRegistrySpawnSpawnedKind, - /// Per-spawn log-capture outcome; populated from spawnLiveTarget. + pub git_hub_token: Option, + /// Opaque account identifier returned by `account.getAllUsers`. When omitted, the current account is used. #[serde(skip_serializing_if = "Option::is_none")] - pub log_capture: Option, + pub selection_id: Option, } -/// Synchronous pre-validation rejected the spawn request. +/// Quota usage snapshot for a Copilot quota type, including entitlement, used requests, overage, reset date, and remaining percentage. /// ///
/// @@ -1243,19 +1408,27 @@ pub struct AgentRegistrySpawnSpawned { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct AgentRegistrySpawnValidationError { - /// Which parameter field was invalid. Omitted when the rejection is not field-specific. +pub struct AccountQuotaSnapshot { + /// Number of requests included in the entitlement, or -1 for unlimited entitlements + pub entitlement_requests: i64, + /// Whether the user has an unlimited usage entitlement + pub is_unlimited_entitlement: bool, + /// Number of additional usage requests made this period + pub overage: f64, + /// Whether additional usage is allowed when quota is exhausted + pub overage_allowed_with_exhausted_quota: bool, + /// Percentage of entitlement remaining + pub remaining_percentage: f64, + /// Date when the quota resets (ISO 8601 string) #[serde(skip_serializing_if = "Option::is_none")] - pub field: Option, - /// Discriminator: synchronous pre-validation rejected the request - pub kind: AgentRegistrySpawnValidationErrorKind, - /// Human-readable explanation; safe to surface in the UI banner. Never logged to unrestricted telemetry. - pub message: String, - /// Categorized reason for the rejection. Low-cardinality enum so telemetry can aggregate by reason without leaking raw paths or agent/model names. - pub reason: AgentRegistrySpawnValidationErrorReason, + pub reset_date: Option, + /// Whether usage is still permitted after quota exhaustion + pub usage_allowed_with_exhausted_quota: bool, + /// Number of requests used so far this period + pub used_requests: i64, } -/// Custom agents available to the session after reloading definitions from disk. +/// Quota usage snapshots for the resolved user, keyed by quota type. /// ///
/// @@ -1265,12 +1438,12 @@ pub struct AgentRegistrySpawnValidationError { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct AgentReloadResult { - /// Reloaded custom agents - pub agents: Vec, +pub struct AccountGetQuotaResult { + /// Quota snapshots keyed by type (e.g., chat, completions, premium_interactions) + pub quota_snapshots: HashMap, } -/// Optional project paths to include in agent discovery. +/// Credentials to validate and store. Omit login to resolve the authenticated user from the token. /// ///
/// @@ -1280,16 +1453,17 @@ pub struct AgentReloadResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct AgentsDiscoverRequest { - /// When true, omit the host's agents (the user-level agent directory and all plugin agents), leaving only project and remote agents. For multitenant deployments. - #[serde(skip_serializing_if = "Option::is_none")] - pub exclude_host_agents: Option, - /// Optional list of project directory paths to scan for project-scoped agents. When omitted or empty, only user/plugin/remote-independent agents are returned (no project scan). +pub struct AccountLoginRequest { + /// GitHub host URL + pub host: String, + /// User login/username. When omitted, the runtime validates the token and resolves the login from GitHub. #[serde(skip_serializing_if = "Option::is_none")] - pub project_paths: Option>, + pub login: Option, + /// GitHub authentication token + pub token: String, } -/// Name of the custom agent to select for subsequent turns. +/// Result of a successful login; throws on failure /// ///
/// @@ -1299,12 +1473,12 @@ pub struct AgentsDiscoverRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct AgentSelectRequest { - /// Name of the custom agent to select - pub name: String, +pub struct AccountLoginResult { + /// Whether the credential was persisted to a secure store (system keychain, or the config file when plaintext storage is enabled). False when no secure store was available and the token was not saved, so the consumer can decide how to proceed. + pub stored_in_vault: bool, } -/// The newly selected custom agent. +/// User to log out /// ///
/// @@ -1314,12 +1488,16 @@ pub struct AgentSelectRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct AgentSelectResult { - /// The newly selected custom agent - pub agent: AgentInfo, +pub struct AccountLogoutRequest { + /// Authentication information for the user to log out + #[serde(skip_serializing_if = "Option::is_none")] + pub auth_info: Option, + /// Opaque account identifier returned by `account.getAllUsers` + #[serde(skip_serializing_if = "Option::is_none")] + pub selection_id: Option, } -/// An in-memory authored prompt override for an available agent. +/// Logout result indicating if more users remain /// ///
/// @@ -1329,14 +1507,12 @@ pub struct AgentSelectResult { ///
#[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, +pub struct AccountLogoutResult { + /// Whether other authenticated users remain after logout + pub has_more_users: bool, } -/// Optional project paths to include when enumerating agent discovery directories. +/// Canonical directory where custom agents can be discovered or created, with scope, preference, and optional project path. /// ///
/// @@ -1346,16 +1522,19 @@ pub struct AgentSetPromptRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct AgentsGetDiscoveryPathsRequest { - /// When true, omit the host's user-level agent directory, leaving only project directories. For multitenant deployments (mirrors `discover`'s `excludeHostAgents`). - #[serde(skip_serializing_if = "Option::is_none")] - pub exclude_host_agents: Option, - /// Optional list of project directory paths. When omitted or empty, only the user-level directory is returned. +pub struct AgentDiscoveryPath { + /// Absolute path of the search/create directory (may not exist on disk yet) + pub path: String, + /// Whether this is the canonical directory to create a new agent in its tier. At most one entry per tier is 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_paths: Option>, + pub project_path: Option, + /// Which tier this directory belongs to + pub scope: AgentDiscoveryPathScope, } -/// Indicates whether the operation succeeded and reports the post-mutation state. +/// Canonical locations where custom agents can be created so the runtime will recognize them. /// ///
/// @@ -1365,17 +1544,12 @@ pub struct AgentsGetDiscoveryPathsRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct AllowAllPermissionSetResult { - /// 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 AgentDiscoveryPathList { + /// Canonical agent create/discovery directories, in priority order + pub paths: Vec, } -/// Current allow-all permission mode. +/// Agent metadata, including identifiers, display details, source, tools, model, models, MCP servers, skills, and file path. /// ///
/// @@ -1385,17 +1559,57 @@ pub struct AllowAllPermissionSetResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct AllowAllPermissionState { - /// Whether full allow-all permissions are currently active - pub enabled: bool, - /// Current allow-all mode +pub struct AgentInfo { + /// Description of the agent's purpose + pub description: String, + /// Human-readable display name + pub display_name: String, + /// Stable identifier for selection. For most agents this is the same as `name`; for plugin/builtin agents it may differ. Always populated; defaults to `name` when no distinct id was assigned. + pub id: String, + /// MCP server configurations attached to this agent, keyed by server name. Server config shape mirrors the MCP `mcpServers` schema. + /// + ///
+ /// + /// **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 mode: Option, -} - -/// Endpoint URLs from the raw Copilot `/copilot_internal/v2/token` user-response passthrough. -/// -///
+ pub mcp_servers: Option>, + /// 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, + /// Whether authored models are preferences or required constraints. + #[serde(skip_serializing_if = "Option::is_none")] + pub model_policy: Option, + /// Authored preferred model ids for this agent, in priority order. Runtime model selection chooses the first available model; omitted means no authored preference. + #[serde(skip_serializing_if = "Option::is_none")] + pub models: Option>, + /// 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>, + /// Where the agent definition was loaded from + #[serde(skip_serializing_if = "Option::is_none")] + pub source: Option, + /// Allowed tool names for this agent. Empty array means none; omitted means inherit defaults. + #[serde(skip_serializing_if = "Option::is_none")] + pub tools: Option>, + /// Whether the agent can be selected directly by the user. Agents marked `false` are subagent-only. + #[serde(skip_serializing_if = "Option::is_none")] + pub user_invocable: Option, +} + +/// The currently selected custom agent, or null when using the default agent. +/// +///
/// /// **Experimental.** This type is part of an experimental wire-protocol surface /// and may change or be removed in future SDK or CLI releases. @@ -1403,20 +1617,12 @@ pub struct AllowAllPermissionState { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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")] - pub proxy: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub telemetry: Option, +pub struct AgentGetCurrentResult { + /// Currently selected custom agent, or null if using the default agent + pub agent: AgentInfo, } -/// Chat quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. +/// Agents available to the session. /// ///
/// @@ -1426,49 +1632,12 @@ pub struct CopilotUserResponseEndpoints { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CopilotUserResponseQuotaSnapshotsChat { - /// Number of requests/units included in the entitlement for this period; `-1` denotes an unlimited entitlement. - #[serde(skip_serializing_if = "Option::is_none")] - pub entitlement: Option, - /// Whether the user currently has quota available; when `false` and not unlimited, further requests are blocked until the quota resets. - #[serde(rename = "has_quota", skip_serializing_if = "Option::is_none")] - pub has_quota: Option, - /// Count of additional pay-per-request usage consumed this period beyond the entitlement. - #[serde(rename = "overage_count", skip_serializing_if = "Option::is_none")] - pub overage_count: Option, - /// Whether usage may continue at pay-per-request rates once the entitlement is exhausted. - #[serde(rename = "overage_permitted", skip_serializing_if = "Option::is_none")] - pub overage_permitted: Option, - /// Percentage of the entitlement remaining at the snapshot timestamp. - #[serde(rename = "percent_remaining", skip_serializing_if = "Option::is_none")] - pub percent_remaining: Option, - /// Identifier of the quota bucket this snapshot describes. - #[serde(rename = "quota_id", skip_serializing_if = "Option::is_none")] - pub quota_id: Option, - /// Amount of quota remaining at the snapshot timestamp. - #[serde(rename = "quota_remaining", skip_serializing_if = "Option::is_none")] - pub quota_remaining: Option, - /// Unix epoch time, in seconds, when this quota next resets. - #[serde(rename = "quota_reset_at", skip_serializing_if = "Option::is_none")] - pub quota_reset_at: Option, - /// Remaining entitlement/quota amount at the snapshot timestamp. - #[serde(skip_serializing_if = "Option::is_none")] - pub remaining: Option, - /// UTC timestamp when this snapshot was captured. - #[serde(rename = "timestamp_utc", skip_serializing_if = "Option::is_none")] - pub timestamp_utc: Option, - /// Whether this category uses usage-based (token/AI-credit) billing rather than a fixed premium-request count. - #[serde( - rename = "token_based_billing", - skip_serializing_if = "Option::is_none" - )] - pub token_based_billing: Option, - /// Whether the entitlement for this category is unlimited. - #[serde(skip_serializing_if = "Option::is_none")] - pub unlimited: Option, +pub struct AgentList { + /// Available agents + pub agents: Vec, } -/// Completions quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. +/// Controls whether built-in agents and authored prompt text are included. /// ///
/// @@ -1478,49 +1647,16 @@ pub struct CopilotUserResponseQuotaSnapshotsChat { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CopilotUserResponseQuotaSnapshotsCompletions { - /// Number of requests/units included in the entitlement for this period; `-1` denotes an unlimited entitlement. - #[serde(skip_serializing_if = "Option::is_none")] - pub entitlement: Option, - /// Whether the user currently has quota available; when `false` and not unlimited, further requests are blocked until the quota resets. - #[serde(rename = "has_quota", skip_serializing_if = "Option::is_none")] - pub has_quota: Option, - /// Count of additional pay-per-request usage consumed this period beyond the entitlement. - #[serde(rename = "overage_count", skip_serializing_if = "Option::is_none")] - pub overage_count: Option, - /// Whether usage may continue at pay-per-request rates once the entitlement is exhausted. - #[serde(rename = "overage_permitted", skip_serializing_if = "Option::is_none")] - pub overage_permitted: Option, - /// Percentage of the entitlement remaining at the snapshot timestamp. - #[serde(rename = "percent_remaining", skip_serializing_if = "Option::is_none")] - pub percent_remaining: Option, - /// Identifier of the quota bucket this snapshot describes. - #[serde(rename = "quota_id", skip_serializing_if = "Option::is_none")] - pub quota_id: Option, - /// Amount of quota remaining at the snapshot timestamp. - #[serde(rename = "quota_remaining", skip_serializing_if = "Option::is_none")] - pub quota_remaining: Option, - /// Unix epoch time, in seconds, when this quota next resets. - #[serde(rename = "quota_reset_at", skip_serializing_if = "Option::is_none")] - pub quota_reset_at: Option, - /// Remaining entitlement/quota amount at the snapshot timestamp. +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 remaining: Option, - /// UTC timestamp when this snapshot was captured. - #[serde(rename = "timestamp_utc", skip_serializing_if = "Option::is_none")] - pub timestamp_utc: Option, - /// Whether this category uses usage-based (token/AI-credit) billing rather than a fixed premium-request count. - #[serde( - rename = "token_based_billing", - skip_serializing_if = "Option::is_none" - )] - pub token_based_billing: Option, - /// Whether the entitlement for this category is unlimited. + 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 unlimited: Option, + pub include_prompt: Option, } -/// Premium-interactions quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. +/// Full registry entry for the spawned child. Lets the controller call `handleLiveTargetSelected(entry)` directly without re-reading the registry (avoids a TOCTOU window). /// ///
/// @@ -1530,49 +1666,57 @@ pub struct CopilotUserResponseQuotaSnapshotsCompletions { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CopilotUserResponseQuotaSnapshotsPremiumInteractions { - /// Number of requests/units included in the entitlement for this period; `-1` denotes an unlimited entitlement. +pub struct AgentRegistryLiveTargetEntry { + /// Kind of attention required when status === "attention". Meaningful only when status === "attention". #[serde(skip_serializing_if = "Option::is_none")] - pub entitlement: Option, - /// Whether the user currently has quota available; when `false` and not unlimited, further requests are blocked until the quota resets. - #[serde(rename = "has_quota", skip_serializing_if = "Option::is_none")] - pub has_quota: Option, - /// Count of additional pay-per-request usage consumed this period beyond the entitlement. - #[serde(rename = "overage_count", skip_serializing_if = "Option::is_none")] - pub overage_count: Option, - /// Whether usage may continue at pay-per-request rates once the entitlement is exhausted. - #[serde(rename = "overage_permitted", skip_serializing_if = "Option::is_none")] - pub overage_permitted: Option, - /// Percentage of the entitlement remaining at the snapshot timestamp. - #[serde(rename = "percent_remaining", skip_serializing_if = "Option::is_none")] - pub percent_remaining: Option, - /// Identifier of the quota bucket this snapshot describes. - #[serde(rename = "quota_id", skip_serializing_if = "Option::is_none")] - pub quota_id: Option, - /// Amount of quota remaining at the snapshot timestamp. - #[serde(rename = "quota_remaining", skip_serializing_if = "Option::is_none")] - pub quota_remaining: Option, - /// Unix epoch time, in seconds, when this quota next resets. - #[serde(rename = "quota_reset_at", skip_serializing_if = "Option::is_none")] - pub quota_reset_at: Option, - /// Remaining entitlement/quota amount at the snapshot timestamp. + pub attention_kind: Option, + /// Git branch of the session (when known) #[serde(skip_serializing_if = "Option::is_none")] - pub remaining: Option, - /// UTC timestamp when this snapshot was captured. - #[serde(rename = "timestamp_utc", skip_serializing_if = "Option::is_none")] - pub timestamp_utc: Option, - /// Whether this category uses usage-based (token/AI-credit) billing rather than a fixed premium-request count. - #[serde( - rename = "token_based_billing", - skip_serializing_if = "Option::is_none" - )] - pub token_based_billing: Option, - /// Whether the entitlement for this category is unlimited. + pub branch: Option, + /// Copilot CLI version that wrote the entry + pub copilot_version: String, + /// Working directory of the session (when known) #[serde(skip_serializing_if = "Option::is_none")] - pub unlimited: Option, + pub cwd: Option, + /// Bind host for the entry's JSON-RPC server + pub host: String, + /// Process kind tag for the registry entry + pub kind: AgentRegistryLiveTargetEntryKind, + /// Wall-clock milliseconds since the watcher last observed this entry (heartbeat freshness) + pub last_seen_ms: i64, + /// How the most recent turn ended (clean vs aborted). Lets the renderer distinguish done from done_cancelled. + #[serde(skip_serializing_if = "Option::is_none")] + pub last_terminal_event: Option, + /// Model identifier currently selected for the session + #[serde(skip_serializing_if = "Option::is_none")] + pub model: Option, + /// Operating-system pid of the process owning this entry + pub pid: i64, + /// TCP port the entry's JSON-RPC server is listening on + pub port: i64, + /// Registry entry schema version (1 = ui-server, 2 = managed-server) + pub schema_version: i64, + /// Session ID of the foreground session for this entry + #[serde(skip_serializing_if = "Option::is_none")] + pub session_id: Option, + /// Friendly session name (when set) + #[serde(skip_serializing_if = "Option::is_none")] + pub session_name: Option, + /// ISO 8601 timestamp captured at registration + pub started_at: String, + /// Coarse lifecycle status of the foreground session + #[serde(skip_serializing_if = "Option::is_none")] + pub status: Option, + /// Monotonic per-publisher revision counter incremented on every status update. Lets watchers detect transient flips. + #[serde(skip_serializing_if = "Option::is_none")] + pub status_revision: Option, + /// Connection token (null when the target is unauthenticated) + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) token: Option, } -/// Quota snapshot map from the raw Copilot user-response passthrough, with chat, completions, premium-interactions, and other entries. +/// Per-spawn log-capture outcome; populated from spawnLiveTarget. /// ///
/// @@ -1582,22 +1726,21 @@ pub struct CopilotUserResponseQuotaSnapshotsPremiumInteractions { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CopilotUserResponseQuotaSnapshots { - /// Chat quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. +pub struct AgentRegistryLogCapture { + /// Whether per-spawn log capture is on (false when env-disabled or open failed) + pub enabled: bool, + /// Human-readable open failure message (only set when enabled === false AND the env-disable opt-out was NOT used) #[serde(skip_serializing_if = "Option::is_none")] - pub chat: Option, - /// Completions quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. + pub open_error: Option, + /// Categorized reason for log-open failure #[serde(skip_serializing_if = "Option::is_none")] - pub completions: Option, - /// Premium-interactions quota snapshot from the raw Copilot user-response passthrough, with entitlement, overage, remaining quota, reset, and billing fields. - #[serde( - rename = "premium_interactions", - skip_serializing_if = "Option::is_none" - )] - pub premium_interactions: Option, + pub open_error_reason: Option, + /// Absolute path to the per-spawn log file (only set when enabled) + #[serde(skip_serializing_if = "Option::is_none")] + pub path: Option, } -/// 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. +/// `child_process.spawn` itself failed before the child entered the registry. /// ///
/// @@ -1607,124 +1750,17 @@ pub struct CopilotUserResponseQuotaSnapshots { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CopilotUserResponse { - /// Copilot access SKU identifier (e.g. `free_limited_copilot`, `copilot_for_business_seat_quota`) used to gate model and feature access. - #[serde(rename = "access_type_sku", skip_serializing_if = "Option::is_none")] - pub access_type_sku: Option, - /// Opaque analytics tracking identifier for the user, forwarded from the Copilot API. - #[serde( - rename = "analytics_tracking_id", - skip_serializing_if = "Option::is_none" - )] - pub analytics_tracking_id: Option, - /// Date the Copilot seat was assigned to the user, if applicable. - #[serde(rename = "assigned_date", skip_serializing_if = "Option::is_none")] - pub assigned_date: Option, - /// Whether the user is eligible to sign up for the free/limited Copilot tier. - #[serde( - rename = "can_signup_for_limited", - skip_serializing_if = "Option::is_none" - )] - pub can_signup_for_limited: Option, - /// Whether the user is able to upgrade their Copilot plan. - #[serde(rename = "can_upgrade_plan", skip_serializing_if = "Option::is_none")] - pub can_upgrade_plan: Option, - /// Whether Copilot chat is enabled for the user. - #[serde(rename = "chat_enabled", skip_serializing_if = "Option::is_none")] - pub chat_enabled: Option, - /// Whether CLI remote control is enabled for the user. - #[serde( - rename = "cli_remote_control_enabled", - skip_serializing_if = "Option::is_none" - )] - pub cli_remote_control_enabled: Option, - /// Whether cloud session storage is enabled for the user. - #[serde( - rename = "cloud_session_storage_enabled", - skip_serializing_if = "Option::is_none" - )] - pub cloud_session_storage_enabled: Option, - /// Whether the Codex agent is enabled for the user. - #[serde( - rename = "codex_agent_enabled", - skip_serializing_if = "Option::is_none" - )] - pub codex_agent_enabled: Option, - /// Copilot plan name for the user (e.g. `individual`, `business`, `enterprise`). - #[serde(rename = "copilot_plan", skip_serializing_if = "Option::is_none")] - pub copilot_plan: Option, - /// Whether `.copilotignore` content-exclusion support is enabled for the user. - #[serde( - rename = "copilotignore_enabled", - skip_serializing_if = "Option::is_none" - )] - pub copilotignore_enabled: Option, - /// Endpoint URLs from the raw Copilot `/copilot_internal/v2/token` user-response passthrough. - #[serde(skip_serializing_if = "Option::is_none")] - pub endpoints: Option, - /// Whether MCP (Model Context Protocol) support is enabled for the user. - #[serde(rename = "is_mcp_enabled", skip_serializing_if = "Option::is_none")] - pub is_mcp_enabled: Option, - /// Whether the user is a GitHub/Microsoft staff member. - #[serde(rename = "is_staff", skip_serializing_if = "Option::is_none")] - pub is_staff: Option, - /// Per-category quota allotments for free/limited-tier users, keyed by quota category. - #[serde( - rename = "limited_user_quotas", - skip_serializing_if = "Option::is_none" - )] - pub limited_user_quotas: Option>, - /// Date the free/limited-tier user's quotas next reset, as a raw string from the Copilot API. - #[serde( - rename = "limited_user_reset_date", - skip_serializing_if = "Option::is_none" - )] - pub limited_user_reset_date: Option, - /// GitHub login of the authenticated user. - #[serde(skip_serializing_if = "Option::is_none")] - pub login: Option, - /// Per-category monthly quota allotments, keyed by quota category. - #[serde(rename = "monthly_quotas", skip_serializing_if = "Option::is_none")] - pub monthly_quotas: Option>, - /// Organizations the user belongs to, each with an optional login and display name. - #[serde(rename = "organization_list", skip_serializing_if = "Option::is_none")] - pub organization_list: Option, - /// Logins of the organizations the user belongs to. - #[serde( - rename = "organization_login_list", - skip_serializing_if = "Option::is_none" - )] - pub organization_login_list: Option>, - /// Date the user's usage quota next resets, as a raw string from the Copilot API; see `quota_reset_date_utc` for the UTC-normalized value. - #[serde(rename = "quota_reset_date", skip_serializing_if = "Option::is_none")] - pub quota_reset_date: Option, - /// UTC-normalized form of `quota_reset_date` (the date the user's usage quota next resets). - #[serde( - rename = "quota_reset_date_utc", - skip_serializing_if = "Option::is_none" - )] - pub quota_reset_date_utc: Option, - /// Quota snapshot map from the raw Copilot user-response passthrough, with chat, completions, premium-interactions, and other entries. - #[serde(rename = "quota_snapshots", skip_serializing_if = "Option::is_none")] - pub quota_snapshots: Option, - /// Whether the user's telemetry is subject to restricted-data handling. - #[serde( - rename = "restricted_telemetry", - skip_serializing_if = "Option::is_none" - )] - pub restricted_telemetry: Option, - /// Raw passthrough of the Copilot API `te` flag for the user (an opaque server-side eligibility signal surfaced in telemetry); not otherwise interpreted by the runtime. +pub struct AgentRegistrySpawnError { + /// Underlying errno code (e.g. ENOENT, EACCES) when available #[serde(skip_serializing_if = "Option::is_none")] - pub te: Option, - /// Whether the account is on usage-based (token/AI-credit) billing rather than a fixed premium-request quota. - #[serde( - rename = "token_based_billing", - skip_serializing_if = "Option::is_none" - )] - pub token_based_billing: Option, + pub code: Option, + /// Discriminator: child_process.spawn itself failed + pub kind: AgentRegistrySpawnErrorKind, + /// Human-readable error message + pub message: String, } -/// Authentication-info variant for API-key authentication to a non-GitHub LLM provider, carrying the secret `apiKey` and host. +/// Spawn succeeded but the child did not publish a matching managed-server entry within the timeout. /// ///
/// @@ -1734,19 +1770,17 @@ pub struct CopilotUserResponse { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ApiKeyAuthInfo { - /// The API key. Treat as a secret. - pub api_key: String, - /// 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 AgentRegistrySpawnRegistryTimeout { + /// Process ID of the orphaned child (so the caller can offer 'kill the pid' guidance) + pub child_pid: i64, + /// Discriminator: spawn succeeded but child never registered + pub kind: AgentRegistrySpawnRegistryTimeoutKind, + /// Per-spawn log-capture outcome; populated from spawnLiveTarget. #[serde(skip_serializing_if = "Option::is_none")] - pub copilot_user: Option, - /// Authentication host. - pub host: String, - /// API-key authentication for non-GitHub LLM providers (e.g. when running BYOM-style). - pub r#type: ApiKeyAuthInfoType, + pub log_capture: Option, } -/// Blob attachment with inline base64-encoded data +/// Inputs to spawn a managed-server child via the controller's spawn delegate. /// ///
/// @@ -1756,29 +1790,27 @@ pub struct ApiKeyAuthInfo { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct AttachmentBlob { - /// Internal: content-addressed id of the session.binary_asset event holding this attachment's model-facing bytes (e.g. "sha256:..."). Absent externally. +pub struct AgentRegistrySpawnRequest { + /// Custom or built-in agent name (e.g. 'explore'). When omitted, the child uses its own default. #[serde(skip_serializing_if = "Option::is_none")] - pub asset_id: Option, - /// Internal: decoded byte length of the attachment's model-facing bytes. Absent externally. + pub agent_name: Option, + /// Working directory for the spawned child (must be an existing directory) + pub cwd: String, + /// Optional first user message. Forwarded to the caller (the CLI's spawn wrapper sends it post-attach via the standard LocalRpcSession.send path). #[serde(skip_serializing_if = "Option::is_none")] - pub byte_length: Option, - /// Base64-encoded content. Present on input and for external consumers; replaced by an internal `assetId` reference in persisted events when interned to a content-addressed asset. + pub initial_prompt: Option, + /// Model identifier to apply to the new session #[serde(skip_serializing_if = "Option::is_none")] - pub data: Option, - /// User-facing display name for the attachment + pub model: Option, + /// Friendly session name. Must satisfy validateSessionName: non-empty, no leading/trailing whitespace, <=100 chars, no control chars, no double quotes. #[serde(skip_serializing_if = "Option::is_none")] - pub display_name: Option, - /// MIME type of the inline data - pub mime_type: String, - /// Internal: why model-facing bytes are absent from persistence. Absent externally. + pub name: Option, + /// Permission posture for the new session. 'yolo' requires the controller-local session to currently be in allow-all mode. #[serde(skip_serializing_if = "Option::is_none")] - pub omitted_reason: Option, - /// Attachment type discriminator - pub r#type: AttachmentBlobType, + pub permission_mode: Option, } -/// Directory attachment +/// Managed-server child was spawned and registered successfully. /// ///
/// @@ -1788,19 +1820,23 @@ pub struct AttachmentBlob { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct AttachmentDirectory { - /// User-facing display name for the attachment - pub display_name: String, - /// Absolute directory path - pub path: String, - /// Frozen rendered line this attachment contributed to the prompt block (e.g. "* /path (12 items)"). Captured at send time so resumed history reproduces the exact text the model saw, independent of later filesystem changes. +pub struct AgentRegistrySpawnSpawned { + /// Full registry entry for the spawned child. Lets the controller call `handleLiveTargetSelected(entry)` directly without re-reading the registry (avoids a TOCTOU window). + pub entry: AgentRegistryLiveTargetEntry, + /// If the delegate attempted to send the initial prompt and failed, the categorized error message. #[serde(skip_serializing_if = "Option::is_none")] - pub tagged_files_entry: Option, - /// Attachment type discriminator - pub r#type: AttachmentDirectoryType, + pub initial_prompt_error: Option, + /// Whether the delegate already sent the initial prompt. Always omitted in the current wiring: the controller sends the prompt post-attach via the standard LocalRpcSession.send path. + #[serde(skip_serializing_if = "Option::is_none")] + pub initial_prompt_sent: Option, + /// Discriminator: managed-server child spawned successfully + pub kind: AgentRegistrySpawnSpawnedKind, + /// Per-spawn log-capture outcome; populated from spawnLiveTarget. + #[serde(skip_serializing_if = "Option::is_none")] + pub log_capture: Option, } -/// Structured context contributed by an extension. Composer pills displayed in the host are forwarded back through session.send.attachments, then rendered into the model prompt as an XML block. +/// Synchronous pre-validation rejected the spawn request. /// ///
/// @@ -1810,27 +1846,19 @@ pub struct AttachmentDirectory { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct AttachmentExtensionContext { - /// Provider-local canvas identifier when the push was bound to a canvas instance - #[serde(skip_serializing_if = "Option::is_none")] - pub canvas_id: Option, - /// ISO 8601 timestamp captured by the runtime when the push was accepted - pub captured_at: String, - /// Owning extension identifier. Runtime-derived from the caller's connection when produced via session.extensions.sendAttachmentsToMessage; preserved verbatim on subsequent transports. - pub extension_id: String, - /// Open canvas instance identifier when the push was bound to a canvas instance - #[serde(skip_serializing_if = "Option::is_none")] - pub instance_id: Option, - /// Caller-supplied JSON payload +pub struct AgentRegistrySpawnValidationError { + /// Which parameter field was invalid. Omitted when the rejection is not field-specific. #[serde(skip_serializing_if = "Option::is_none")] - pub payload: Option, - /// Human-readable composer pill label - pub title: String, - /// Attachment type discriminator - pub r#type: AttachmentExtensionContextType, + pub field: Option, + /// Discriminator: synchronous pre-validation rejected the request + pub kind: AgentRegistrySpawnValidationErrorKind, + /// Human-readable explanation; safe to surface in the UI banner. Never logged to unrestricted telemetry. + pub message: String, + /// Categorized reason for the rejection. Low-cardinality enum so telemetry can aggregate by reason without leaking raw paths or agent/model names. + pub reason: AgentRegistrySpawnValidationErrorReason, } -/// Optional line range to scope the attachment to a specific section of the file +/// Custom agents available to the session after reloading definitions from disk. /// ///
/// @@ -1840,14 +1868,12 @@ pub struct AttachmentExtensionContext { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct AttachmentFileLineRange { - /// End line number (1-based, inclusive) - pub end: i64, - /// Start line number (1-based) - pub start: i64, +pub struct AgentReloadResult { + /// Reloaded custom agents + pub agents: Vec, } -/// File attachment +/// Optional project paths to include in agent discovery. /// ///
/// @@ -1857,34 +1883,16 @@ pub struct AttachmentFileLineRange { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct AttachmentFile { - /// Internal: content-addressed id of the session.binary_asset event holding this attachment's model-facing bytes (e.g. "sha256:..."). Absent externally. - #[serde(skip_serializing_if = "Option::is_none")] - pub asset_id: Option, - /// Internal: decoded byte length of the attachment's model-facing bytes. Absent externally. - #[serde(skip_serializing_if = "Option::is_none")] - pub byte_length: Option, - /// 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, - /// Internal: MIME type of the file's model-facing bytes (post-resize for images). Set when the file's bytes are interned to an asset. Absent externally. - #[serde(skip_serializing_if = "Option::is_none")] - pub mime_type: Option, - /// Internal: why model-facing bytes are absent from persistence. Absent externally. +pub struct AgentsDiscoverRequest { + /// When true, omit the host's agents (the user-level agent directory and all plugin agents), leaving only project and remote agents. For multitenant deployments. #[serde(skip_serializing_if = "Option::is_none")] - pub omitted_reason: Option, - /// Absolute file path - pub path: String, - /// Frozen rendered line this attachment contributed to the prompt block (e.g. "* /path (123 lines)"). Captured at send time so resumed history reproduces the exact text the model saw, independent of later filesystem changes. Present only for attachments routed to (mutually exclusive with assetId, which marks bytes sent natively). + pub exclude_host_agents: Option, + /// Optional list of project directory paths to scan for project-scoped agents. When omitted or empty, only user/plugin/remote-independent agents are returned (no project scan). #[serde(skip_serializing_if = "Option::is_none")] - pub tagged_files_entry: Option, - /// Attachment type discriminator - pub r#type: AttachmentFileType, + pub project_paths: Option>, } -/// Pointer to a GitHub repository. +/// Name of the custom agent to select for subsequent turns. /// ///
/// @@ -1894,12 +1902,216 @@ pub struct AttachmentFile { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct GitHubRepoRef { - /// Numeric GitHub repository id - #[serde(skip_serializing_if = "Option::is_none")] - pub id: Option, - /// Repository name (without owner) - pub name: String, +pub struct AgentSelectRequest { + /// Name of the custom agent to select + pub name: String, +} + +/// The newly selected custom 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 AgentSelectResult { + /// The newly selected custom agent + 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. +/// +///
+/// +/// **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 AgentsGetDiscoveryPathsRequest { + /// When true, omit the host's user-level agent directory, leaving only project directories. For multitenant deployments (mirrors `discover`'s `excludeHostAgents`). + #[serde(skip_serializing_if = "Option::is_none")] + pub exclude_host_agents: Option, + /// Optional list of project directory paths. When omitted or empty, only the user-level directory is returned. + #[serde(skip_serializing_if = "Option::is_none")] + pub project_paths: Option>, +} + +/// Blob attachment with inline base64-encoded data +/// +///
+/// +/// **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 AttachmentBlob { + /// Internal: content-addressed id of the session.binary_asset event holding this attachment's model-facing bytes (e.g. "sha256:..."). Absent externally. + #[serde(skip_serializing_if = "Option::is_none")] + pub asset_id: Option, + /// Internal: decoded byte length of the attachment's model-facing bytes. Absent externally. + #[serde(skip_serializing_if = "Option::is_none")] + pub byte_length: Option, + /// Base64-encoded content. Present on input and for external consumers; replaced by an internal `assetId` reference in persisted events when interned to a content-addressed asset. + #[serde(skip_serializing_if = "Option::is_none")] + pub data: Option, + /// 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, + /// Internal: why model-facing bytes are absent from persistence. Absent externally. + #[serde(skip_serializing_if = "Option::is_none")] + pub omitted_reason: Option, + /// Attachment type discriminator + pub r#type: AttachmentBlobType, +} + +/// Directory attachment +/// +///
+/// +/// **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 AttachmentDirectory { + /// User-facing display name for the attachment + pub display_name: String, + /// Absolute directory path + pub path: String, + /// Frozen rendered line this attachment contributed to the prompt block (e.g. "* /path (12 items)"). Captured at send time so resumed history reproduces the exact text the model saw, independent of later filesystem changes. + #[serde(skip_serializing_if = "Option::is_none")] + pub tagged_files_entry: Option, + /// Attachment type discriminator + pub r#type: AttachmentDirectoryType, +} + +/// Structured context contributed by an extension. Composer pills displayed in the host are forwarded back through session.send.attachments, then rendered into the model prompt as an XML block. +/// +///
+/// +/// **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 AttachmentExtensionContext { + /// Provider-local canvas identifier when the push was bound to a canvas instance + #[serde(skip_serializing_if = "Option::is_none")] + pub canvas_id: Option, + /// ISO 8601 timestamp captured by the runtime when the push was accepted + pub captured_at: String, + /// Owning extension identifier. Runtime-derived from the caller's connection when produced via session.extensions.sendAttachmentsToMessage; preserved verbatim on subsequent transports. + pub extension_id: String, + /// Open canvas instance identifier when the push was bound to a canvas instance + #[serde(skip_serializing_if = "Option::is_none")] + pub instance_id: Option, + /// Caller-supplied JSON payload + #[serde(skip_serializing_if = "Option::is_none")] + pub payload: Option, + /// Human-readable composer pill label + pub title: String, + /// Attachment type discriminator + pub r#type: AttachmentExtensionContextType, +} + +/// Optional line range to scope the attachment to a specific section of the file +/// +///
+/// +/// **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 AttachmentFileLineRange { + /// End line number (1-based, inclusive) + pub end: i64, + /// Start line number (1-based) + pub start: i64, +} + +/// File attachment +/// +///
+/// +/// **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 AttachmentFile { + /// Internal: content-addressed id of the session.binary_asset event holding this attachment's model-facing bytes (e.g. "sha256:..."). Absent externally. + #[serde(skip_serializing_if = "Option::is_none")] + pub asset_id: Option, + /// Internal: decoded byte length of the attachment's model-facing bytes. Absent externally. + #[serde(skip_serializing_if = "Option::is_none")] + pub byte_length: Option, + /// 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, + /// Internal: MIME type of the file's model-facing bytes (post-resize for images). Set when the file's bytes are interned to an asset. Absent externally. + #[serde(skip_serializing_if = "Option::is_none")] + pub mime_type: Option, + /// Internal: why model-facing bytes are absent from persistence. Absent externally. + #[serde(skip_serializing_if = "Option::is_none")] + pub omitted_reason: Option, + /// Absolute file path + pub path: String, + /// Frozen rendered line this attachment contributed to the prompt block (e.g. "* /path (123 lines)"). Captured at send time so resumed history reproduces the exact text the model saw, independent of later filesystem changes. Present only for attachments routed to (mutually exclusive with assetId, which marks bytes sent natively). + #[serde(skip_serializing_if = "Option::is_none")] + pub tagged_files_entry: Option, + /// Attachment type discriminator + pub r#type: AttachmentFileType, +} + +/// Pointer to a GitHub repository. +/// +///
+/// +/// **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 GitHubRepoRef { + /// 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, } @@ -2247,7 +2459,7 @@ pub struct AttachmentSelection { pub r#type: AttachmentSelectionType, } -/// A well-known model in the runtime's built-in catalog. +/// Credential-free authentication identity safe to expose to hosts and user interfaces. /// ///
/// @@ -2257,12 +2469,26 @@ pub struct AttachmentSelection { ///
#[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, +pub struct AuthIdentity { + /// Snapshot of the authenticated user's Copilot subscription info, if known + #[serde(skip_serializing_if = "Option::is_none")] + pub copilot_user: Option, + /// Name of the environment variable that supplied the credential, when applicable + #[serde(skip_serializing_if = "Option::is_none")] + pub env_var: Option, + /// Authentication host + pub host: String, + /// Authenticated login, when available + #[serde(skip_serializing_if = "Option::is_none")] + pub login: Option, + /// Opaque SDK GitHub credential registration backing this identity. Routing metadata only; never a credential. + #[serde(skip_serializing_if = "Option::is_none")] + pub registration_id: Option, + /// Authentication type + pub r#type: AuthInfoType, } -/// The running runtime's complete catalog of well-known built-in model IDs, including supported models and additional IDs with built-in metadata. +/// Validation error from an authentication attempt. /// ///
/// @@ -2272,12 +2498,15 @@ pub struct BuiltInModelCatalogEntry { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct BuiltInModelCatalog { - /// Built-in model entries. - pub models: Vec, +pub struct AuthValidationError { + /// Optional message returned by GitHub + #[serde(skip_serializing_if = "Option::is_none")] + pub github_message: Option, + /// Authentication validation error message + pub message: String, } -/// Cancellation result for a user-requested shell command. +/// A well-known model in the runtime's built-in catalog. /// ///
/// @@ -2287,12 +2516,12 @@ pub struct BuiltInModelCatalog { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CancelUserRequestedShellCommandResult { - /// Whether an in-flight execution was found and signalled to cancel - pub cancelled: bool, +pub struct BuiltInModelCatalogEntry { + /// Well-known runtime model ID suitable for provider or provider-model metadata. This is not necessarily the provider-facing deployment or model name and does not indicate CAPI entitlement or provider availability. + pub id: String, } -/// Canvas action that the agent or host can invoke. To discover the input schema for a particular action, call the list_canvas_capabilities tool. +/// The running runtime's complete catalog of well-known built-in model IDs, including supported models and additional IDs with built-in metadata. /// ///
/// @@ -2302,18 +2531,12 @@ pub struct CancelUserRequestedShellCommandResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CanvasAction { - /// Description of the action - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, - /// JSON Schema for the action input - #[serde(skip_serializing_if = "Option::is_none")] - pub input_schema: Option, - /// Action name exposed by the canvas provider - pub name: String, +pub struct BuiltInModelCatalog { + /// Built-in model entries. + pub models: Vec, } -/// Canvas action invocation parameters. +/// Custom grammar input format accepted by a built-in tool. /// ///
/// @@ -2323,13 +2546,135 @@ pub struct CanvasAction { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CanvasActionInvokeRequest { - /// Action name to invoke - pub action_name: String, - /// Action input - #[serde(skip_serializing_if = "Option::is_none")] - pub input: Option, - /// Open canvas instance identifier +pub struct BuiltinToolFormat { + /// Grammar definition accepted by the tool. + pub definition: String, + /// Grammar syntax used by the format definition. + pub syntax: String, + /// Custom input-format discriminator. + pub r#type: BuiltinToolFormatType, +} + +/// JSON Schema object accepted by a built-in tool. +/// +///
+/// +/// **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 BuiltinToolInputSchema { + /// Root type of the tool input schema. + pub r#type: BuiltinToolInputSchemaType, +} + +/// Rust-owned metadata and input schema for a built-in tool. +/// +///
+/// +/// **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 BuiltinToolDescriptor { + /// Model-facing description of the tool's behavior. + pub description: String, + /// Optional custom input format used instead of a JSON Schema. + pub format: Option, + /// Whether the tool provides a specialized intention summary. + pub has_summarise_intention: bool, + /// JSON Schema for the tool input, or null when the tool uses a custom format. + pub input_schema: Option, + /// Optional supplemental usage instructions for the tool. + pub instructions: Option, + /// Whether the tool executes commands in a terminal. + pub is_terminal: bool, + /// Stable name used to invoke the built-in tool. + pub name: String, + /// Policy describing which tool metadata may be recorded without obfuscation. + pub safe_for_telemetry: serde_json::Value, + /// Optional human-readable title for the tool. + pub title: Option, + /// Optional tool category discriminator. + pub r#type: Option, +} + +/// Per-field telemetry-safety policy for a built-in tool. +/// +///
+/// +/// **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 BuiltinToolSafeTelemetryFields { + /// Whether tool input names may be included in telemetry without obfuscation. + #[serde(skip_serializing_if = "Option::is_none")] + pub inputs_names: Option, + /// Whether the tool name may be included in telemetry without obfuscation. + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, +} + +/// Cancellation result for a user-requested shell command. +/// +///
+/// +/// **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 CancelUserRequestedShellCommandResult { + /// Whether an in-flight execution was found and signalled to cancel + pub cancelled: bool, +} + +/// Canvas action that the agent or host can invoke. To discover the input schema for a particular action, call the list_canvas_capabilities tool. +/// +///
+/// +/// **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 CanvasAction { + /// Description of the action + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + /// JSON Schema for the action input + #[serde(skip_serializing_if = "Option::is_none")] + pub input_schema: Option, + /// Action name exposed by the canvas provider + pub name: String, +} + +/// Canvas action invocation parameters. +/// +///
+/// +/// **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 CanvasActionInvokeRequest { + /// Action name to invoke + pub action_name: String, + /// Action input + #[serde(skip_serializing_if = "Option::is_none")] + pub input: Option, + /// Open canvas instance identifier pub instance_id: String, } @@ -2646,7 +2991,7 @@ pub struct CanvasProviderOpenResult { pub url: Option, } -/// Options scoped to the built-in CAPI (Copilot API) provider. +/// Internal canvas provider registration parameters. /// ///
/// @@ -2656,13 +3001,16 @@ pub struct CanvasProviderOpenResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CapiSessionOptions { - /// 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. - #[serde(skip_serializing_if = "Option::is_none")] - pub enable_web_socket_responses: Option, +pub struct CanvasProviderRegisterRequest { + /// Canvas contributions supplied by the provider + pub canvases: Vec, + /// Connection identifier for callback routing + pub connection_id: String, + /// Provider metadata supplied by the host + pub info: serde_json::Value, } -/// A literal choice the command input accepts, with a human-facing description +/// Internal canvas provider unregistration parameters. /// ///
/// @@ -2672,14 +3020,12 @@ pub struct CapiSessionOptions { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SlashCommandInputChoice { - /// Human-readable description shown alongside the choice - pub description: String, - /// The literal choice value (e.g. 'on', 'off', 'show') - pub name: String, +pub struct CanvasProviderUnregisterRequest { + /// Connection identifier to unregister + pub connection_id: String, } -/// Optional unstructured input hint +/// Options scoped to the built-in CAPI (Copilot API) provider. /// ///
/// @@ -2689,24 +3035,16 @@ pub struct SlashCommandInputChoice { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SlashCommandInput { - /// Optional literal choices the input accepts, each with a human-facing description; clients may render these as selectable options - #[serde(skip_serializing_if = "Option::is_none")] - pub choices: Option>, - /// Optional completion hint for the input (e.g. 'directory' for filesystem path completion) - #[serde(skip_serializing_if = "Option::is_none")] - pub completion: Option, - /// Hint to display when command input has not been provided - pub hint: String, - /// When true, clients should pass the full text after the command name as a single argument rather than splitting on whitespace +pub struct CapiSessionOptions { + /// Routing preference used when the session model is `auto`. The runtime persists the preference across cold resume. When omitted, the default routing behavior is used. Resuming an already-resident session cannot change its preference. #[serde(skip_serializing_if = "Option::is_none")] - pub preserve_multiline_input: Option, - /// When true, the command requires non-empty input; clients should render the input hint as required + pub auto_tier: Option, + /// 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. #[serde(skip_serializing_if = "Option::is_none")] - pub required: Option, + pub enable_web_socket_responses: Option, } -/// Slash-command metadata with name, aliases, description, kind, input hint, execution allowance, and schedulability. +/// Semantic digest of a strictly parsed and schema-validated JSON MCP card. Both URL-backed and embedded cards are canonicalised with RFC 8785 JSON Canonicalization Scheme, encoded as UTF-8, and hashed with SHA-256. /// ///
/// @@ -2716,30 +3054,14 @@ pub struct SlashCommandInput { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SlashCommandInfo { - /// Canonical aliases without leading slashes - #[serde(skip_serializing_if = "Option::is_none")] - pub aliases: Option>, - /// Whether the command may run while an agent turn is active - pub allow_during_agent_execution: bool, - /// Human-readable command description - pub description: String, - /// Whether the command is experimental - #[serde(skip_serializing_if = "Option::is_none")] - pub experimental: Option, - /// Optional unstructured input hint - #[serde(skip_serializing_if = "Option::is_none")] - pub input: Option, - /// Coarse command category for grouping and behavior: runtime built-in, skill-backed command, or SDK/client-owned command - pub kind: SlashCommandKind, - /// Canonical command name without a leading slash - pub name: String, - /// Whether the command may be the target of `/every` / `/after` schedules. Resolution happens at every tick, so only set this when the command is safe to re-invoke and produces an agent prompt. - #[serde(skip_serializing_if = "Option::is_none")] - pub schedulable: Option, +pub struct CardDigest { + /// Digest algorithm and canonical representation + pub algorithm: CardDigestAlgorithm, + /// SHA-256 digest of the RFC 8785 canonical UTF-8 bytes, encoded as exactly 64 lowercase hexadecimal characters. + pub value: String, } -/// Slash commands available in the session, after applying any include/exclude filters. +/// Where and when an AI skill catalog reference was observed. Discovery provenance deliberately carries no content digest because search does not establish the exact validated content a later plan will bind. /// ///
/// @@ -2749,12 +3071,16 @@ pub struct SlashCommandInfo { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CommandList { - /// Commands available in this session - pub commands: Vec, +pub struct CatalogAiSkillCandidateProvenance { + /// Host of the catalog authority that advertised the reference, without path, query, or credentials. Inert untrusted data. + pub authority: String, + /// Media type advertised for the referenced AI skill card + pub media_type: CatalogAiSkillCandidateProvenanceMediaType, + /// ISO 8601 timestamp at which the runtime observed the catalog reference. This is not a retrieval or validation timestamp. + pub observed_at: String, } -/// Pending command request ID and an optional error if the client handler failed. +/// Candidate whose card is retrieved from a URL through the runtime's hardened fetch boundary. /// ///
/// @@ -2764,15 +3090,14 @@ pub struct CommandList { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CommandsHandlePendingCommandRequest { - /// Error message if the command handler failed - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, - /// Request ID from the command invocation event - pub request_id: RequestId, +pub struct CatalogCandidateSourceUrl { + /// Discriminator: the card is URL-backed, and carries no embedded data + pub kind: CatalogCandidateSourceUrlKind, + /// Card URL as advertised. Inert untrusted data: the runtime retrieves it only through its own hardened boundary, and it is never logged. + pub url: String, } -/// Indicates whether the pending client-handled command was completed successfully. +/// Candidate whose card reference arrived inline. The document and its content-derived properties stay behind the runtime boundary. /// ///
/// @@ -2782,12 +3107,12 @@ pub struct CommandsHandlePendingCommandRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CommandsHandlePendingCommandResult { - /// Whether the command was handled successfully - pub success: bool, +pub struct CatalogCandidateSourceEmbedded { + /// Discriminator: the card is embedded, and carries no URL + pub kind: CatalogCandidateSourceEmbeddedKind, } -/// Slash command name and optional raw input string to invoke. +/// An inert AI skill catalog result. AI skills are discovery-only and cannot be represented as installable through this surface. /// ///
/// @@ -2795,17 +3120,34 @@ pub struct CommandsHandlePendingCommandResult { /// 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 CommandsInvokeRequest { - /// Raw input after the command name +pub struct CatalogAiSkillCandidate { + /// Description taken verbatim from the card. Inert untrusted text. #[serde(skip_serializing_if = "Option::is_none")] - pub input: Option, - /// Command name. Leading slashes are stripped and the name is matched case-insensitively. - pub name: String, + pub description: Option, + /// Display name taken verbatim from the card. Inert untrusted text. + pub display_name: String, + /// Opaque, runtime-instance scoped, TTL-bound, single-use handle for this candidate. Carries no readable information and is rejected when stale, replayed, or presented to a different runtime instance. Never logged. + pub handle: String, + /// ISO 8601 timestamp after which the handle is stale and will be rejected. + pub handle_expires_at: String, + /// AI skills are discovery-only and cannot be installed through this surface + pub installability: CatalogAiSkillCandidateInstallability, + /// Discriminator: this candidate describes an AI skill + pub kind: CatalogAiSkillCandidateKind, + /// Media type of the underlying AI skill card + pub media_type: CatalogAiSkillCandidateMediaType, + /// Where the catalog reference was observed, without the card itself or any content digest. + pub provenance: CatalogAiSkillCandidateProvenance, + /// Publisher taken verbatim from the card. Inert untrusted text. + #[serde(skip_serializing_if = "Option::is_none")] + pub publisher: Option, + /// Where the card came from: exactly one of a URL or embedded data, encoded as a tagged union so neither both nor neither can be represented. + pub source: CatalogCandidateSource, } -/// Optional filters controlling which command sources to include in the listing. +/// An optional catalog authentication exchange did not establish the caller's identity. Anonymous search remains supported; this refusal is reserved for an operation that cannot continue after the attempted exchange. It is distinct from `policy-rejected` and from a network failure, and the reason identifies the recovery action. /// ///
/// @@ -2815,19 +3157,16 @@ pub struct CommandsInvokeRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CommandsListRequest { - /// Include runtime built-in commands - #[serde(skip_serializing_if = "Option::is_none")] - pub include_builtins: Option, - /// Include commands registered by protocol clients, including SDK clients and extensions - #[serde(skip_serializing_if = "Option::is_none")] - pub include_client_commands: Option, - /// Include enabled user-invocable skills and commands - #[serde(skip_serializing_if = "Option::is_none")] - pub include_skills: Option, +pub struct CatalogAuthenticationRequiredError { + /// Discriminator: the caller is not authenticated + pub kind: CatalogAuthenticationRequiredErrorKind, + /// Human-readable explanation, safe to surface. Never contains a credential or token, nor a query, URL, handle, or secret. + pub message: String, + /// Why authentication failed. Only an expired credential justifies attempting a silent refresh; an absent or rejected credential requires sign-in. + pub reason: CatalogAuthenticationRequiredReason, } -/// Queued-command request ID and the result indicating whether the host executed it (and whether to stop processing further queued commands). +/// Where and when an MCP server catalog reference was observed. Discovery provenance deliberately carries no content digest because search does not establish the exact validated content a later plan will bind. /// ///
/// @@ -2837,14 +3176,16 @@ pub struct CommandsListRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CommandsRespondToQueuedCommandRequest { - /// Request ID from the `command.queued` event the host is responding to. - pub request_id: RequestId, - /// Result of the queued command execution. - pub result: serde_json::Value, +pub struct CatalogMcpServerCandidateProvenance { + /// Host of the catalog authority that advertised the reference, without path, query, or credentials. Inert untrusted data. + pub authority: String, + /// JSON MCP media type advertised for the referenced card. + pub media_type: McpServerCardMediaType, + /// ISO 8601 timestamp at which the runtime observed the catalog reference. This is not a retrieval or validation timestamp. + pub observed_at: String, } -/// Indicates whether the queued-command response was matched to a pending request. +/// An inert MCP server catalog result. Every free-text field is untrusted external data and must never be treated as an instruction, and the handle is the only way to refer to the candidate in a later operation. /// ///
/// @@ -2852,14 +3193,34 @@ pub struct CommandsRespondToQueuedCommandRequest { /// 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 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. - pub success: bool, +pub struct CatalogMcpServerCandidate { + /// Description taken verbatim from the card. Inert untrusted text. + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + /// Display name taken verbatim from the card. Inert untrusted text. + pub display_name: String, + /// Opaque, runtime-instance scoped, TTL-bound, single-use handle for this candidate. Carries no readable information and is rejected when stale, replayed, or presented to a different runtime instance. Never logged. + pub handle: String, + /// ISO 8601 timestamp after which the handle is stale and will be rejected. + pub handle_expires_at: String, + /// Whether this MCP server can be planned for installation, and if policy prevents it. + pub installability: CatalogMcpServerInstallability, + /// Discriminator: this candidate describes an MCP server + pub kind: CatalogMcpServerCandidateKind, + /// JSON MCP media type of the underlying card. + pub media_type: McpServerCardMediaType, + /// Where the catalog reference was observed, without the card itself or any content digest. + pub provenance: CatalogMcpServerCandidateProvenance, + /// Publisher taken verbatim from the card. Inert untrusted text. + #[serde(skip_serializing_if = "Option::is_none")] + pub publisher: Option, + /// Where the card came from: exactly one of a URL or embedded data, encoded as a tagged union so neither both nor neither can be represented. + pub source: CatalogCandidateSource, } -/// 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`). +/// The protocol version and capability set a caller requires, supplied on every catalog request so negotiation cannot be skipped by omission. /// ///
/// @@ -2869,12 +3230,14 @@ pub struct CommandsRespondToQueuedCommandResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CompletionsGetTriggerCharactersResult { - /// Trigger characters advertised by the host (e.g. `["@", "#"]`). Empty disables host-driven completions for the session. - pub trigger_characters: Vec, +pub struct CatalogClientContract { + /// SDK protocol version the caller was generated against. A caller below the runtime's minimum supported version is refused rather than served a partial result. + pub protocol_version: i64, + /// Wire features the caller requires the runtime to understand. Identifiers are bounded but extensible so a newer caller can negotiate with an older runtime. Requiring an unknown feature yields a typed refusal listing what is understood, never a partial grant. A grant does not promise that a deployment has enabled the operation; typed unavailable results report that separately. + pub required_capabilities: Vec, } -/// Request host-driven completions for the current composer input. +/// An upstream catalog response broke the wire contract. Most importantly, every result must carry exactly one of a URL or embedded data: a result carrying both, or neither, is refused here rather than being guessed at. /// ///
/// @@ -2884,14 +3247,16 @@ pub struct CompletionsGetTriggerCharactersResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CompletionsRequestRequest { - /// Cursor offset within `text`, in UTF-16 code units. - pub offset: i64, - /// The full composed composer input. - pub text: String, +pub struct CatalogContractViolationError { + /// Discriminator: the upstream response broke the contract + pub kind: CatalogContractViolationErrorKind, + /// Human-readable explanation, safe to surface. Never echoes response content, nor a query, URL, handle, or secret. + pub message: String, + /// Which rule the response broke. + pub reason: CatalogContractViolationReason, } -/// 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. +/// A presented handle was not accepted. Handles are runtime-instance scoped, TTL-bound, and single-use, so each way of failing is reported distinctly. /// ///
/// @@ -2901,24 +3266,18 @@ pub struct CompletionsRequestRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionCompletionItem { - /// Text spliced into the composer when the item is accepted. - pub insert_text: String, - /// Render-kind hint for the picker row (e.g. `"document"`, `"directory"`), derived from the host's display kind. - #[serde(skip_serializing_if = "Option::is_none")] - pub kind: Option, - /// Primary display label for the picker row. Falls back to `insertText` when absent. - #[serde(skip_serializing_if = "Option::is_none")] - pub label: Option, - /// End (exclusive) of the replacement range in `text`, in UTF-16 code units. - #[serde(skip_serializing_if = "Option::is_none")] - pub range_end: Option, - /// Start of the replacement range in `text`, in UTF-16 code units. - #[serde(skip_serializing_if = "Option::is_none")] - pub range_start: Option, +pub struct CatalogHandleRejectedError { + /// Which kind of handle was presented. + pub handle_type: CatalogHandleType, + /// Discriminator: a handle was rejected + pub kind: CatalogHandleRejectedErrorKind, + /// Human-readable explanation, safe to surface. Never contains the handle itself, nor a query, URL, or secret. + pub message: String, + /// Why the handle was rejected. + pub reason: CatalogHandleRejectionReason, } -/// Host-driven completion items for the current composer input. Empty when the host returns no items or does not support completions. +/// The request was rejected before any work was done, because a bounded field fell outside its permitted range or a required field was unusable. /// ///
/// @@ -2928,12 +3287,16 @@ pub struct SessionCompletionItem { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CompletionsRequestResult { - /// Completion items in host-ranked order. - pub items: Vec, +pub struct CatalogInvalidRequestError { + /// Which request field was rejected. + pub field: CatalogInvalidRequestField, + /// Discriminator: the request itself was invalid + pub kind: CatalogInvalidRequestErrorKind, + /// Human-readable explanation, safe to surface. Never echoes the offending value, nor a query, URL, handle, or secret. + pub message: String, } -/// Params to attach or detach an in-process ExtensionController delegate. +/// A card could not be parsed or did not satisfy its declared media type's schema. /// ///
/// @@ -2943,16 +3306,19 @@ pub struct CompletionsRequestResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub(crate) struct 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. - #[doc(hidden)] +pub struct CatalogMalformedCardError { + /// Discriminator: the card was malformed + pub kind: CatalogMalformedCardErrorKind, + /// Media type the card was interpreted as, when it declared one this runtime recognises. #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) controller: Option, - /// Session to attach the extension controller delegate to. - pub session_id: SessionId, + pub media_type: Option, + /// Human-readable explanation, safe to surface. Never echoes card content, nor a query, URL, handle, or secret. + pub message: String, + /// How the card failed validation. + pub reason: CatalogMalformedCardReason, } -/// Repository associated with the connected remote session. +/// The protocol version and capability set the runtime actually honoured for a successful catalog operation. /// ///
/// @@ -2962,16 +3328,14 @@ pub(crate) struct ConfigureSessionExtensionsParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ConnectedRemoteSessionMetadataRepository { - /// Branch associated with the remote session. - pub branch: String, - /// Repository name. - pub name: String, - /// Repository owner or organization login. - pub owner: String, +pub struct CatalogNegotiatedContract { + /// Wire features the runtime understood for this operation. Always a superset of the caller's required features, because any shortfall is a refusal instead. Operation availability remains a separate typed result. + pub granted_capabilities: Vec, + /// Protocol version of the runtime that served the request. + pub runtime_protocol_version: i64, } -/// Metadata for a connected remote session. +/// The caller's protocol version or required capabilities cannot be honoured. Returned instead of a partial or ambiguous success. /// ///
/// @@ -2981,38 +3345,24 @@ pub struct ConnectedRemoteSessionMetadataRepository { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ConnectedRemoteSessionMetadata { - /// Neutral SDK discriminator for the connected remote session kind. - pub kind: ConnectedRemoteSessionMetadataKind, - /// Last session update time as an ISO 8601 string. - pub modified_time: String, - /// Optional friendly session name. - #[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, - /// Repository associated with the connected remote session. - pub repository: ConnectedRemoteSessionMetadataRepository, - /// Original remote resource identifier. - #[serde(skip_serializing_if = "Option::is_none")] - pub resource_id: Option, - /// SDK session ID for the connected remote session. - pub session_id: SessionId, - /// Remote session staleness deadline as an ISO 8601 string. - #[serde(skip_serializing_if = "Option::is_none")] - pub stale_at: Option, - /// Session start time as an ISO 8601 string. - pub start_time: String, - /// Remote session state returned by the backing service. - #[serde(skip_serializing_if = "Option::is_none")] - pub state: Option, - /// Optional session summary. - #[serde(skip_serializing_if = "Option::is_none")] - pub summary: Option, +pub struct CatalogNegotiationRefusedError { + /// Discriminator: capability or protocol-version negotiation failed + pub kind: CatalogNegotiationRefusedErrorKind, + /// Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. + pub message: String, + /// Lowest caller protocol version this runtime will serve. + pub minimum_supported_protocol_version: i64, + /// Whether the version or the capability set was the problem. + pub reason: CatalogNegotiationRefusedReason, + /// Protocol version of the runtime that refused the request. + pub runtime_protocol_version: i64, + /// Every wire feature this runtime understands, so the caller can retry within that contract. This list does not imply that every deployment has enabled every operation. + pub supported_capabilities: Vec, + /// The subset of the caller's bounded extensible capability identifiers this runtime cannot honour. + pub unsupported_capabilities: Vec, } -/// Remote session connection parameters. +/// The runtime could not reach the catalog authority or retrieve a card. Covers being offline as well as transport-level failure. /// ///
/// @@ -3022,12 +3372,22 @@ pub struct ConnectedRemoteSessionMetadata { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ConnectRemoteSessionParams { - /// Session ID to connect to. - pub session_id: SessionId, +pub struct CatalogNetworkFailureError { + /// Discriminator: the network operation failed + pub kind: CatalogNetworkFailureErrorKind, + /// Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. + pub message: String, + /// Categorised failure, low cardinality so it can be aggregated without carrying a URL. + pub reason: CatalogNetworkFailureReason, + /// Bounded cooldown in seconds before another catalog request should be attempted, when the authority supplied a numeric Retry-After value or the runtime applied its documented fallback. + #[serde(skip_serializing_if = "Option::is_none")] + pub retry_after_seconds: Option, + /// HTTP status code, when the failure was a rejected response. + #[serde(skip_serializing_if = "Option::is_none")] + pub status_code: Option, } -/// Parameters for the `server.connect` handshake: an optional connection token and optional connection-level opt-ins (e.g. GitHub telemetry forwarding). +/// The candidate is discoverable but cannot be installed. `application/ai-skill` resolves here, because it stays searchable while remaining typed non-installable. /// ///
/// @@ -3037,16 +3397,16 @@ 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. 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 - #[serde(skip_serializing_if = "Option::is_none")] - pub token: Option, +pub struct CatalogNotInstallableError { + /// Discriminator: the candidate cannot be installed + pub kind: CatalogNotInstallableErrorKind, + /// Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. + pub message: String, + /// Why the candidate cannot be installed. + pub reason: CatalogNotInstallableReason, } -/// Handshake result reporting the server's protocol version and package version on success. +/// Registry or enterprise policy refused the operation. /// ///
/// @@ -3056,16 +3416,16 @@ pub(crate) struct ConnectRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub(crate) struct ConnectResult { - /// Always true on success - pub ok: bool, - /// Server protocol version number - pub protocol_version: i64, - /// Server package version - pub version: String, +pub struct CatalogPolicyRejectedError { + /// Discriminator: policy refused the operation + pub kind: CatalogPolicyRejectedErrorKind, + /// Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. + pub message: String, + /// Which authority produced the decision. + pub source: McpPlanPolicySource, } -/// Local file system absolute paths within the session working directory to check against its content-exclusion policy. +/// A bounded catalog search. Both the query length and the result count are capped by the schema so a caller cannot request an unbounded scan. /// ///
/// @@ -3075,12 +3435,20 @@ pub(crate) struct ConnectResult { ///
#[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, +pub struct CatalogSearchRequest { + /// Protocol version and capabilities the caller requires. + pub contract: CatalogClientContract, + /// Restrict results to these candidate kinds. When omitted, every kind the runtime supports is searched. + #[serde(skip_serializing_if = "Option::is_none")] + pub kinds: Option>, + /// Maximum number of candidates to return. Defaults to 10 when omitted. + #[serde(skip_serializing_if = "Option::is_none")] + pub limit: Option, + /// Free-text search query. Persisted as tool input for session continuity, but omitted from telemetry. + pub query: String, } -/// Content-exclusion decision for one requested path. +/// A completed catalog search: inert candidate summaries, each carrying a single-use handle. /// ///
/// @@ -3090,14 +3458,20 @@ pub struct ContentExclusionCheckPathsRequest { ///
#[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, +pub struct CatalogSearchSucceeded { + /// Matching candidates, never more than the requested limit. All text is inert untrusted data. + pub candidates: Vec, + /// Discriminator: the search completed + pub kind: CatalogSearchSucceededKind, + /// Protocol version and capabilities the runtime honoured. + pub negotiated: CatalogNegotiatedContract, + /// Pseudonymous identifier for this search, issued by the runtime or by the catalog authority it queried and never by the caller, so it cannot be forged or replayed to attribute an install to a search that never happened. Always present on a success, so a result set can be tied to the installs it leads to. It identifies a search rather than a person: it is derived from no user, account, device, or query data, and must never be joined with user identity to re-identify anyone. + pub search_id: String, + /// Whether further matches existed beyond the requested limit. + pub truncated: bool, } -/// Batch content-exclusion result. Callers must fail closed when policy evaluation is unavailable. +/// The request asked for a candidate kind this runtime does not serve. /// ///
/// @@ -3107,14 +3481,18 @@ pub struct ContentExclusionPathCheck { ///
#[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, +pub struct CatalogUnsupportedKindError { + /// Discriminator: an unsupported candidate kind was requested + pub kind: CatalogUnsupportedKindErrorKind, + /// Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. + pub message: String, + /// The kinds from the request that are not supported. + pub requested_kinds: Vec, + /// Every candidate kind this runtime can serve. + pub supported_kinds: Vec, } -/// A single large message currently in context. +/// Retrieval was refused by the runtime's hardened fetch boundary before any request left the process, or before a redirect was followed. /// ///
/// @@ -3124,18 +3502,16 @@ pub struct ContentExclusionCheckPathsResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ContextHeaviestMessage { - /// Stable identifier for this message within the snapshot. - pub id: String, - /// Human-readable source label, e.g. `tool: bash` or `skill: tmux`. Presentation-only. - pub label: String, - /// Role of the chat message (`user`, `assistant`, or `tool`). - pub role: String, - /// Token count currently in context for this individual message. - pub tokens: i64, +pub struct CatalogUnsafeRetrievalError { + /// Discriminator: retrieval was refused as unsafe + pub kind: CatalogUnsafeRetrievalErrorKind, + /// Human-readable explanation, safe to surface. Never contains the refused URL, nor a query, handle, or secret. + pub message: String, + /// Which control refused the retrieval, low cardinality so it can be aggregated without carrying a URL. + pub reason: CatalogUnsafeRetrievalReason, } -/// Authentication-info variant for direct Copilot API token auth sourced from environment variables, with public GitHub host. +/// The operation is not available on this runtime. Distinct from a network failure: nothing was attempted. /// ///
/// @@ -3145,17 +3521,16 @@ pub struct ContextHeaviestMessage { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CopilotApiTokenAuthInfo { - /// 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 (always the public GitHub host). - pub host: CopilotApiTokenAuthInfoHost, - /// Direct Copilot API authentication via the `GITHUB_COPILOT_API_TOKEN` + `COPILOT_API_URL` environment-variable pair. The token itself is read from the environment by the runtime, not carried in this struct. - pub r#type: CopilotApiTokenAuthInfoType, +pub struct CatalogUnavailableError { + /// Discriminator: the operation is not available + pub kind: CatalogUnavailableErrorKind, + /// Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. + pub message: String, + /// Why the operation is unavailable. + pub reason: CatalogUnavailableReason, } -/// 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. +/// No transport this runtime can use is available for the requested server. /// ///
/// @@ -3165,19 +3540,16 @@ pub struct CopilotApiTokenAuthInfo { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CurrentModel { - /// 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 CatalogUnavailableTransportError { + /// Discriminator: no usable transport is available + pub kind: CatalogUnavailableTransportErrorKind, + /// Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. + pub message: String, + /// Why no transport could be offered. + pub reason: CatalogUnavailableTransportReason, } -/// Lightweight metadata for a currently initialized session tool +/// A literal choice the command input accepts, with a human-facing description /// ///
/// @@ -3187,29 +3559,14 @@ pub struct CurrentModel { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CurrentToolMetadata { - /// Whether the tool is loaded on demand via tool search - #[serde(skip_serializing_if = "Option::is_none")] - pub defer_loading: Option, - /// Tool description +pub struct SlashCommandInputChoice { + /// Human-readable description shown alongside the choice pub description: String, - /// JSON Schema for tool input - #[serde(rename = "input_schema", skip_serializing_if = "Option::is_none")] - pub input_schema: Option>, - /// MCP server name for MCP-backed tools - #[serde(skip_serializing_if = "Option::is_none")] - pub mcp_server_name: Option, - /// Raw MCP tool name for MCP-backed tools - #[serde(skip_serializing_if = "Option::is_none")] - pub mcp_tool_name: Option, - /// Model-facing tool name + /// The literal choice value (e.g. 'on', 'off', 'show') pub name: String, - /// Optional MCP/config namespaced tool name - #[serde(skip_serializing_if = "Option::is_none")] - pub namespaced_name: Option, } -/// A file included in the redacted debug bundle. +/// Optional unstructured input hint /// ///
/// @@ -3219,35 +3576,24 @@ pub struct CurrentToolMetadata { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct DebugCollectLogsCollectedEntry { - /// Relative path of the file in the staged bundle/archive. - pub bundle_path: String, - /// Redacted output size in bytes. - pub size_bytes: i64, - /// Source category for this entry. - pub source: DebugCollectLogsSource, -} - -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct DebugCollectLogsDestinationArchive { - pub kind: DebugCollectLogsDestinationArchiveKind, - /// When true, create the archive atomically without overwriting an existing file by appending ` (N)` before the extension as needed. Defaults to false. +pub struct SlashCommandInput { + /// Optional literal choices the input accepts, each with a human-facing description; clients may render these as selectable options #[serde(skip_serializing_if = "Option::is_none")] - pub no_overwrite: Option, - /// Absolute or server-relative path for the .tgz archive to create. - pub output_path: String, -} - -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct DebugCollectLogsDestinationDirectory { - pub kind: DebugCollectLogsDestinationDirectoryKind, - /// Directory where redacted files should be staged. The directory is created if needed. - pub output_directory: String, + pub choices: Option>, + /// Optional completion hint for the input (e.g. 'directory' for filesystem path completion) + #[serde(skip_serializing_if = "Option::is_none")] + pub completion: Option, + /// Hint to display when command input has not been provided + pub hint: String, + /// When true, clients should pass the full text after the command name as a single argument rather than splitting on whitespace + #[serde(skip_serializing_if = "Option::is_none")] + pub preserve_multiline_input: Option, + /// When true, the command requires non-empty input; clients should render the input hint as required + #[serde(skip_serializing_if = "Option::is_none")] + pub required: Option, } -/// A caller-provided server-local file or directory to include in the debug bundle. +/// Slash-command metadata with name, aliases, description, kind, input hint, execution allowance, and schedulability. /// ///
/// @@ -3257,22 +3603,30 @@ pub struct DebugCollectLogsDestinationDirectory { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct DebugCollectLogsEntry { - /// Relative path to use inside the staged bundle/archive. - pub bundle_path: String, - /// Kind of source path to include. - pub kind: DebugCollectLogsEntryKind, - /// Server-local source path to read. - pub path: String, - /// How text content from this entry should be redacted. Defaults to plain-text. +pub struct SlashCommandInfo { + /// Canonical aliases without leading slashes #[serde(skip_serializing_if = "Option::is_none")] - pub redaction: Option, - /// When true, collection fails if this entry cannot be read. Defaults to false, which records the entry in `skippedEntries`. + pub aliases: Option>, + /// Whether the command may run while an agent turn is active + pub allow_during_agent_execution: bool, + /// Human-readable command description + pub description: String, + /// Whether the command is experimental #[serde(skip_serializing_if = "Option::is_none")] - pub required: Option, + pub experimental: Option, + /// Optional unstructured input hint + #[serde(skip_serializing_if = "Option::is_none")] + pub input: Option, + /// Coarse command category for grouping and behavior: runtime built-in, skill-backed command, or SDK/client-owned command + pub kind: SlashCommandKind, + /// Canonical command name without a leading slash + pub name: String, + /// Whether the command may be the target of `/every` / `/after` schedules. Resolution happens at every tick, so only set this when the command is safe to re-invoke and produces an agent prompt. + #[serde(skip_serializing_if = "Option::is_none")] + pub schedulable: Option, } -/// Built-in session diagnostics to include in the bundle. Omitted fields default to true. +/// Slash commands available in the session, after applying any include/exclude filters. /// ///
/// @@ -3282,31 +3636,12 @@ pub struct DebugCollectLogsEntry { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct DebugCollectLogsInclude { - /// Server-local path to the current process log. When set, it is included as `process.log` and its directory is searched for prior logs from the same session. - #[serde(skip_serializing_if = "Option::is_none")] - pub current_process_log_path: Option, - /// Include the session event log (`events.jsonl`). Defaults to true. - #[serde(skip_serializing_if = "Option::is_none")] - pub events: Option, - /// Server-local path to the session's events.jsonl file. Internal callers normally omit this and let the runtime derive it from the session. - #[serde(skip_serializing_if = "Option::is_none")] - pub events_path: Option, - /// Maximum number of previous process logs to include. Defaults to 5. - #[serde(skip_serializing_if = "Option::is_none")] - pub previous_process_log_limit: Option, - /// Server-local process log directory to search when `currentProcessLogPath` is unavailable, useful for collecting logs for inactive sessions. - #[serde(skip_serializing_if = "Option::is_none")] - pub process_log_directory: Option, - /// Include process logs for the session. Defaults to true. - #[serde(skip_serializing_if = "Option::is_none")] - pub process_logs: Option, - /// Include interactive shell logs written under the session's `shell-logs` directory. Defaults to true. - #[serde(skip_serializing_if = "Option::is_none")] - pub shell_logs: Option, +pub struct CommandList { + /// Commands available in this session + pub commands: Vec, } -/// Options for collecting a redacted session debug bundle. +/// The pending slash-command invocation effect to finalize, plus whether the host applied or cancelled it. /// ///
/// @@ -3314,20 +3649,16 @@ pub struct DebugCollectLogsInclude { /// 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 DebugCollectLogsRequest { - /// Caller-provided server-local files or directories to include in addition to the runtime's built-in session diagnostics. This lets host applications add their own diagnostics without changing the API shape. - #[serde(skip_serializing_if = "Option::is_none")] - pub additional_entries: Option>, - /// Where the redacted bundle should be written. Use `archive` to produce a .tgz, or `directory` to stage redacted files for caller-managed upload/post-processing. - pub destination: DebugCollectLogsDestination, - /// Which built-in session diagnostics to include. Omitted fields default to true. - #[serde(skip_serializing_if = "Option::is_none")] - pub include: Option, +pub struct CommandsFinalizeInvocationEffectRequest { + /// The slash-command result object that produced the pending effect, echoed back unchanged. + pub effect: serde_json::Value, + /// Whether the host applied or cancelled the pending invocation effect. + pub outcome: CommandsInvocationEffectOutcome, } -/// An optional debug bundle entry that could not be included. +/// Whether finalizing the invocation effect succeeded, and the failure reason when it did not. /// ///
/// @@ -3337,17 +3668,15 @@ pub struct DebugCollectLogsRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct DebugCollectLogsSkippedEntry { - /// Relative path requested for this bundle entry. - pub bundle_path: String, - /// Server-local source path that could not be read. +pub struct CommandsFinalizeInvocationEffectResult { + /// Failure reason when the invocation effect could not be finalized. #[serde(skip_serializing_if = "Option::is_none")] - pub path: Option, - /// Reason the entry was skipped. - pub reason: String, + pub error: Option, + /// Whether the pending invocation effect was finalized successfully. + pub success: bool, } -/// Result of collecting a redacted debug bundle. +/// Pending command request ID and an optional error if the client handler failed. /// ///
/// @@ -3357,19 +3686,15 @@ pub struct DebugCollectLogsSkippedEntry { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct DebugCollectLogsResult { - /// 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 CommandsHandlePendingCommandRequest { + /// Error message if the command handler failed #[serde(skip_serializing_if = "Option::is_none")] - pub skipped_entries: Option>, + pub error: Option, + /// Request ID from the command invocation event + pub request_id: RequestId, } -/// Installed plugin that contributes a discovered extension. +/// Indicates whether the pending client-handled command was completed successfully. /// ///
/// @@ -3379,12 +3704,12 @@ pub struct DebugCollectLogsResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct DiscoveredExtensionPlugin { - /// Installed plugin name - pub name: String, +pub struct CommandsHandlePendingCommandResult { + /// Whether the command was handled successfully + pub success: bool, } -/// Discovered extension metadata and persistent enablement state. +/// Slash command name and optional raw input string to invoke. /// ///
/// @@ -3394,23 +3719,18 @@ pub struct DiscoveredExtensionPlugin { ///
#[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 struct CommandsInvokeRequest { + /// Raw input after the command name + #[serde(skip_serializing_if = "Option::is_none")] + pub input: Option, + /// Command name. Leading slashes are stripped and the name is matched case-insensitively. 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 + /// Optional client surface that initiated the invocation #[serde(skip_serializing_if = "Option::is_none")] - pub plugin: Option, - /// Discovery source - pub source: DiscoveredExtensionSource, + pub origin: Option, } -/// Extensions discovered from persisted Copilot home state and their effective loading mode. Launch-scoped additional plugins are not included. +/// Optional filters controlling which command sources to include in the listing. /// ///
/// @@ -3420,14 +3740,19 @@ pub struct DiscoveredExtension { ///
#[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, +pub struct CommandsListRequest { + /// Include runtime built-in commands + #[serde(skip_serializing_if = "Option::is_none")] + pub include_builtins: Option, + /// Include commands registered by protocol clients, including SDK clients and extensions + #[serde(skip_serializing_if = "Option::is_none")] + pub include_client_commands: Option, + /// Include enabled user-invocable skills and commands + #[serde(skip_serializing_if = "Option::is_none")] + pub include_skills: Option, } -/// Source-qualified extension identifiers to persistently disable for future sessions. +/// Queued-command request ID and the result indicating whether the host executed it (and whether to stop processing further queued commands). /// ///
/// @@ -3437,12 +3762,14 @@ pub struct DiscoveredExtensions { ///
#[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, +pub struct CommandsRespondToQueuedCommandRequest { + /// Request ID from the `command.queued` event the host is responding to. + pub request_id: RequestId, + /// Result of the queued command execution. + pub result: serde_json::Value, } -/// Source-qualified extension identifiers to persistently enable for future sessions. +/// Indicates whether the queued-command response was matched to a pending request. /// ///
/// @@ -3452,12 +3779,12 @@ pub struct DiscoveredExtensionsDisableRequest { ///
#[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, +pub struct 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. + pub success: bool, } -/// MCP server discovered by `mcp.discover`, with config source, optional plugin source, transport type, and enabled state. +/// 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`). /// ///
/// @@ -3467,25 +3794,12 @@ pub struct DiscoveredExtensionsEnableRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct DiscoveredMcpServer { - /// Whether the server is enabled (not in the disabled list) - pub enabled: bool, - /// Server name (config key) - pub name: String, - /// Configuration source: user, workspace, plugin, or builtin - pub source: McpServerSource, - /// 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, - /// Server transport type: stdio, http, sse (deprecated), or memory - #[serde(skip_serializing_if = "Option::is_none")] - pub r#type: Option, +pub struct CompletionsGetTriggerCharactersResult { + /// Trigger characters advertised by the host (e.g. `["@", "#"]`). Empty disables host-driven completions for the session. + pub trigger_characters: Vec, } -/// Slash-prefixed command string to enqueue for FIFO processing. +/// Request host-driven completions for the current composer input. /// ///
/// @@ -3495,12 +3809,14 @@ pub struct DiscoveredMcpServer { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct 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. - pub command: String, +pub struct CompletionsRequestRequest { + /// Cursor offset within `text`, in UTF-16 code units. + pub offset: i64, + /// The full composed composer input. + pub text: String, } -/// Indicates whether the command was accepted into the local execution queue. +/// 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. /// ///
/// @@ -3510,12 +3826,24 @@ pub struct EnqueueCommandParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct EnqueueCommandResult { - /// 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 SessionCompletionItem { + /// Text spliced into the composer when the item is accepted. + pub insert_text: String, + /// Render-kind hint for the picker row (e.g. `"document"`, `"directory"`), derived from the host's display kind. + #[serde(skip_serializing_if = "Option::is_none")] + pub kind: Option, + /// Primary display label for the picker row. Falls back to `insertText` when absent. + #[serde(skip_serializing_if = "Option::is_none")] + pub label: Option, + /// End (exclusive) of the replacement range in `text`, in UTF-16 code units. + #[serde(skip_serializing_if = "Option::is_none")] + pub range_end: Option, + /// Start of the replacement range in `text`, in UTF-16 code units. + #[serde(skip_serializing_if = "Option::is_none")] + pub range_start: Option, } -/// Authentication-info variant for a token sourced from an environment variable, with host, optional login, token, and env var name. +/// Host-driven completion items for the current composer input. Empty when the host returns no items or does not support completions. /// ///
/// @@ -3525,24 +3853,12 @@ pub struct EnqueueCommandResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct EnvAuthInfo { - /// 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, - /// Name of the environment variable the token was sourced from. - pub env_var: String, - /// Authentication host (e.g. https://github.com or a GHES host). - pub host: String, - /// User login associated with the token. Undefined for server-to-server tokens (those starting with `ghs_`). - #[serde(skip_serializing_if = "Option::is_none")] - pub login: Option, - /// The token value itself. Treat as a secret. - pub token: String, - /// Personal access token (PAT) or server-to-server token sourced from an environment variable. - pub r#type: EnvAuthInfoType, +pub struct CompletionsRequestResult { + /// Completion items in host-ranked order. + pub items: Vec, } -/// Cursor, batch size, and optional long-poll/filter parameters for reading session events. +/// Params to attach or detach an in-process ExtensionController delegate. /// ///
/// @@ -3552,34 +3868,16 @@ 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). 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. +pub(crate) struct 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. + #[doc(hidden)] #[serde(skip_serializing_if = "Option::is_none")] - pub wait_ms: Option, + pub(crate) controller: Option, + /// Session to attach the extension controller delegate to. + pub session_id: SessionId, } -/// Indicates whether the operation succeeded. +/// Identity of the integrating host, declared once on the `server.connect` handshake so telemetry from this connection is attributed to a single, consistent surface. All fields are optional; omit them to keep the default attribution. /// ///
/// @@ -3589,12 +3887,22 @@ pub struct EventLogReadRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct EventLogReleaseInterestResult { - /// Whether the operation succeeded - pub success: bool, +pub(crate) struct ConnectClientInfo { + /// Name of the host editor, e.g. `"vscode"`. + #[serde(skip_serializing_if = "Option::is_none")] + pub editor_name: Option, + /// Version of the host editor, e.g. `"1.124.2"`. Ignored unless it looks like a version string. + #[serde(skip_serializing_if = "Option::is_none")] + pub editor_version: Option, + /// Name of the Copilot extension within the host, e.g. `"copilot-chat"`. + #[serde(skip_serializing_if = "Option::is_none")] + pub extension_name: Option, + /// Version of the Copilot extension within the host, e.g. `"0.54.0"`. Ignored unless it looks like a version string. + #[serde(skip_serializing_if = "Option::is_none")] + pub extension_version: Option, } -/// 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). +/// Repository associated with the connected remote session. /// ///
/// @@ -3604,12 +3912,16 @@ pub struct EventLogReleaseInterestResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct 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). - pub cursor: String, +pub struct ConnectedRemoteSessionMetadataRepository { + /// Branch associated with the remote session. + pub branch: String, + /// Repository name. + pub name: String, + /// Repository owner or organization login. + pub owner: String, } -/// Batch of session events returned by a read, with cursor and continuation metadata. +/// Metadata for a connected remote session. /// ///
/// @@ -3619,18 +3931,38 @@ 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. 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 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, - /// 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 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, +pub struct ConnectedRemoteSessionMetadata { + /// Neutral SDK discriminator for the connected remote session kind. + pub kind: ConnectedRemoteSessionMetadataKind, + /// Last session update time as an ISO 8601 string. + pub modified_time: String, + /// Optional friendly session name. + #[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, + /// Repository associated with the connected remote session. + pub repository: ConnectedRemoteSessionMetadataRepository, + /// Original remote resource identifier. + #[serde(skip_serializing_if = "Option::is_none")] + pub resource_id: Option, + /// SDK session ID for the connected remote session. + pub session_id: SessionId, + /// Remote session staleness deadline as an ISO 8601 string. + #[serde(skip_serializing_if = "Option::is_none")] + pub stale_at: Option, + /// Session start time as an ISO 8601 string. + pub start_time: String, + /// Remote session state returned by the backing service. + #[serde(skip_serializing_if = "Option::is_none")] + pub state: Option, + /// Optional session summary. + #[serde(skip_serializing_if = "Option::is_none")] + pub summary: Option, } -/// Slash command name and argument string to execute synchronously. +/// Remote session connection parameters. /// ///
/// @@ -3640,14 +3972,12 @@ pub struct EventsReadResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ExecuteCommandParams { - /// Argument string to pass to the command (empty string if none). - pub args: String, - /// Name of the slash command to invoke (without the leading '/'). - pub command_name: String, +pub struct ConnectRemoteSessionParams { + /// Session ID to connect to. + pub session_id: SessionId, } -/// Error message produced while executing the command, if any. +/// Connection-level opt-ins for the `server.connect` handshake. Transport authentication is consumed by the native protocol boundary before dispatch. /// ///
/// @@ -3657,14 +3987,21 @@ pub struct ExecuteCommandParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ExecuteCommandResult { - /// Error message produced while executing the command, if any. Omitted when the handler succeeded. +pub(crate) struct ConnectRequest { + /// Identity of the integrating host. Optional; omit it to keep the default attribution. + #[doc(hidden)] #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, -} - -/// Discovered extension metadata, including source-qualified ID, name, discovery source, status, and optional process ID. -/// + pub(crate) client_info: Option, + /// 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 + #[serde(skip_serializing_if = "Option::is_none")] + pub token: Option, +} + +/// Handshake result reporting the server's protocol version and package version on success. +/// ///
/// /// **Experimental.** This type is part of an experimental wire-protocol surface @@ -3673,21 +4010,16 @@ pub struct ExecuteCommandResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct Extension { - /// Source-qualified ID (e.g., 'project:my-ext', 'user:auth-helper', 'plugin:my-plugin:my-ext') - pub id: String, - /// Extension name (directory name) - pub name: String, - /// Process ID if the extension is running - #[serde(skip_serializing_if = "Option::is_none")] - pub pid: Option, - /// Discovery source: project (.github/extensions/), user (~/.copilot/extensions/), plugin (installed plugin), or session (session-state//extensions/) - pub source: ExtensionSource, - /// Current status: running, disabled, failed, or starting - pub status: ExtensionStatus, +pub(crate) struct ConnectResult { + /// Always true on success + pub ok: bool, + /// Server protocol version number + pub protocol_version: i64, + /// Server package version + pub version: String, } -/// Slim input shape for extension_context attachments; identity fields are runtime-derived. +/// Local file system absolute paths within the session working directory to check against its content-exclusion policy. /// ///
/// @@ -3697,16 +4029,12 @@ pub struct Extension { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ExtensionContextPushInput { - /// Caller-supplied JSON payload (required, may be null but not undefined) - pub payload: serde_json::Value, - /// Human-readable composer pill label - pub title: String, - /// Attachment type discriminator - pub r#type: ExtensionContextPushInputType, +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, } -/// Extensions discovered for the session, with their current status. +/// Content-exclusion decision for one requested path. /// ///
/// @@ -3716,12 +4044,14 @@ pub struct ExtensionContextPushInput { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ExtensionList { - /// Discovered extensions and their current status - pub extensions: Vec, +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, } -/// Source-qualified extension identifier to disable for the session. +/// Batch content-exclusion result. Callers must fail closed when policy evaluation is unavailable. /// ///
/// @@ -3731,12 +4061,14 @@ pub struct ExtensionList { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ExtensionsDisableRequest { - /// Source-qualified extension ID to disable - pub id: String, +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, } -/// Source-qualified extension identifier to enable for the session. +/// A single large message currently in context. /// ///
/// @@ -3746,12 +4078,18 @@ pub struct ExtensionsDisableRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ExtensionsEnableRequest { - /// Source-qualified extension ID to enable +pub struct ContextHeaviestMessage { + /// Stable identifier for this message within the snapshot. pub id: String, + /// Human-readable source label, e.g. `tool: bash` or `skill: tmux`. Presentation-only. + pub label: String, + /// Role of the chat message (`user`, `assistant`, or `tool`). + pub role: String, + /// Token count currently in context for this individual message. + pub tokens: i64, } -/// Binary result returned by a tool for the model +/// 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. /// ///
/// @@ -3761,22 +4099,19 @@ pub struct ExtensionsEnableRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ExternalToolTextResultForLlmBinaryResultsForLlm { - /// Base64-encoded binary data - pub data: String, - /// Human-readable description of the binary data +pub struct CurrentModel { + /// Context tier for models that support multiple context-window sizes. #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, - /// Optional metadata from the producing tool. + pub context_tier: Option, + /// Currently active model identifier #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option>, - /// MIME type of the binary data - pub mime_type: String, - /// Binary result type discriminator. Use "image" for images and "resource" for other binary data. - pub r#type: ExternalToolTextResultForLlmBinaryResultsForLlmType, + 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, } -/// Expanded external tool result payload +/// Lightweight metadata for a currently initialized session tool /// ///
/// @@ -3786,33 +4121,29 @@ pub struct ExternalToolTextResultForLlmBinaryResultsForLlm { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ExternalToolTextResultForLlm { - /// Base64-encoded binary results returned to the model - #[serde(skip_serializing_if = "Option::is_none")] - pub binary_results_for_llm: Option>, - /// Structured content blocks from the tool - #[serde(skip_serializing_if = "Option::is_none")] - pub contents: Option>, - /// Optional error message for failed executions - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, - /// Execution outcome classification. Optional for back-compat; normalized to 'success' (or 'failure' when error is present) when missing or unrecognized. +pub struct CurrentToolMetadata { + /// Whether the tool is loaded on demand via tool search #[serde(skip_serializing_if = "Option::is_none")] - pub result_type: Option, - /// Detailed log content for timeline display + pub defer_loading: Option, + /// Tool description + pub description: String, + /// JSON Schema for tool input + #[serde(rename = "input_schema", skip_serializing_if = "Option::is_none")] + pub input_schema: Option>, + /// MCP server name for MCP-backed tools #[serde(skip_serializing_if = "Option::is_none")] - pub session_log: Option, - /// Text result returned to the model - pub text_result_for_llm: String, - /// Tool references returned by a tool-search override: names of deferred tools to surface to the model. When set, the tool result is materialized as `tool_reference` content blocks (rather than plain text) so the model knows which deferred tools are now available. + pub mcp_server_name: Option, + /// Raw MCP tool name for MCP-backed tools #[serde(skip_serializing_if = "Option::is_none")] - pub tool_references: Option>, - /// Optional tool-specific telemetry + pub mcp_tool_name: Option, + /// Model-facing tool name + pub name: String, + /// Optional MCP/config namespaced tool name #[serde(skip_serializing_if = "Option::is_none")] - pub tool_telemetry: Option>, + pub namespaced_name: Option, } -/// Audio content block with base64-encoded data +/// A file included in the redacted debug bundle. /// ///
/// @@ -3822,52 +4153,37 @@ pub struct ExternalToolTextResultForLlm { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ExternalToolTextResultForLlmContentAudio { - /// Base64-encoded audio data - pub data: String, - /// MIME type of the audio (e.g., audio/wav, audio/mpeg) - pub mime_type: String, - /// Content block type discriminator - pub r#type: ExternalToolTextResultForLlmContentAudioType, +pub struct DebugCollectLogsCollectedEntry { + /// Relative path of the file in the staged bundle/archive. + pub bundle_path: String, + /// Redacted output size in bytes. + pub size_bytes: i64, + /// Source category for this entry. + pub source: DebugCollectLogsSource, } -/// Image content block with base64-encoded data -/// -///
-/// -/// **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 ExternalToolTextResultForLlmContentImage { - /// Base64-encoded image data - pub data: String, - /// MIME type of the image (e.g., image/png, image/jpeg) - pub mime_type: String, - /// Content block type discriminator - pub r#type: ExternalToolTextResultForLlmContentImageType, +pub struct DebugCollectLogsDestinationArchive { + /// Destination variant discriminator. + pub kind: DebugCollectLogsDestinationArchiveKind, + /// When true, create the archive atomically without overwriting an existing file by appending ` (N)` before the extension as needed. Defaults to false. + #[serde(skip_serializing_if = "Option::is_none")] + pub no_overwrite: Option, + /// Absolute or server-relative path for the .tgz archive to create. + pub output_path: String, } -/// Embedded resource content block with inline text or binary data -/// -///
-/// -/// **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 ExternalToolTextResultForLlmContentResource { - /// The embedded resource contents, either text or base64-encoded binary - pub resource: serde_json::Value, - /// Content block type discriminator - pub r#type: ExternalToolTextResultForLlmContentResourceType, +pub struct DebugCollectLogsDestinationDirectory { + /// Destination variant discriminator. + pub kind: DebugCollectLogsDestinationDirectoryKind, + /// Directory where redacted files should be staged. The directory is created if needed. + pub output_directory: String, } -/// Icon image for a resource +/// A caller-provided server-local file or directory to include in the debug bundle. /// ///
/// @@ -3877,21 +4193,22 @@ pub struct ExternalToolTextResultForLlmContentResource { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ExternalToolTextResultForLlmContentResourceLinkIcon { - /// MIME type of the icon image - #[serde(skip_serializing_if = "Option::is_none")] - pub mime_type: Option, - /// Available icon sizes (e.g., ['16x16', '32x32']) +pub struct DebugCollectLogsEntry { + /// Relative path to use inside the staged bundle/archive. + pub bundle_path: String, + /// Kind of source path to include. + pub kind: DebugCollectLogsEntryKind, + /// Server-local source path to read. + pub path: String, + /// How text content from this entry should be redacted. Defaults to plain-text. #[serde(skip_serializing_if = "Option::is_none")] - pub sizes: Option>, - /// URL or path to the icon image - pub src: String, - /// Theme variant this icon is intended for + pub redaction: Option, + /// When true, collection fails if this entry cannot be read. Defaults to false, which records the entry in `skippedEntries`. #[serde(skip_serializing_if = "Option::is_none")] - pub theme: Option, + pub required: Option, } -/// Resource link content block referencing an external resource +/// Built-in session diagnostics to include in the bundle. Omitted fields default to true. /// ///
/// @@ -3901,31 +4218,31 @@ pub struct ExternalToolTextResultForLlmContentResourceLinkIcon { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ExternalToolTextResultForLlmContentResourceLink { - /// Human-readable description of the resource +pub struct DebugCollectLogsInclude { + /// Server-local path to the current process log. When set, it is included as `process.log` and its directory is searched for prior logs from the same session. #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, - /// Icons associated with this resource + pub current_process_log_path: Option, + /// Include the session event log (`events.jsonl`). Defaults to true. #[serde(skip_serializing_if = "Option::is_none")] - pub icons: Option>, - /// MIME type of the resource content + pub events: Option, + /// Server-local path to the session's events.jsonl file. Internal callers normally omit this and let the runtime derive it from the session. #[serde(skip_serializing_if = "Option::is_none")] - pub mime_type: Option, - /// Resource name identifier - pub name: String, - /// Size of the resource in bytes + pub events_path: Option, + /// Maximum number of previous process logs to include. Defaults to 5. #[serde(skip_serializing_if = "Option::is_none")] - pub size: Option, - /// Human-readable display title for the resource + pub previous_process_log_limit: Option, + /// Server-local process log directory to search when `currentProcessLogPath` is unavailable, useful for collecting logs for inactive sessions. #[serde(skip_serializing_if = "Option::is_none")] - pub title: Option, - /// Content block type discriminator - pub r#type: ExternalToolTextResultForLlmContentResourceLinkType, - /// URI identifying the resource - pub uri: String, + pub process_log_directory: Option, + /// Include process logs for the session. Defaults to true. + #[serde(skip_serializing_if = "Option::is_none")] + pub process_logs: Option, + /// Include interactive shell logs written under the session's `shell-logs` directory. Defaults to true. + #[serde(skip_serializing_if = "Option::is_none")] + pub shell_logs: Option, } -/// Shell command exit metadata with optional output preview +/// Options for collecting a redacted session debug bundle. /// ///
/// @@ -3933,27 +4250,20 @@ pub struct ExternalToolTextResultForLlmContentResourceLink { /// 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 ExternalToolTextResultForLlmContentShellExit { - /// Working directory where the shell command was executed +pub struct DebugCollectLogsRequest { + /// Caller-provided server-local files or directories to include in addition to the runtime's built-in session diagnostics. This lets host applications add their own diagnostics without changing the API shape. #[serde(skip_serializing_if = "Option::is_none")] - pub cwd: Option, - /// Exit code from the completed shell command - pub exit_code: i64, - /// Output associated with this shell command, if available. May be partial, truncated, or a preview; not guaranteed to be full output. - #[serde(skip_serializing_if = "Option::is_none")] - pub output_preview: Option, - /// Whether outputPreview is known to be incomplete or truncated + pub additional_entries: Option>, + /// Where the redacted bundle should be written. Use `archive` to produce a .tgz, or `directory` to stage redacted files for caller-managed upload/post-processing. + pub destination: DebugCollectLogsDestination, + /// Which built-in session diagnostics to include. Omitted fields default to true. #[serde(skip_serializing_if = "Option::is_none")] - pub output_truncated: Option, - /// Shell id, as assigned by Copilot runtime - pub shell_id: String, - /// Content block type discriminator - pub r#type: ExternalToolTextResultForLlmContentShellExitType, + pub include: Option, } -/// Terminal/shell output content block with optional exit code and working directory +/// An optional debug bundle entry that could not be included. /// ///
/// @@ -3963,20 +4273,17 @@ pub struct ExternalToolTextResultForLlmContentShellExit { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ExternalToolTextResultForLlmContentTerminal { - /// Working directory where the command was executed - #[serde(skip_serializing_if = "Option::is_none")] - pub cwd: Option, - /// Process exit code, if the command has completed +pub struct DebugCollectLogsSkippedEntry { + /// Relative path requested for this bundle entry. + pub bundle_path: String, + /// Server-local source path that could not be read. #[serde(skip_serializing_if = "Option::is_none")] - pub exit_code: Option, - /// Terminal/shell output text - pub text: String, - /// Content block type discriminator - pub r#type: ExternalToolTextResultForLlmContentTerminalType, + pub path: Option, + /// Reason the entry was skipped. + pub reason: String, } -/// Plain text content block +/// Result of collecting a redacted debug bundle. /// ///
/// @@ -3986,14 +4293,19 @@ pub struct ExternalToolTextResultForLlmContentTerminal { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ExternalToolTextResultForLlmContentText { - /// The text content - pub text: String, - /// Content block type discriminator - pub r#type: ExternalToolTextResultForLlmContentTextType, +pub struct DebugCollectLogsResult { + /// 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>, } -/// Parameters for cooperatively aborting a factory body. +/// Installed plugin that contributes a discovered extension. /// ///
/// @@ -4003,14 +4315,12 @@ pub struct ExternalToolTextResultForLlmContentText { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FactoryAbortRequest { - /// Target session identifier - pub session_id: SessionId, - /// Factory run identifier. - pub run_id: String, +pub struct DiscoveredExtensionPlugin { + /// Installed plugin name + pub name: String, } -/// Acknowledgement that a factory request was accepted. +/// Discovered extension metadata and persistent enablement state. /// ///
/// @@ -4020,9 +4330,23 @@ pub struct FactoryAbortRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FactoryAckResult {} +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, +} -/// Options for one factory-scoped subagent call. +/// Extensions discovered from persisted Copilot home state and their effective loading mode. Launch-scoped additional plugins are not included. /// ///
/// @@ -4032,19 +4356,14 @@ pub struct FactoryAckResult {} ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FactoryAgentOptions { - /// 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 JSON Schema for structured agent output. - #[serde(skip_serializing_if = "Option::is_none")] - pub schema: Option, +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, } -/// Parameters for one factory-scoped subagent call. +/// Source-qualified extension identifiers to persistently disable for future sessions. /// ///
/// @@ -4054,18 +4373,12 @@ 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. - pub opts: FactoryAgentOptions, - /// Prompt to send to the subagent. - pub prompt: String, +pub struct DiscoveredExtensionsDisableRequest { + /// Source-qualified user or plugin extension IDs to disable + pub ids: Vec, } -/// Result of one factory-scoped subagent call. +/// Source-qualified extension identifiers to persistently enable for future sessions. /// ///
/// @@ -4075,13 +4388,12 @@ pub struct FactoryAgentRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FactoryAgentResult { - /// Agent result, omitted when the agent produced no result. - #[serde(skip_serializing_if = "Option::is_none")] - pub result: Option, +pub struct DiscoveredExtensionsEnableRequest { + /// Source-qualified user or plugin extension IDs to enable + pub ids: Vec, } -/// Prompt-safe durable identity and live status for a direct factory agent. +/// One server-discovered hook action from user, repository, plugin, or managed-policy configuration. /// ///
/// @@ -4091,28 +4403,27 @@ pub struct FactoryAgentResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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, +pub struct DiscoveredHook { + /// Durable content hash used by hook enablement. Identical actions may intentionally share this key. Omitted when changing the user's disabled-hooks setting cannot change the action's current server-discovered state, including managed-policy hooks, session-start prompt actions, actions suppressed by disable-all settings, and projectless plugin actions that require project-directory expansion. #[serde(skip_serializing_if = "Option::is_none")] - pub requested_model: Option, + pub disable_key: Option, + /// Whether this action is enabled under the server-side discovery settings. Concrete sessions may differ because they can add session-specific directories, plugins, or trust. False when its disable key is present in the user's disabled-hooks setting or disable-all settings suppress the action. + pub enabled: bool, + /// Hook event that invokes this action. + pub hook_type: HookType, + /// Deterministic identifier for this server-discovered action row. It remains stable while the project, origin, source, event, action content, and duplicate ordinal are unchanged. This is row identity, not the key persisted in disabledHooks. + pub id: String, + /// Configuration tier that contributed this hook action. + pub origin: HookOrigin, + /// Input project path for which this server-side action was resolved. Set on every row returned for project-scoped discovery, including repeated user and policy actions. #[serde(skip_serializing_if = "Option::is_none")] - pub resolved_model: Option, - pub run_id: String, + pub project_path: Option, + /// Human-readable source label, such as a hook file path, settings source, or plugin name. #[serde(skip_serializing_if = "Option::is_none")] - pub started_at: Option, - pub status: String, - pub tool_call_id: String, + pub source: Option, } -/// Parameters for cancelling a factory run. +/// MCP server discovered by `mcp.discover`, with config source, optional plugin source, transport type, and enabled state. /// ///
/// @@ -4122,12 +4433,25 @@ pub struct FactoryAgentSummary { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FactoryCancelRequest { - /// Factory run identifier. - pub run_id: String, +pub struct DiscoveredMcpServer { + /// Whether the server is enabled (not in the disabled list) + pub enabled: bool, + /// Server name (config key) + pub name: String, + /// Configuration source: user, workspace, plugin, or builtin + pub source: McpServerSource, + /// 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, + /// Server transport type: stdio, http, sse (deprecated), or memory + #[serde(skip_serializing_if = "Option::is_none")] + pub r#type: Option, } -/// Current factory phase identity. +/// Slash-prefixed command string to enqueue for FIFO processing. /// ///
/// @@ -4137,12 +4461,15 @@ pub struct FactoryCancelRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FactoryCurrentPhase { - pub id: String, - pub ordinal: Option, +pub struct 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. + pub command: String, + /// Optional user-facing text for the queue row. The command string is shown when omitted. + #[serde(skip_serializing_if = "Option::is_none")] + pub display_text: Option, } -/// Declared or approved factory resource ceilings. +/// Indicates whether the command was accepted into the local execution queue. /// ///
/// @@ -4152,18 +4479,12 @@ pub struct FactoryCurrentPhase { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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, +pub struct EnqueueCommandResult { + /// 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, } -/// Parameters sent to the owning extension to execute a factory closure. +/// Cursor, batch size, and optional long-poll/filter parameters for reading session events. /// ///
/// @@ -4173,20 +4494,34 @@ pub struct FactoryDeclaredLimits { ///
#[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, - /// 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, +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). 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, } -/// Result returned by an extension factory closure. +/// Indicates whether the operation succeeded. /// ///
/// @@ -4196,13 +4531,12 @@ pub struct FactoryExecuteRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FactoryExecuteResult { - /// Factory result value. - #[serde(skip_serializing_if = "Option::is_none")] - pub result: Option, +pub struct EventLogReleaseInterestResult { + /// Whether the operation succeeded + pub success: bool, } -/// Parameters for paging factory progress. +/// 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). /// ///
/// @@ -4212,24 +4546,12 @@ pub struct FactoryExecuteResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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, +pub struct 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). + pub cursor: String, } -/// Parameters for retrieving a factory run. +/// Batch of session events returned by a read, with cursor and continuation metadata. /// ///
/// @@ -4239,12 +4561,18 @@ pub struct FactoryGetRunProgressRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FactoryGetRunRequest { - /// Factory run identifier. - pub run_id: String, +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. 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 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, + /// 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 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, } -/// Parameters for reading a factory journal entry. +/// Slash command name and argument string to execute synchronously. /// ///
/// @@ -4254,16 +4582,14 @@ pub struct FactoryGetRunRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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, +pub struct ExecuteCommandParams { + /// Argument string to pass to the command (empty string if none). + pub args: String, + /// Name of the slash command to invoke (without the leading '/'). + pub command_name: String, } -/// Result of reading a factory journal entry. +/// Error message produced while executing the command, if any. /// ///
/// @@ -4273,15 +4599,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 ExecuteCommandResult { + /// Error message produced while executing the command, if any. Omitted when the handler succeeded. #[serde(skip_serializing_if = "Option::is_none")] - pub result_json: Option, + pub error: Option, } -/// Parameters for storing a factory journal entry. +/// Discovered extension metadata, including source-qualified ID, name, discovery source, status, and optional process ID. /// ///
/// @@ -4291,18 +4615,21 @@ pub struct FactoryJournalGetResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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, +pub struct Extension { + /// Source-qualified ID (e.g., 'project:my-ext', 'user:auth-helper', 'plugin:my-plugin:my-ext') + pub id: String, + /// Extension name (directory name) + pub name: String, + /// Process ID if the extension is running + #[serde(skip_serializing_if = "Option::is_none")] + pub pid: Option, + /// Discovery source: project (.github/extensions/), user (~/.copilot/extensions/), plugin (installed plugin), or session (session-state//extensions/) + pub source: ExtensionSource, + /// Current status: running, disabled, failed, or starting + pub status: ExtensionStatus, } -/// Empty parameters for listing factory runs. +/// Slim input shape for extension_context attachments; identity fields are runtime-derived. /// ///
/// @@ -4312,9 +4639,16 @@ pub struct FactoryJournalPutRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FactoryListRunsRequest {} +pub struct ExtensionContextPushInput { + /// Caller-supplied JSON payload (required, may be null but not undefined) + pub payload: serde_json::Value, + /// Human-readable composer pill label + pub title: String, + /// Attachment type discriminator + pub r#type: ExtensionContextPushInputType, +} -/// Durable factory resource consumption. +/// Opaque integrator-owned process launch profile for one extension entrypoint. /// ///
/// @@ -4324,13 +4658,16 @@ pub struct FactoryListRunsRequest {} ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FactoryRunConsumed { - pub active_ms: i64, - pub nano_aiu: i64, - pub subagents: i64, +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, } -/// Prompt-safe terminal factory outcome. +/// A discovered extension entrypoint that the registered integrator may classify and resolve to an opaque launch profile. /// ///
/// @@ -4340,18 +4677,18 @@ pub struct FactoryRunConsumed { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FactoryRunTerminal { - #[serde(skip_serializing_if = "Option::is_none")] - 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, +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, } -/// Durable factory run summary with read-time live overlays. +/// The launch profile for a supported entrypoint. Omit launch when the provider does not support the entrypoint. /// ///
/// @@ -4361,29 +4698,13 @@ pub struct FactoryRunTerminal { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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, +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, } -/// Factory runs in durable creation order. +/// Extensions discovered for the session, with their current status. /// ///
/// @@ -4393,11 +4714,12 @@ pub struct FactoryRunSummary { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FactoryListRunsResult { - pub runs: Vec, +pub struct ExtensionList { + /// Discovered extensions and their current status + pub extensions: Vec, } -/// One ordered factory progress line. +/// Source-qualified extension identifier to disable for the session. /// ///
/// @@ -4407,16 +4729,12 @@ pub struct FactoryListRunsResult { ///
#[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 ExtensionsDisableRequest { + /// Source-qualified extension ID to disable + pub id: String, } -/// Parameters for recording factory progress. +/// Source-qualified extension identifier to enable for the session. /// ///
/// @@ -4426,16 +4744,12 @@ pub struct FactoryLogLine { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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, +pub struct ExtensionsEnableRequest { + /// Source-qualified extension ID to enable + pub id: String, } -/// Durable lifecycle and timing for one factory phase. +/// Binary result returned by a tool for the model /// ///
/// @@ -4445,26 +4759,22 @@ pub struct FactoryLogRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FactoryPhaseObservation { - pub accumulated_active_ms: i64, - #[serde(skip_serializing_if = "Option::is_none")] - pub completed_at: Option, - pub current_active_ms: i64, +pub struct ExternalToolTextResultForLlmBinaryResultsForLlm { + /// Base64-encoded binary data + pub data: String, + /// Human-readable description of the binary data #[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, + pub description: Option, + /// Optional metadata from the producing tool. #[serde(skip_serializing_if = "Option::is_none")] - pub started_at: Option, - pub status: FactoryPhaseStatus, - pub title: String, - pub total_agent_count: i64, + pub metadata: Option>, + /// MIME type of the binary data + pub mime_type: String, + /// Binary result type discriminator. Use "image" for images and "resource" for other binary data. + pub r#type: ExternalToolTextResultForLlmBinaryResultsForLlmType, } -/// One durable factory progress record. +/// Expanded external tool result payload /// ///
/// @@ -4474,22 +4784,33 @@ pub struct FactoryPhaseObservation { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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, +pub struct ExternalToolTextResultForLlm { + /// Base64-encoded binary results returned to the model + #[serde(skip_serializing_if = "Option::is_none")] + pub binary_results_for_llm: Option>, + /// Structured content blocks from the tool + #[serde(skip_serializing_if = "Option::is_none")] + pub contents: Option>, + /// Optional error message for failed executions + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// Execution outcome classification. Optional for back-compat; normalized to 'success' (or 'failure' when error is present) when missing or unrecognized. + #[serde(skip_serializing_if = "Option::is_none")] + pub result_type: Option, + /// Detailed log content for timeline display + #[serde(skip_serializing_if = "Option::is_none")] + pub session_log: Option, + /// Text result returned to the model + pub text_result_for_llm: String, + /// Tool references returned by a tool-search override: names of deferred tools to surface to the model. When set, the tool result is materialized as `tool_reference` content blocks (rather than plain text) so the model knows which deferred tools are now available. + #[serde(skip_serializing_if = "Option::is_none")] + pub tool_references: Option>, + /// Optional tool-specific telemetry + #[serde(skip_serializing_if = "Option::is_none")] + pub tool_telemetry: Option>, } -/// A bidirectional page of factory progress. +/// Audio content block with base64-encoded data /// ///
/// @@ -4499,17 +4820,16 @@ pub struct FactoryProgressLine { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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, +pub struct ExternalToolTextResultForLlmContentAudio { + /// Base64-encoded audio data + pub data: String, + /// MIME type of the audio (e.g., audio/wav, audio/mpeg) + pub mime_type: String, + /// Content block type discriminator + pub r#type: ExternalToolTextResultForLlmContentAudioType, } -/// Wire-only per-invocation factory resource ceiling overrides. +/// Image content block with base64-encoded data /// ///
/// @@ -4519,22 +4839,16 @@ pub struct FactoryProgressPage { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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 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, +pub struct ExternalToolTextResultForLlmContentImage { + /// Base64-encoded image data + pub data: String, + /// MIME type of the image (e.g., image/png, image/jpeg) + pub mime_type: String, + /// Content block type discriminator + pub r#type: ExternalToolTextResultForLlmContentImageType, } -/// Parameters for resuming a factory run from its persisted identity. +/// Embedded resource content block with inline text or binary data /// ///
/// @@ -4544,15 +4858,14 @@ pub struct FactoryRunLimits { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FactoryResumeRequest { - /// Optional per-invocation resource ceiling overrides. - #[serde(skip_serializing_if = "Option::is_none")] - pub limits: Option, - /// Factory run identifier. - pub run_id: String, +pub struct ExternalToolTextResultForLlmContentResource { + /// The embedded resource contents, either text or base64-encoded binary + pub resource: serde_json::Value, + /// Content block type discriminator + pub r#type: ExternalToolTextResultForLlmContentResourceType, } -/// Complete current or terminal factory run envelope. +/// Icon image for a resource /// ///
/// @@ -4562,29 +4875,21 @@ pub struct FactoryResumeRequest { ///
#[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. +pub struct ExternalToolTextResultForLlmContentResourceLinkIcon { + /// MIME type of the icon image #[serde(skip_serializing_if = "Option::is_none")] - pub reason: Option, - /// Completed factory result. + pub mime_type: Option, + /// Available icon sizes (e.g., ['16x16', '32x32']) #[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 sizes: Option>, + /// URL or path to the icon image + pub src: String, + /// Theme variant this icon is intended for #[serde(skip_serializing_if = "Option::is_none")] - pub snapshot: Option, - /// Current or terminal factory run status. - pub status: FactoryRunStatus, + pub theme: Option, } -/// Resolved persisted factory identity and resumed run envelope. +/// Resource link content block referencing an external resource /// ///
/// @@ -4594,14 +4899,31 @@ pub struct FactoryRunResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FactoryResumeResult { - /// Persisted factory name resolved for the resumed run. - pub factory_name: String, - /// Terminal resumed run envelope. - pub run: FactoryRunResult, +pub struct ExternalToolTextResultForLlmContentResourceLink { + /// Human-readable description of the resource + #[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 content + #[serde(skip_serializing_if = "Option::is_none")] + pub mime_type: Option, + /// Resource name identifier + pub name: String, + /// Size of the resource in bytes + #[serde(skip_serializing_if = "Option::is_none")] + pub size: Option, + /// Human-readable display title for the resource + #[serde(skip_serializing_if = "Option::is_none")] + pub title: Option, + /// Content block type discriminator + pub r#type: ExternalToolTextResultForLlmContentResourceLinkType, + /// URI identifying the resource + pub uri: String, } -/// Full factory run observability detail. +/// Shell command exit metadata with optional output preview /// ///
/// @@ -4611,32 +4933,28 @@ pub struct FactoryResumeResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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, +pub struct ExternalToolTextResultForLlmContentShellExit { + /// Working directory where the shell command was executed + #[serde(skip_serializing_if = "Option::is_none")] + pub cwd: Option, + /// Exit code from the completed shell command + pub exit_code: i64, + /// Path reported in the shell session's filesystem namespace when shell output exceeded the configured large-output threshold. + #[serde(skip_serializing_if = "Option::is_none")] + pub output_file_path: Option, + /// Output associated with this shell command, if available. May be partial, truncated, or a preview; not guaranteed to be full output. + #[serde(skip_serializing_if = "Option::is_none")] + pub output_preview: Option, + /// Whether outputPreview is known to be incomplete or truncated + #[serde(skip_serializing_if = "Option::is_none")] + pub output_truncated: Option, + /// Shell id, as assigned by Copilot runtime + pub shell_id: String, + /// Content block type discriminator + pub r#type: ExternalToolTextResultForLlmContentShellExitType, } -/// Options controlling factory invocation. +/// Terminal/shell output content block with optional exit code and working directory /// ///
/// @@ -4646,16 +4964,20 @@ pub struct FactoryRunDetail { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct RunOptions { - /// Per-invocation resource ceiling overrides. +pub struct ExternalToolTextResultForLlmContentTerminal { + /// Working directory where the command was executed #[serde(skip_serializing_if = "Option::is_none")] - pub limits: Option, - /// Run identifier whose journal and progress should seed this resumed run. + pub cwd: Option, + /// Process exit code, if the command has completed #[serde(skip_serializing_if = "Option::is_none")] - pub resume_from_run_id: Option, + pub exit_code: Option, + /// Terminal/shell output text + pub text: String, + /// Content block type discriminator + pub r#type: ExternalToolTextResultForLlmContentTerminalType, } -/// Parameters for invoking a registered factory. +/// Plain text content block /// ///
/// @@ -4665,17 +4987,14 @@ 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. - #[serde(skip_serializing_if = "Option::is_none")] - pub options: Option, +pub struct ExternalToolTextResultForLlmContentText { + /// The text content + pub text: String, + /// Content block type discriminator + pub r#type: ExternalToolTextResultForLlmContentTextType, } -/// Optional user prompt to combine with the fleet orchestration instructions. +/// Parameters for cooperatively aborting a factory body. /// ///
/// @@ -4685,13 +5004,14 @@ pub struct FactoryRunRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FleetStartRequest { - /// Optional user prompt to combine with fleet instructions - #[serde(skip_serializing_if = "Option::is_none")] - pub prompt: Option, +pub struct FactoryAbortRequest { + /// Target session identifier + pub session_id: SessionId, + /// Factory run identifier. + pub run_id: String, } -/// Indicates whether fleet mode was successfully activated. +/// Acknowledgement that a factory request was accepted. /// ///
/// @@ -4701,12 +5021,9 @@ 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 FactoryAckResult {} -/// Folder path to add to trusted folders. +/// Options for one factory-scoped subagent call. /// ///
/// @@ -4716,12 +5033,28 @@ 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 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, } -/// Folder path to check for trust. +/// Parameters for one factory-scoped subagent call. /// ///
/// @@ -4731,12 +5064,18 @@ 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 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. + pub opts: FactoryAgentOptions, + /// Prompt to send to the subagent. + pub prompt: String, } -/// Folder trust check result. +/// Result of one factory-scoped subagent call. /// ///
/// @@ -4746,12 +5085,13 @@ 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 FactoryAgentResult { + /// Agent result, omitted when the agent produced no result. + #[serde(skip_serializing_if = "Option::is_none")] + pub result: Option, } -/// Authentication-info variant for GitHub CLI credentials, carrying host, login, and the `gh auth token` value. +/// Prompt-safe durable identity and live status for a direct factory agent. /// ///
/// @@ -4761,21 +5101,44 @@ 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 FactoryAgentSummary { + /// Accumulated active agent time in milliseconds. + pub active_ms: i64, + /// Prompt-safe live activity text. #[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 activity: Option, + /// Stable direct-agent identifier. + pub agent_id: String, + /// Registered agent type. + pub agent_type: String, + /// Epoch milliseconds when the agent completed. + #[serde(skip_serializing_if = "Option::is_none")] + pub completed_at: Option, + /// Friendly, non-unique name intended for display + #[serde(skip_serializing_if = "Option::is_none")] + pub display_name: Option, + /// Friendly, non-unique name intended for display + pub label: String, + /// Phase identifier active when the agent was launched, or null. + pub phase_id: Option, + /// Model requested when the agent was launched. + #[serde(skip_serializing_if = "Option::is_none")] + pub requested_model: Option, + /// Concrete model resolved for the agent. + #[serde(skip_serializing_if = "Option::is_none")] + pub resolved_model: Option, + /// Owning factory run identifier. + pub run_id: String, + /// Epoch milliseconds when the agent started. + #[serde(skip_serializing_if = "Option::is_none")] + pub started_at: Option, + /// Current durable or live agent status. + pub status: String, + /// Tool-call identifier that launched the agent. + pub tool_call_id: String, } -/// Client environment metadata describing the process that produced a telemetry event. +/// Parameters for cancelling a factory run. /// ///
/// @@ -4785,40 +5148,12 @@ 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 FactoryCancelRequest { + /// Factory run identifier. + pub run_id: 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. +/// Current factory phase identity. /// ///
/// @@ -4828,43 +5163,39 @@ pub struct GitHubTelemetryClientInfo { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct GitHubTelemetryEvent { - /// Client environment metadata. +pub struct FactoryCurrentPhase { + /// Current phase identifier. + pub id: String, + /// Zero-based declared phase ordinal, or null for an undeclared phase. + pub ordinal: Option, +} + +/// Declared or approved factory resource ceilings. +/// +///
+/// +/// **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 FactoryDeclaredLimits { + /// Maximum AI credits consumed by subagents and descendants. #[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. + pub max_ai_credits: Option, + /// Maximum concurrently active subagents. #[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 max_concurrent_subagents: Option, + /// Maximum total subagents spawned by the run. + #[serde(skip_serializing_if = "Option::is_none")] + pub max_total_subagents: Option, + /// Maximum accumulated active execution time in seconds. + #[serde(skip_serializing_if = "Option::is_none")] + pub timeout_seconds: Option, } -/// 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. +/// Parameters sent to the owning extension to execute a factory closure. /// ///
/// @@ -4874,17 +5205,20 @@ 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. - #[serde(skip_serializing_if = "Option::is_none")] - pub session_id: Option, +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, } -/// Pending external tool call request ID, with the tool result or an error describing why it failed. +/// Result returned by an extension factory closure. /// ///
/// @@ -4894,18 +5228,13 @@ 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 FactoryExecuteResult { + /// Factory result value. #[serde(skip_serializing_if = "Option::is_none")] pub result: Option, } -/// Indicates whether the external tool call result was handled successfully. +/// Parameters for paging factory progress. /// ///
/// @@ -4915,12 +5244,24 @@ 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 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, } -/// Indicates whether an in-progress manual compaction was aborted. +/// Parameters for retrieving a factory run. /// ///
/// @@ -4930,12 +5271,12 @@ 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 FactoryGetRunRequest { + /// Factory run identifier. + pub run_id: String, } -/// Indicates whether an in-progress background compaction was cancelled. +/// Parameters for reading a factory journal entry. /// ///
/// @@ -4945,12 +5286,16 @@ 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 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, } -/// Parameters for clearing the conversation and seeding the window that replaces it. +/// Result of reading a factory journal entry. /// ///
/// @@ -4960,12 +5305,15 @@ pub struct HistoryCancelBackgroundCompactionResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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, +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 result_json: Option, } -/// What a successful clear removed. A clear that could not be applied rejects instead of reporting a count. +/// Parameters for storing a factory journal entry. /// ///
/// @@ -4975,12 +5323,18 @@ pub struct HistoryClearContextRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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, +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, } -/// Post-compaction context window usage breakdown +/// Parameters for paging factory runs. /// ///
/// @@ -4990,25 +5344,19 @@ pub struct HistoryClearContextResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct HistoryCompactContextWindow { - /// Token count from non-system messages (user, assistant, tool) +pub struct FactoryListRunsRequest { + /// Exclusive forward cursor. #[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 after_seq: Option, + /// Exclusive backward cursor. #[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 before_seq: Option, + /// Maximum terminal runs to return. Defaults to 200 and is capped at 500. #[serde(skip_serializing_if = "Option::is_none")] - pub tool_definitions_tokens: Option, + pub limit: Option, } -/// Optional compaction parameters. +/// Durable factory resource consumption. /// ///
/// @@ -5018,19 +5366,16 @@ 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 - #[serde(skip_serializing_if = "Option::is_none")] - 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 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 trigger: Option, +pub struct FactoryRunConsumed { + /// Accumulated active execution time in milliseconds. + pub active_ms: i64, + /// AI usage consumed by the run in nano-AIU. + pub nano_aiu: i64, + /// Total subagents spawned by the run. + pub subagents: i64, } -/// Compaction outcome with the number of tokens and messages removed, summary text, and the resulting context window breakdown. +/// Prompt-safe terminal factory outcome. /// ///
/// @@ -5040,22 +5385,22 @@ pub struct HistoryCompactRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct HistoryCompactResult { - /// Post-compaction context window usage breakdown +pub struct FactoryRunTerminal { + /// Human-readable terminal error. #[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 error: Option, + /// Machine-readable terminal failure. #[serde(skip_serializing_if = "Option::is_none")] - pub summary_content: Option, - /// Number of tokens freed by compaction - pub tokens_removed: i64, + pub failure: Option, + /// Human-readable terminal reason. + #[serde(skip_serializing_if = "Option::is_none")] + pub reason: Option, + /// Prompt-safe preview of the completed result. + #[serde(skip_serializing_if = "Option::is_none")] + pub result_preview: Option, } -/// A root user turn that the session can rewind to. +/// Durable factory run summary with read-time live overlays. /// ///
/// @@ -5065,28 +5410,48 @@ pub struct HistoryCompactResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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, +pub struct FactoryRunSummary { + /// Epoch milliseconds when the current active segment started, or null while inactive. + pub active_segment_started_at: Option, + /// Approved effective resource ceilings, or null until approved. + pub approved: Option, + /// Epoch milliseconds when the run completed, or null while nonterminal. + pub completed_at: Option, + /// Durable resource consumption. + pub consumed: FactoryRunConsumed, + /// Epoch milliseconds when the run was created. + pub created_at: i64, + /// Current phase identity, or null before any phase is entered. + pub current_phase: Option, + /// Resource ceilings declared by the factory. + pub declared_limits: FactoryDeclaredLimits, + /// Number of phases declared by the factory. + pub declared_phase_count: i64, + /// Human-readable factory description. + pub description: String, + /// Registered factory name. + pub factory_name: String, + /// Number of direct factory agents currently live. + pub live_agent_count: i64, + /// Epoch milliseconds when this live-overlay snapshot was observed. + pub observed_at: i64, + /// Monotonic durable run revision. + pub revision: i64, + /// Factory run identifier. + pub run_id: String, + /// Epoch milliseconds when execution first started, or null before start. + pub started_at: Option, + /// Current factory run status. + pub status: FactoryRunStatus, + /// Terminal run outcome, or null while nonterminal. + pub terminal: Option, + /// Total direct factory agents spawned across all attempts. + pub total_spawned_agent_count: i64, + /// Epoch milliseconds when the durable run was last updated. + pub updated_at: i64, } -/// Rewind points and file-change-tracking availability for the session. +/// A page of factory runs in durable creation order. /// ///
/// @@ -5096,17 +5461,24 @@ pub struct HistoryRewindPoint { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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`. +pub struct FactoryListRunsResult { + /// Whether terminal runs newer than this page exist. #[serde(skip_serializing_if = "Option::is_none")] - pub unavailable_reason: Option, + 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, + /// Factory run summaries in durable creation order. + pub runs: Vec, } -/// Event boundary to preview for conversation-and-files rewind. +/// One ordered factory progress line. /// ///
/// @@ -5116,12 +5488,16 @@ pub struct HistoryListRewindPointsResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct HistoryPreviewRewindRequest { - /// ID of the user.message event that begins the discarded suffix. - pub event_id: 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, } -/// A file that a conversation-and-files rewind would restore. +/// Parameters for recording factory progress. /// ///
/// @@ -5131,18 +5507,16 @@ pub struct HistoryPreviewRewindRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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, +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, } -/// Files and aggregate changes for a prospective rewind. +/// Durable lifecycle and timing for one factory phase. /// ///
/// @@ -5152,19 +5526,39 @@ pub struct HistoryRewindFilePreview { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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. +pub struct FactoryPhaseObservation { + /// Completed active time accumulated by this phase in milliseconds. + pub accumulated_active_ms: i64, + /// Epoch milliseconds when this phase completed; for a skipped phase, the synthetic skip timestamp (equal to `startedAt`). #[serde(skip_serializing_if = "Option::is_none")] - pub reason: Option, + pub completed_at: Option, + /// Current live active time for this phase in milliseconds. + pub current_active_ms: i64, + /// Optional human-readable phase detail. + #[serde(skip_serializing_if = "Option::is_none")] + pub detail: Option, + /// Number of times execution entered this phase. + pub entry_count: i64, + /// Phase identifier. + pub id: String, + /// Most recent run attempt that entered this phase, or `0` if the phase has never been entered. + pub last_entered_run_attempt: i64, + /// Direct agents in this phase that are currently live. + pub live_agent_count: i64, + /// Zero-based declared phase ordinal, or null for an undeclared phase. + pub ordinal: Option, + /// Epoch milliseconds when this phase first started; for a skipped phase, the synthetic skip timestamp (equal to `completedAt`). + #[serde(skip_serializing_if = "Option::is_none")] + pub started_at: Option, + /// Derived lifecycle state of the phase. + pub status: FactoryPhaseStatus, + /// Human-readable phase title. + pub title: String, + /// Total direct agents associated with this phase. + pub total_agent_count: i64, } -/// Boundary and mode for rewinding session history. +/// One durable factory progress record. /// ///
/// @@ -5174,14 +5568,22 @@ pub struct HistoryPreviewRewindResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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, +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 captured file that rewind intentionally left unchanged. +/// A bidirectional page of factory progress. /// ///
/// @@ -5191,14 +5593,22 @@ pub struct HistoryRewindRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct HistorySkippedFileRestore { - /// Absolute path of the skipped file. - pub path: String, - /// Reason the file was not restored. - pub reason: HistoryFileRestoreSkipReason, +pub struct FactoryProgressPage { + /// Whether progress records newer than this page exist. + pub has_more_newer: bool, + /// Whether progress records older than this page exist. + pub has_more_older: bool, + /// Newest sequence number in this page, or null when empty. + pub newest_seq: Option, + /// Oldest sequence number in this page, or null when empty. + pub oldest_seq: Option, + /// Progress records in sequence order. + pub records: Vec, + /// Run revision reflected by this page. + pub revision: i64, } -/// Structured outcome of a rewind request. +/// Wire-only per-invocation factory resource ceiling overrides. /// ///
/// @@ -5208,22 +5618,22 @@ pub struct HistorySkippedFileRestore { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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`). +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 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`. + pub max_ai_credits: Option, + /// Maximum number of factory subagents that may run concurrently. #[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, + 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, } -/// Markdown summary of the conversation context (empty when not available). +/// Parameters for resuming a factory run from its persisted identity. /// ///
/// @@ -5233,12 +5643,21 @@ pub struct HistoryRewindResult { ///
#[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 FactoryResumeRequest { + /// Optional per-invocation resource ceiling overrides. + #[serde(skip_serializing_if = "Option::is_none")] + pub limits: Option, + /// Whether to emit factory phase names to the session transcript. + #[serde(skip_serializing_if = "Option::is_none")] + pub log_phase_names: Option, + /// Whether to notify the originating session when the factory completes. + #[serde(skip_serializing_if = "Option::is_none")] + pub notify_on_complete: Option, + /// Factory run identifier. + pub run_id: String, } -/// Identifier of the event to truncate to; this event and all later events are removed. +/// Complete current or terminal factory run envelope. /// ///
/// @@ -5248,12 +5667,32 @@ 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 FactoryRunResult { + /// One-based execution attempt represented by this envelope. Absent before the first attempt starts or when returned by an older runtime. + #[serde(skip_serializing_if = "Option::is_none")] + pub attempt: Option, + /// Error message for an errored run. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// Machine-readable failure details for a halted or 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, } -/// Number of events that were removed by the truncation. +/// Resolved persisted factory identity and resumed run envelope. /// ///
/// @@ -5263,18 +5702,14 @@ pub struct HistoryTruncateRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct HistoryTruncateResult { - /// 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, +pub struct FactoryResumeResult { + /// Persisted factory name resolved for the resumed run. + pub factory_name: String, + /// Terminal resumed run envelope. + pub run: FactoryRunResult, } -/// Authentication-info variant for GitHub-internal HMAC auth, carrying the public GitHub host and HMAC secret. +/// Full factory run observability detail. /// ///
/// @@ -5284,37 +5719,79 @@ 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 FactoryRunDetail { + /// Epoch milliseconds when the current active segment started, or null while inactive. + pub active_segment_started_at: Option, + /// Durable identities and live statuses for direct factory agents. + pub agents: Vec, + /// Approved effective resource ceilings, or null until approved. + pub approved: Option, + /// Epoch milliseconds when the run completed, or null while nonterminal. + pub completed_at: Option, + /// Durable resource consumption. + pub consumed: FactoryRunConsumed, + /// Epoch milliseconds when the run was created. + pub created_at: i64, + /// Current phase identity, or null before any phase is entered. + pub current_phase: Option, + /// Resource ceilings declared by the factory. + pub declared_limits: FactoryDeclaredLimits, + /// Number of phases declared by the factory. + pub declared_phase_count: i64, + /// Human-readable factory description. + pub description: String, + /// Registered factory name. + pub factory_name: String, + /// Number of direct factory agents currently live. + pub live_agent_count: i64, + /// Epoch milliseconds when this live-overlay snapshot was observed. + pub observed_at: i64, + /// Lifecycle and timing observations for each factory phase. + pub phases: Vec, + /// Bidirectional page of durable factory progress. + pub progress: FactoryProgressPage, + /// Monotonic durable run revision. + pub revision: i64, + /// Factory run identifier. + pub run_id: String, + /// Epoch milliseconds when execution first started, or null before start. + pub started_at: Option, + /// Current factory run status. + pub status: FactoryRunStatus, + /// Terminal run outcome, or null while nonterminal. + pub terminal: Option, + /// Total direct factory agents spawned across all attempts. + pub total_spawned_agent_count: i64, + /// Epoch milliseconds when the durable run was last updated. + pub updated_at: i64, } -/// Optional output returned by an SDK callback hook. +/// Options controlling factory invocation. +/// +///
+/// +/// **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 RunOptions { + /// Per-invocation resource ceiling overrides. #[serde(skip_serializing_if = "Option::is_none")] - pub output: Option, + pub limits: Option, + /// Whether to emit factory phase names to the session transcript. + #[serde(skip_serializing_if = "Option::is_none")] + pub log_phase_names: Option, + /// Whether to notify the originating session when the factory completes. + #[serde(skip_serializing_if = "Option::is_none")] + pub notify_on_complete: 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, } -/// Installed plugin record from global state, with marketplace, version, install time, enabled state, cache path, and source. +/// Parameters for invoking a registered factory. /// ///
/// @@ -5324,31 +5801,17 @@ 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 struct FactoryRunRequest { + /// Factory input value. + pub args: serde_json::Value, + /// Registered factory 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) + /// Factory invocation options. #[serde(skip_serializing_if = "Option::is_none")] - pub version: Option, + pub options: Option, } -/// Information about an installed plugin tracked in global state. +/// Internal parameters for resuming a factory run from a tool. /// ///
/// @@ -5358,22 +5821,18 @@ 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(crate) struct FactoryToolResumeRequest { + /// Optional per-invocation resource ceiling overrides. #[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 limits: Option, + /// Factory run identifier. + pub run_id: String, + /// Opaque identifier of the originating tool call. #[serde(skip_serializing_if = "Option::is_none")] - pub version: Option, + pub tool_call_id: Option, } -/// Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref or full commit SHA, and optional subpath. +/// Options for an internal tool-originated factory invocation. /// ///
/// @@ -5383,20 +5842,16 @@ 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, +pub(crate) struct FactoryToolRunOptions { + /// Per-invocation resource ceiling overrides. #[serde(skip_serializing_if = "Option::is_none")] - pub r#ref: Option, - pub repo: String, - /// Optional full 40-character hexadecimal commit SHA. + pub limits: Option, + /// Run identifier whose journal and progress should seed this resumed run. #[serde(skip_serializing_if = "Option::is_none")] - pub sha: Option, - /// Constant value. Always "github". - pub source: InstalledPluginSourceGitHubSource, + pub resume_from_run_id: Option, } -/// Source descriptor for a direct local plugin install, with a local filesystem path. +/// Internal parameters for invoking a registered factory from a tool. /// ///
/// @@ -5406,13 +5861,21 @@ 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(crate) struct FactoryToolRunRequest { + /// Factory input value. + pub args: serde_json::Value, + /// Registered factory name. + pub name: String, + /// Tool-originated factory invocation options. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) options: Option, + /// Opaque identifier of the originating tool call. + #[serde(skip_serializing_if = "Option::is_none")] + pub tool_call_id: Option, } -/// Source descriptor for a direct URL plugin install, with URL, optional ref or full commit SHA, and optional subpath. +/// Optional user prompt to combine with the fleet orchestration instructions. /// ///
/// @@ -5422,20 +5885,13 @@ 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, - /// Optional full 40-character hexadecimal commit SHA. +pub struct FleetStartRequest { + /// Optional user prompt to combine with fleet instructions #[serde(skip_serializing_if = "Option::is_none")] - pub sha: Option, - /// Constant value. Always "url". - pub source: InstalledPluginSourceUrlSource, - pub url: String, + pub prompt: Option, } -/// Canonical file or directory where custom instructions can be discovered or created, with location, kind, preference, and project path. +/// Indicates whether fleet mode was successfully activated. /// ///
/// @@ -5445,21 +5901,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 FleetStartResult { + /// Whether fleet mode was successfully activated + pub started: bool, } -/// Canonical files and directories where custom instructions can be created so the runtime will recognize them. +/// Folder path to add to trusted folders. /// ///
/// @@ -5469,12 +5916,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 FolderTrustAddParams { + /// Folder path to mark as trusted + pub path: String, } -/// Optional project paths to include in instruction discovery. +/// Folder path to check for trust. /// ///
/// @@ -5484,16 +5931,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 FolderTrustCheckParams { + /// Folder path to check + pub path: String, } -/// Optional project paths to include when enumerating instruction discovery targets. +/// Folder trust check result. /// ///
/// @@ -5503,16 +5946,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 FolderTrustCheckResult { + /// Whether the folder is trusted + pub trusted: bool, } -/// Loaded instruction source for a session, including path, content, category, location, applicability, and optional description. +/// Client environment metadata describing the process that produced a telemetry event. /// ///
/// @@ -5522,34 +5961,46 @@ 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 - #[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, +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, + /// Number of logical CPU cores on the host. + #[serde(rename = "cpu_count", skip_serializing_if = "Option::is_none")] + pub cpu_count: Option, + /// Distinct CPU model names for the host, comma-separated. + #[serde(rename = "cpu_model", skip_serializing_if = "Option::is_none")] + pub cpu_model: 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, } -/// Instruction sources loaded for the session, in merge order. +/// 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. /// ///
/// @@ -5559,12 +6010,43 @@ pub struct InstructionSource { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct InstructionsGetSourcesResult { - /// Instruction sources for the session - pub sources: Vec, +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, } -/// Parameters for interrupting the main agent turn. +/// 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. /// ///
/// @@ -5574,13 +6056,17 @@ pub struct InstructionsGetSourcesResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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. +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 flush_queued: Option, + pub session_id: Option, } -/// Result of interrupting the main agent turn. +/// Asks the SDK client to acquire a GitHub access token from an opaque callback registration. /// ///
/// @@ -5590,78 +6076,40 @@ pub struct InterruptMainTurnRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct InterruptMainTurnResult { - /// Whether an in-flight main agent turn was interrupted. False when the main loop was not processing. - pub interrupted: bool, +pub struct GitHubTokenAcquireRequest { + /// Effective GitHub host for which the callback must return a token. + pub host: String, + /// Why the runtime is requesting a GitHub credential. + pub reason: GitHubTokenAcquireReason, + /// Opaque identifier generated by the SDK for this callback registration. + pub registration_id: String, + /// Session receiving the token. Absent only before a cloud session has been assigned its id. + #[serde(skip_serializing_if = "Option::is_none")] + pub session_id: Option, } -/// 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. - #[serde(skip_serializing_if = "Option::is_none")] - pub cancel: Option, - /// Optional human-readable reason for the cancellation, propagated for logging. - #[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 struct GitHubTokenAcquireResultToken { + /// GitHub access token acquired by the SDK host. + pub access_token: String, + /// Remaining token lifetime in seconds when callback execution completes. It must exceed the one-hour preflight refresh threshold. + pub expires_in: i64, + /// GitHub credential response variant discriminator. + pub kind: GitHubTokenAcquireResultTokenKind, + /// OAuth token type. Defaults to bearer when omitted. #[serde(skip_serializing_if = "Option::is_none")] - pub end: Option, - /// Matches the requestId from the originating httpRequestStart frame. - pub request_id: RequestId, + pub token_type: 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. #[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. - #[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, +pub struct GitHubTokenAcquireResultCancelled { + /// GitHub credential response variant discriminator. + pub kind: GitHubTokenAcquireResultCancelledKind, } -/// 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. +/// Pending external tool call request ID, with the tool result or an error describing why it failed. /// ///
/// @@ -5671,15 +6119,18 @@ pub struct LlmInferenceHttpRequestStartResult {} ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct LlmInferenceHttpResponseChunkError { - /// Optional machine-readable error code. +pub struct HandlePendingToolCallRequest { + /// Error message if the tool call failed #[serde(skip_serializing_if = "Option::is_none")] - pub code: Option, - /// Human-readable failure description. - pub message: String, + 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, } -/// A response body chunk or terminal error. +/// Indicates whether the external tool call result was handled successfully. /// ///
/// @@ -5689,23 +6140,12 @@ 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. - #[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 struct HandlePendingToolCallResult { + /// Whether the tool call result was handled successfully + pub success: bool, } -/// Whether the chunk was accepted. +/// Indicates whether an in-progress manual compaction was aborted. /// ///
/// @@ -5715,12 +6155,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 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, } -/// Response head. +/// Indicates whether an in-progress background compaction was cancelled. /// ///
/// @@ -5730,18 +6170,12 @@ 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 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, } -/// Whether the start frame was accepted. +/// Parameters for clearing the conversation and seeding the window that replaces it. /// ///
/// @@ -5751,12 +6185,12 @@ 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 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, } -/// Indicates whether the calling client was registered as the LLM inference provider. +/// What a successful clear removed. A clear that could not be applied rejects instead of reporting a count. /// ///
/// @@ -5766,12 +6200,12 @@ 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 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, } -/// Pre-resolved working-directory context for session startup. +/// Post-compaction context window usage breakdown /// ///
/// @@ -5781,24 +6215,25 @@ 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 +pub struct HistoryCompactContextWindow { + /// Token count from non-system messages (user, assistant, tool) #[serde(skip_serializing_if = "Option::is_none")] - pub git_root: Option, - /// Repository host type + 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 host_type: Option, - /// Repository slug in `owner/name` form, when known + 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 repository: Option, + pub tool_definitions_tokens: Option, } -/// Persisted local session metadata, including identifiers, timestamps, summary/name, client, context, detached state, and task ID. +/// Optional compaction parameters. /// ///
/// @@ -5808,36 +6243,19 @@ 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. +pub struct HistoryCompactRequest { + /// Optional user-provided instructions to focus the compaction summary #[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 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 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 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 summary: Option, + pub trigger: Option, } -/// Message text, optional severity level, persistence flag, optional follow-up URL, and optional tip. +/// Compaction outcome with the number of tokens and messages removed, summary text, and the resulting context window breakdown. /// ///
/// @@ -5847,27 +6265,22 @@ 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". +pub struct HistoryCompactResult { + /// Post-compaction context window usage breakdown #[serde(skip_serializing_if = "Option::is_none")] - pub r#type: Option, - /// Optional URL the user can open in their browser for more details + 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 url: Option, + 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. +/// A root user turn that the session can rewind to. /// ///
/// @@ -5877,12 +6290,28 @@ 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 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, } -/// Parameters for (re)loading the merged LSP configuration set. +/// Rewind points and file-change-tracking availability for the session. /// ///
/// @@ -5892,19 +6321,17 @@ 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). - #[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 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 working_directory: Option, + pub unavailable_reason: Option, } -/// Result of registering a new marketplace. +/// Event boundary to preview for conversation-and-files rewind. /// ///
/// @@ -5914,12 +6341,12 @@ 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 HistoryPreviewRewindRequest { + /// ID of the user.message event that begins the discarded suffix. + pub event_id: String, } -/// Plugin entry advertised by a marketplace. +/// A file that a conversation-and-files rewind would restore. /// ///
/// @@ -5929,15 +6356,18 @@ pub struct MarketplaceAddResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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, +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, } -/// Plugins advertised by the marketplace. +/// Files and aggregate changes for a prospective rewind. /// ///
/// @@ -5947,12 +6377,19 @@ 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 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, } -/// Registered marketplace summary. +/// Boundary and mode for rewinding session history. /// ///
/// @@ -5962,17 +6399,14 @@ 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. - #[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, +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, } -/// All registered marketplaces, including built-in defaults. +/// A captured file that rewind intentionally left unchanged. /// ///
/// @@ -5982,12 +6416,14 @@ pub struct MarketplaceInfo { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct MarketplaceListResult { - /// Registered marketplaces - pub marketplaces: Vec, +pub struct HistorySkippedFileRestore { + /// Absolute path of the skipped file. + pub path: String, + /// Reason the file was not restored. + pub reason: HistoryFileRestoreSkipReason, } -/// Per-marketplace refresh result, including marketplace name, success flag, and optional failure error. +/// Structured outcome of a rewind request. /// ///
/// @@ -5997,17 +6433,22 @@ pub struct MarketplaceListResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct MarketplaceRefreshEntry { - /// Error message (failure only) +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 error: Option, - /// Marketplace name that was refreshed - pub name: String, - /// Whether the refresh succeeded - pub success: bool, + /// 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, } -/// Result of refreshing one or more marketplace catalogs. +/// Markdown summary of the conversation context (empty when not available). /// ///
/// @@ -6017,12 +6458,12 @@ 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 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, } -/// Outcome of the remove attempt, including dependent-plugin info when applicable. +/// Identifier of the event to truncate to; this event and all later events are removed. /// ///
/// @@ -6032,15 +6473,12 @@ 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`. - #[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 struct HistoryTruncateRequest { + /// Event ID to truncate to. This event and all events after it are removed from the session. + pub event_id: String, } -/// MCP server allowed by policy, with server name and optional PII-free explanatory note. +/// Number of events that were removed by the truncation. /// ///
/// @@ -6050,56 +6488,35 @@ 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 +pub struct HistoryTruncateResult { + /// Failure detail when checkpointCleanupFailed is true. #[serde(skip_serializing_if = "Option::is_none")] - pub redacted_note: Option, + 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, } -/// MCP server, tool name, and arguments to invoke from an MCP App view. -/// -///
-/// -/// **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 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, +pub(crate) struct HookInvokeRequest { + pub hook_type: HookType, + pub input: serde_json::Value, + pub session_id: SessionId, } -/// Capability negotiation snapshot -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
+/// Optional output returned by an SDK callback hook. #[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(crate) struct HookInvokeResponse { + #[serde(skip_serializing_if = "Option::is_none")] + pub output: Option, } -/// MCP server to diagnose MCP Apps wiring for. +/// Optional project paths and host-exclusion behavior for server-scoped hook discovery. /// ///
/// @@ -6109,12 +6526,16 @@ 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 HooksDiscoverRequest { + /// When true, omit host-owned user and plugin hook rows and their diagnostics. Managed-policy hooks and trusted repository hooks remain visible, and host disabledHooks still contribute to each remaining row's effective enabled state. This filters sources rather than simulating a host with no settings. + #[serde(skip_serializing_if = "Option::is_none")] + pub exclude_host_hooks: Option, + /// Optional project directory paths whose trusted repository and project-expanded plugin hooks should be discovered. When omitted or empty, user, managed-policy, and globally enabled installed or explicit plugin hooks are returned without project expansion. + #[serde(skip_serializing_if = "Option::is_none")] + pub project_paths: Option>, } -/// What the server returned for this session +/// Server-discovered hook actions and partial-load diagnostics from user, repository, plugin, and managed-policy sources. Concrete sessions may include additional session-specific hook sources. /// ///
/// @@ -6124,18 +6545,16 @@ 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 HooksDiscoverResult { + /// Errors for hook sources or actions that could not be loaded, making the result partially incomplete. Other valid actions are still returned. Project-resolution and repository-settings errors are prefixed with their project path. + pub errors: Vec, + /// All discovered hook actions. Byte-identical actions remain separate rows even when they share a disable key. + pub hooks: Vec, + /// Non-fatal source-loading warnings. Discovery remains complete for the affected source, although the source had a recoverable issue. Repository-settings warnings are prefixed with their project path when attribution is available. + pub warnings: Vec, } -/// Diagnostic snapshot of MCP Apps wiring for the named server. +/// Installed plugin record from global state, with marketplace, version, install time, enabled state, cache path, and source. /// ///
/// @@ -6145,14 +6564,34 @@ 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 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, + /// Absolute path of the marketplace directory a live plugin was resolved from. Present only on live, never-persisted records — those synthesized at session start for a directory/local marketplace, whose cache_path points at the real plugin directory on disk rather than a copy under the installed-plugins cache. Its presence is what marks a record as live, and no record carrying it is ever written to the persisted installedPlugins key. + #[serde(rename = "installed_from", skip_serializing_if = "Option::is_none")] + pub installed_from: Option, + /// 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, } -/// Current host context +/// Information about an installed plugin tracked in global state. /// ///
/// @@ -6162,31 +6601,25 @@ pub struct McpAppsDiagnoseResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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 +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 theme: Option, - /// IANA timezone, e.g. 'America/New_York' + pub direct_source_id: Option, + /// Whether the plugin is currently enabled for new sessions + pub enabled: bool, + /// Absolute path of the marketplace directory a live plugin was resolved from. Present only on live, never-persisted records — a plugin belonging to a directory/local marketplace, which is loaded from its real directory on every pass instead of a copy under the installed-plugins cache. Its presence is what marks a listed plugin as live: such a plugin is always present on disk, so `enabled` is its only meaningful state and it is never "not installed". #[serde(skip_serializing_if = "Option::is_none")] - pub time_zone: Option, - /// Host application identifier + pub installed_from: Option, + /// 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) #[serde(skip_serializing_if = "Option::is_none")] - pub user_agent: Option, + pub version: Option, } -/// Current host context advertised to MCP App guests. +/// Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref or full commit SHA, and optional subpath. /// ///
/// @@ -6196,12 +6629,23 @@ pub struct McpAppsHostContextDetails { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpAppsHostContext { - /// Current host context - pub context: McpAppsHostContextDetails, +pub struct InstalledPluginSourceGitHub { + /// Optional repository-relative path to the plugin. + #[serde(skip_serializing_if = "Option::is_none")] + pub path: Option, + /// Optional Git ref to resolve. + #[serde(skip_serializing_if = "Option::is_none")] + pub r#ref: Option, + /// GitHub repository in `owner/repo` form. + 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, } -/// MCP server to list app-callable tools for. +/// Source descriptor for a direct local plugin install, with a local filesystem path. /// ///
/// @@ -6211,14 +6655,14 @@ 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 InstalledPluginSourceLocal { + /// Local filesystem path to the plugin. + pub path: String, + /// Constant value. Always "local". + pub source: InstalledPluginSourceLocalSource, } -/// App-callable tools from the named MCP server. +/// Source descriptor for a direct URL plugin install, with URL, optional ref or full commit SHA, and optional subpath. /// ///
/// @@ -6228,12 +6672,23 @@ 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 InstalledPluginSourceUrl { + /// Optional source-relative path to the plugin. + #[serde(skip_serializing_if = "Option::is_none")] + pub path: Option, + /// Optional Git ref to resolve. + #[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, + /// URL of the plugin source. + pub url: String, } -/// MCP server and resource URI to fetch. +/// Canonical file or directory where custom instructions can be discovered or created, with location, kind, preference, and project path. /// ///
/// @@ -6243,14 +6698,21 @@ 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 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, } -/// MCP Apps resource content with URI, optional MIME type, text or base64 blob, and resource metadata. +/// Canonical files and directories where custom instructions can be created so the runtime will recognize them. /// ///
/// @@ -6260,24 +6722,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 InstructionDiscoveryPathList { + /// Canonical instruction create/discovery files and directories, in priority order + pub paths: Vec, } -/// Resource contents returned by the MCP server. +/// Optional project paths to include in instruction discovery. /// ///
/// @@ -6287,12 +6737,16 @@ 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 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>, } -/// Host context advertised to MCP App guests +/// Optional project paths to include when enumerating instruction discovery targets. /// ///
/// @@ -6302,31 +6756,16 @@ 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 - #[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' +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 time_zone: Option, - /// Host application identifier + 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 user_agent: Option, + pub project_paths: Option>, } -/// Host context to advertise to MCP App guests. +/// Loaded instruction source for a session, including path, content, category, location, applicability, and optional description. /// ///
/// @@ -6336,12 +6775,34 @@ 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 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, } -/// The requestId previously passed to executeSampling that should be cancelled. +/// Instruction sources loaded for the session, in merge order. /// ///
/// @@ -6351,12 +6812,12 @@ 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 InstructionsGetSourcesResult { + /// Instruction sources for the session + pub sources: Vec, } -/// Indicates whether an in-flight sampling execution with the given requestId was found and cancelled. +/// Parameters for interrupting the main agent turn. /// ///
/// @@ -6366,12 +6827,13 @@ 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 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, } -/// MCP server name and configuration to add to user configuration. +/// Result of interrupting the main agent turn. /// ///
/// @@ -6381,14 +6843,79 @@ 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 InterruptMainTurnResult { + /// Whether an in-flight main agent turn was interrupted. False when the main loop was not processing. + pub interrupted: bool, } -/// MCP server names to disable for new sessions. +/// 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. + #[serde(skip_serializing_if = "Option::is_none")] + pub cancel: Option, + /// Optional human-readable reason for the cancellation, propagated for logging. + #[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. + #[serde(skip_serializing_if = "Option::is_none")] + 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 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, + /// HTTP request headers, preserving multiple values per name. + 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, +} + +/// 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. /// ///
/// @@ -6398,12 +6925,15 @@ 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 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 names to enable for new sessions. +/// A response body chunk or terminal error. /// ///
/// @@ -6413,12 +6943,23 @@ 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 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, } -/// User-configured MCP servers, keyed by server name. +/// Whether the chunk was accepted. /// ///
/// @@ -6428,12 +6969,12 @@ 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 LlmInferenceHttpResponseChunkResult { + /// True when the chunk was matched to a pending request; false when unknown. + pub accepted: bool, } -/// MCP server name to remove from user configuration. +/// Response head. /// ///
/// @@ -6443,12 +6984,19 @@ 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 LlmInferenceHttpResponseStartRequest { + /// HTTP response headers, preserving multiple values per name. + 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 server name and replacement configuration to write to user configuration. +/// Whether the start frame was accepted. /// ///
/// @@ -6458,14 +7006,12 @@ 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 name: String, +pub struct LlmInferenceHttpResponseStartResult { + /// True when the response start was matched to a pending request; false when unknown. + pub accepted: bool, } -/// Opaque auth info used to configure GitHub MCP. +/// Indicates whether the calling client was registered as the LLM inference provider. /// ///
/// @@ -6475,13 +7021,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 LlmInferenceSetProviderResult { + /// Whether the provider was set successfully + pub success: bool, } -/// Result of configuring GitHub MCP. +/// Pre-resolved working-directory context for session startup. /// ///
/// @@ -6491,12 +7036,24 @@ 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 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, } -/// Name of the MCP server to disable for the session. +/// Persisted local session metadata, including identifiers, timestamps, summary/name, client, context, detached state, and task ID. /// ///
/// @@ -6506,12 +7063,36 @@ 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 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, } -/// Optional working directory used as context for MCP server discovery. +/// Message text, optional severity level, persistence flag, optional follow-up URL, and optional tip. /// ///
/// @@ -6521,13 +7102,27 @@ 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 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 working_directory: Option, + 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, } -/// MCP servers discovered from user, workspace, plugin, and built-in sources. +/// Identifier of the session event that was emitted for the log message. /// ///
/// @@ -6537,12 +7132,12 @@ 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 LogResult { + /// The unique identifier of the emitted session event + pub event_id: String, } -/// Name of the MCP server to enable for the session. +/// Parameters for (re)loading the merged LSP configuration set. /// ///
/// @@ -6552,12 +7147,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 server_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, } -/// 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. +/// Validated device-managed settings discovered before a session exists. /// ///
/// @@ -6567,9 +7169,16 @@ pub struct McpEnableRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpExecuteSamplingRequest {} +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, +} -/// Identifiers and raw MCP CreateMessageRequest params used to run a sampling inference. +/// Result of registering a new marketplace. /// ///
/// @@ -6579,18 +7188,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 server_name: String, +pub struct MarketplaceAddResult { + /// Final name of the marketplace as resolved from its manifest + pub name: String, } -/// MCP server filtered by policy, with name, reason, and optional redacted reason. +/// Plugin entry advertised by a marketplace. /// ///
/// @@ -6600,36 +7203,15 @@ pub struct McpExecuteSamplingParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpFilteredServer { - /// Deprecated. This field is no longer populated. - #[doc(hidden)] - #[deprecated] +pub struct MarketplacePluginInfo { + /// Short description from the marketplace catalog, when present #[serde(skip_serializing_if = "Option::is_none")] - pub enterprise_name: Option, - /// Filtered server name + pub description: Option, + /// Plugin name as listed in the marketplace catalog 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, -} - -#[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. +/// Plugins advertised by the marketplace. /// ///
/// @@ -6637,16 +7219,14 @@ 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 MarketplaceBrowseResult { + /// Plugins advertised by the marketplace + pub plugins: Vec, } -/// Indicates whether the pending MCP headers refresh response was accepted. +/// Registered marketplace summary. /// ///
/// @@ -6656,12 +7236,17 @@ 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 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, } -/// Recorded MCP server connection failure. +/// All registered marketplaces, including built-in defaults. /// ///
/// @@ -6671,14 +7256,12 @@ 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 MarketplaceListResult { + /// Registered marketplaces + pub marketplaces: Vec, } -/// Recorded MCP server pending-auth state. +/// Per-marketplace refresh result, including marketplace name, success flag, and optional failure error. /// ///
/// @@ -6688,12 +7271,17 @@ 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 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, } -/// Host-level state, omitted when no MCP host is initialized. +/// Result of refreshing one or more marketplace catalogs. /// ///
/// @@ -6703,24 +7291,12 @@ 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 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, +pub struct MarketplaceRefreshResult { + /// Per-marketplace refresh results in deterministic order. + pub results: Vec, } -/// Server name to check running status for. +/// Outcome of the remove attempt, including dependent-plugin info when applicable. /// ///
/// @@ -6730,12 +7306,15 @@ 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 MarketplaceRemoveResult { + /// 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, } -/// Whether the named MCP server is running. +/// MCP server allowed by policy, with server name and optional PII-free explanatory note. /// ///
/// @@ -6745,46 +7324,15 @@ 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, -} - -/// Server name whose tool list 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 McpListToolsRequest { - /// Name of the connected MCP server whose tools to list. - pub server_name: String, -} - -/// Normalized MCP Apps discovery metadata from a tool's `_meta.ui` block. -/// -///
-/// -/// **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 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. +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 visibility: Option>, + pub redacted_note: Option, } -/// MCP tool metadata with tool name, optional description, and normalized MCP Apps discovery metadata. +/// MCP server, tool name, and arguments to invoke from an MCP App view. /// ///
/// @@ -6794,18 +7342,19 @@ 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. +pub struct McpAppsCallToolRequest { + /// Tool arguments #[serde(skip_serializing_if = "Option::is_none")] - pub ui: Option, + 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, } -/// Tools exposed by the connected MCP server. Throws when the server is not connected. +/// Capability negotiation snapshot /// ///
/// @@ -6815,12 +7364,16 @@ pub struct McpTools { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpListToolsResult { - /// Tools exposed by the server. - pub tools: Vec, +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, } -/// Identifies the MCP server whose persisted OAuth credentials were updated. +/// MCP server to diagnose MCP Apps wiring for. /// ///
/// @@ -6830,36 +7383,12 @@ pub struct McpListToolsResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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, -} - -#[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, +pub struct McpAppsDiagnoseRequest { + /// MCP server to probe + pub server_name: String, } -/// Pending MCP OAuth request ID and host-provided token or cancellation response. +/// What the server returned for this session /// ///
/// @@ -6867,16 +7396,20 @@ 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 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, } -/// Indicates whether the pending MCP OAuth response was accepted. +/// Diagnostic snapshot of MCP Apps wiring for the named server. /// ///
/// @@ -6886,12 +7419,14 @@ 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 McpAppsDiagnoseResult { + /// Capability negotiation snapshot + pub capability: McpAppsDiagnoseCapability, + /// What the server returned for this session + pub server: McpAppsDiagnoseServer, } -/// Remote MCP server name and optional overrides controlling reauthentication, OAuth client display name, callback success-page copy, and static OAuth client selection. +/// Current host context /// ///
/// @@ -6901,33 +7436,31 @@ 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. +pub struct McpAppsHostContextDetails { + /// Display modes the host supports #[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. + pub available_display_modes: Option>, + /// Current display mode (SEP-1865) #[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. + pub display_mode: Option, + /// BCP-47 locale, e.g. 'en-US' #[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. + pub locale: Option, + /// Platform type for responsive design #[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. + pub platform: Option, + /// UI theme preference per SEP-1865 #[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. + pub theme: Option, + /// IANA timezone, e.g. 'America/New_York' #[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. + pub time_zone: Option, + /// Host application identifier #[serde(skip_serializing_if = "Option::is_none")] - pub public_client: Option, - /// Name of the remote MCP server to authenticate - pub server_name: String, + pub user_agent: Option, } -/// OAuth authorization URL the caller should open, or empty when cached tokens already authenticated the server. +/// Current host context advertised to MCP App guests. /// ///
/// @@ -6937,13 +7470,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 McpAppsHostContext { + /// Current host context + pub context: McpAppsHostContextDetails, } -/// Pending MCP OAuth request id to respond to. +/// MCP server to list app-callable tools for. /// ///
/// @@ -6953,12 +7485,14 @@ pub struct McpOauthLoginResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpOauthRespondRequest { - /// OAuth request identifier from the mcp.oauth_required event - pub request_id: RequestId, +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, } -/// Indicates whether the pending MCP OAuth response was accepted. +/// App-callable tools from the named MCP server. /// ///
/// @@ -6968,12 +7502,12 @@ pub struct McpOauthRespondRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpOauthRespondResult { - /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. - pub success: bool, +pub struct McpAppsListToolsResult { + /// App-callable tools from the server + pub tools: Vec>, } -/// Registration parameters for an external MCP client. +/// MCP server and resource URI to fetch. /// ///
/// @@ -6983,21 +7517,14 @@ pub struct McpOauthRespondResult { ///
#[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 struct McpAppsReadResourceRequest { + /// Name of the MCP server hosting the resource 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, + /// Resource URI (typically ui://...) + pub uri: String, } -/// Opaque MCP reload configuration. +/// MCP Apps resource content with URI, optional MIME type, text or base64 blob, and resource metadata. /// ///
/// @@ -7007,13 +7534,24 @@ 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). - #[doc(hidden)] - pub(crate) config: serde_json::Value, +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, } -/// Indicates whether the auto-managed `github` MCP server was removed (false when nothing to remove). +/// Resource contents returned by the MCP server. /// ///
/// @@ -7023,12 +7561,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 McpAppsReadResourceResult { + /// Resource contents returned by the server + pub contents: Vec, } -/// Standard MCP resource annotations plus preserved non-standard annotation fields. +/// Host context advertised to MCP App guests /// ///
/// @@ -7038,22 +7576,31 @@ 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 +pub struct McpAppsSetHostContextDetails { + /// Display modes the host supports #[serde(skip_serializing_if = "Option::is_none")] - pub additional_properties: Option>, - /// Intended audience roles for this resource + pub available_display_modes: Option>, + /// Current display mode (SEP-1865) #[serde(skip_serializing_if = "Option::is_none")] - pub audience: Option>, - /// Last-modified timestamp hint + pub display_mode: Option, + /// BCP-47 locale, e.g. 'en-US' #[serde(skip_serializing_if = "Option::is_none")] - pub last_modified: Option, - /// Priority hint for model/client use + pub locale: Option, + /// Platform type for responsive design #[serde(skip_serializing_if = "Option::is_none")] - pub priority: Option, + 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, } -/// A resource icon descriptor plus preserved non-standard icon fields. +/// Host context to advertise to MCP App guests. /// ///
/// @@ -7063,24 +7610,12 @@ 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 - #[serde(skip_serializing_if = "Option::is_none")] - pub theme: Option, +pub struct McpAppsSetHostContextRequest { + /// Host context advertised to MCP App guests + pub context: McpAppsSetHostContextDetails, } -/// 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`. +/// The requestId previously passed to executeSampling that should be cancelled. /// ///
/// @@ -7090,38 +7625,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 McpCancelSamplingExecutionParams { + /// The requestId previously passed to executeSampling that should be cancelled + pub request_id: RequestId, } -/// 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. /// ///
/// @@ -7131,24 +7640,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 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 whose resources to enumerate. +/// MCP server name and configuration to add to user configuration. /// ///
/// @@ -7158,15 +7655,14 @@ 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 McpConfigAddRequest { + /// MCP server configuration (stdio process or remote HTTP/SSE) + pub config: serde_json::Value, + /// Unique name for the MCP server + pub name: String, } -/// One page of resources advertised by the named MCP server. +/// MCP server names to disable for new sessions. /// ///
/// @@ -7176,15 +7672,12 @@ 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 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, } -/// MCP server whose resource templates to enumerate. +/// MCP server names to enable for new sessions. /// ///
/// @@ -7194,15 +7687,12 @@ 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 - #[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 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, } -/// 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`. +/// User-configured MCP servers, keyed by server name. /// ///
/// @@ -7212,35 +7702,27 @@ pub struct McpResourcesListTemplatesRequest { ///
#[derive(Debug, Clone, Default, 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 struct McpConfigList { + /// All MCP servers from user config, keyed by name + pub servers: HashMap, +} + +/// MCP server name to remove from 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 McpConfigRemoveRequest { + /// Name of the MCP server to remove 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. +/// MCP server name and replacement configuration to write to user configuration. /// ///
/// @@ -7250,15 +7732,14 @@ 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 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, } -/// MCP server and resource URI to fetch. +/// Credential-free authentication identity used to configure GitHub MCP. /// ///
/// @@ -7268,14 +7749,13 @@ 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(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, } -/// Resource contents returned by the MCP server. +/// Result of configuring GitHub MCP. /// ///
/// @@ -7285,12 +7765,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 McpConfigureGitHubResult { + /// Whether GitHub MCP configuration changed. + pub changed: bool, } -/// 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. +/// Name of the MCP server to disable for the session. /// ///
/// @@ -7300,15 +7780,12 @@ 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 struct McpDisableRequest { + /// Name of the MCP server to disable pub server_name: String, } -/// Outcome of an MCP sampling execution: success result, failure error, or cancellation. +/// Optional working directory used as context for MCP server discovery. /// ///
/// @@ -7318,18 +7795,13 @@ 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. +pub struct McpDiscoverRequest { + /// Working directory used as context for discovery (e.g., plugin resolution) #[serde(skip_serializing_if = "Option::is_none")] - pub result: Option, + pub working_directory: Option, } -/// MCP server status entry, including config source/plugin source and any connection error. +/// MCP servers discovered from user, workspace, plugin, and built-in sources. /// ///
/// @@ -7339,26 +7811,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, stopped, or not_configured - pub status: McpServerStatus, +pub struct McpDiscoverResult { + /// MCP servers discovered from all sources + pub servers: Vec, } -/// Authentication settings with optional redirect port configuration. +/// Name of the MCP server to enable for the session. /// ///
/// @@ -7368,13 +7826,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 McpEnableRequest { + /// Name of the MCP server to enable + pub server_name: String, } -/// Remote MCP server configuration accessed over HTTP or SSE. +/// 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. /// ///
/// @@ -7384,51 +7841,9 @@ 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, - /// 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, - /// 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. - #[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, -} +pub struct McpExecuteSamplingRequest {} -/// Stdio MCP server configuration launched as a child process. +/// Identifiers and raw MCP CreateMessageRequest params used to run a sampling inference. /// ///
/// @@ -7438,45 +7853,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, - /// 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>, +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 servers configured for the session, with their connection status and host-level state. +/// MCP server whose connection attempt failed. /// ///
/// @@ -7486,15 +7874,15 @@ 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. +pub struct McpFailedServer { + /// The captured connection failure detail. #[serde(skip_serializing_if = "Option::is_none")] - pub host: Option, - /// Configured MCP servers - pub servers: Vec, + pub error: Option, + /// The config key of the server that failed to connect. + pub name: String, } -/// Mode controlling how MCP server env values are resolved (`direct` or `indirect`). +/// MCP server filtered by policy, with name, reason, and optional redacted reason. /// ///
/// @@ -7504,12 +7892,38 @@ 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 McpFilteredServer { + /// Deprecated. This field is no longer populated. + #[doc(hidden)] + #[deprecated] + #[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, } -/// Env-value mode recorded on the session after the update. +#[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, + /// Headers-refresh response variant discriminator. + pub kind: McpHeadersHandlePendingHeadersRefreshRequestHeadersKind, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct McpHeadersHandlePendingHeadersRefreshRequestNone { + /// Headers-refresh response variant discriminator. + pub kind: McpHeadersHandlePendingHeadersRefreshRequestNoneKind, +} + +/// MCP headers refresh request id and the host response. /// ///
/// @@ -7517,14 +7931,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 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, } -/// 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. +/// Indicates whether the pending MCP headers refresh response was accepted. /// ///
/// @@ -7534,15 +7950,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). Omit to start the server with its already-registered configuration (config-free start-by-name). - #[serde(skip_serializing_if = "Option::is_none")] - pub config: Option, - /// Name of the MCP server to start - pub server_name: String, +pub struct McpHeadersHandlePendingHeadersRefreshRequestResult { + /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. + pub success: bool, } -/// MCP server startup filtering result. +/// Recorded MCP server connection failure. /// ///
/// @@ -7552,15 +7965,14 @@ pub struct McpStartServerRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpStartServersResult { - /// 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 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, } -/// Server name for an individual MCP server stop. +/// Recorded MCP server pending-auth state. /// ///
/// @@ -7570,12 +7982,12 @@ 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 McpServerNeedsAuthInfo { + /// epoch-ms timestamp at which the server signalled it needs authentication. + pub timestamp: i64, } -/// Server name identifying the external client to remove. +/// Host-level state, omitted when no MCP host is initialized. /// ///
/// @@ -7585,12 +7997,24 @@ 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 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, } -/// Memory configuration for this session. +/// One change applying the plan would make, described rather than serialised so the configuration payload stays behind the runtime boundary. /// ///
/// @@ -7600,51 +8024,63 @@ 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, -} - -/// 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, +pub struct McpPlanConfigurationChange { + /// Names of the configuration fields the change would set, without their values. + pub changed_fields: Vec, + /// Configuration key the change applies to. + pub config_key: String, + /// Whether the change would create a new entry or modify an existing one. + pub operation: McpPlanConfigurationOperation, + /// Scope the change would be written to. + pub scope: McpPlanScope, + /// Secret placeholders the written configuration would reference. The constrained placeholder type cannot carry a literal secret value. + pub secret_references: Vec, } +/// Normalised identity of the MCP server a plan targets, independent of how the card spelled it. +/// +///
+/// +/// **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 MetadataContextAttributionResultContextAttributionEntriesItem { - /// Supplementary per-entry metadata (e.g. `messageCount`, `role`, `evictable`, `pluginSource`). Values are stringified; parse as needed and ignore unrecognized keys. +pub struct McpPlanResourceIdentity { + /// Canonical, normalised name of the server, for example `io.github.owner/server`. + pub canonical_name: String, + /// Registry identifier of the server, when it came from a registry. #[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. + pub registry_id: Option, + /// Local configuration key the server would be recorded under. + pub server_name: String, + /// Version advertised by the card, when it declares one. #[serde(skip_serializing_if = "Option::is_none")] - pub parent_id: Option, - /// Token count currently in context attributable to this entry. - pub tokens: i64, + pub version: Option, } -/// Per-source token attribution snapshot for the current context window. The heaviest individual messages are available separately via `metadata.getContextHeaviestMessages`. +/// Outcome of evaluating the planned server against registry and enterprise policy. Evaluation is read-only. +/// +///
+/// +/// **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 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, +pub struct McpPlanPolicyResult { + /// What policy decided for this server. + pub decision: McpPlanPolicyDecision, + /// Human-readable explanation, safe to surface. Never contains a query, URL, handle, or secret. + #[serde(skip_serializing_if = "Option::is_none")] + pub reason: Option, + /// Which authority produced the decision. + pub source: McpPlanPolicySource, } -/// Per-source attribution breakdown for the session's current context window, or null if uninitialized. +/// Provenance of the exact validated JSON MCP card content bound privately to a completed plan and its opaque handle. /// ///
/// @@ -7654,12 +8090,18 @@ 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 struct McpPlanProvenance { + /// Authority associated with the validated card, without path, query, or credentials. Inert untrusted data. + pub authority: String, + /// Semantic digest of the exact validated JSON content bound to the plan handle. + pub card_digest: CardDigest, + /// JSON MCP media type the validated card was interpreted as. + pub media_type: McpServerCardMediaType, + /// ISO 8601 timestamp at which the runtime completed strict parsing and schema validation of the card content. + pub validated_at: String, } -/// Parameters for the heaviest-messages query. +/// Where a plan would be written. /// ///
/// @@ -7669,13 +8111,14 @@ 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 struct McpPlanTarget { + /// Configuration key the server would be recorded under within that scope. + pub config_key: String, + /// Configuration scope the plan targets. + pub scope: McpPlanScope, } -/// The heaviest individual messages in the session's context window, most-expensive first. +/// A normalised, inert description of what installing an MCP server would involve. Carries no raw card, no install specification, and no secret value. /// ///
/// @@ -7685,14 +8128,33 @@ 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 McpInstallPlan { + /// The configuration changes installing would make, described rather than serialised, so the mutable configuration payload stays behind the runtime boundary. + pub configuration_changes: Vec, + /// Normalised identity of the server the plan would install. + pub identity: McpPlanResourceIdentity, + /// Opaque, runtime-instance scoped, TTL-bound, single-use handle for this plan. Rejected when stale, replayed, or presented to a different runtime instance. Never logged. + pub plan_handle: String, + /// ISO 8601 timestamp after which the plan handle is stale and will be rejected. Abandoning a plan needs no call: an unused handle simply expires, so cancellation before commit is side-effect free. + pub plan_handle_expires_at: String, + /// Outcome of evaluating the server against registry and enterprise policy. + pub policy: McpPlanPolicyResult, + /// Origin and semantic digest of the exact validated JSON MCP card content bound to this plan. + pub provenance: McpPlanProvenance, + /// Identifier of the choice the runtime would pick by default. Omitted when there is no eligible transport, or when the runtime expresses no preference. + #[serde(skip_serializing_if = "Option::is_none")] + pub recommended_transport_choice_id: Option, + /// Whether applying this plan would require an MCP reload to take effect. Planning itself never reloads. + pub reload_required: bool, + /// Whether the plan cannot be applied without further input, because a required value has no default or a secret must be supplied. + pub requires_interactive_configuration: bool, + /// Configuration scope and key the plan would write to. + pub target: McpPlanTarget, + /// Every eligible transport, so a host can present an explicit choice. A completed plan always has at least one; when none is eligible, planning returns `CatalogUnavailableTransportError` instead. + pub transport_choices: Vec, } -/// Model identifier and token limits used to compute the context-info breakdown. +/// Server name to check running status for. /// ///
/// @@ -7702,43 +8164,27 @@ 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. - #[serde(skip_serializing_if = "Option::is_none")] - pub selected_model: Option, +pub struct McpIsServerRunningRequest { + /// Name of the MCP server to check + pub server_name: String, } -/// Token-usage breakdown for the session's current context window +/// Whether the named MCP server is running. +/// +///
+/// +/// **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 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 struct McpIsServerRunningResult { + /// True if the server has an active client and transport. + pub running: bool, } -/// Token breakdown for the session's current context window, or null if uninitialized. +/// Server name whose tool list should be returned. /// ///
/// @@ -7748,12 +8194,12 @@ 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 McpListToolsRequest { + /// Name of the connected MCP server whose tools to list. + pub server_name: String, } -/// Indicates whether the local session is currently processing a turn or background continuation. +/// Normalized MCP Apps discovery metadata from a tool's `_meta.ui` block. /// ///
/// @@ -7763,12 +8209,16 @@ 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 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>, } -/// Model identifier to use when re-tokenizing the session's existing messages. +/// MCP tool metadata with tool name, optional description, and normalized MCP Apps discovery metadata. /// ///
/// @@ -7778,12 +8228,18 @@ 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 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, } -/// 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. +/// Tools exposed by the connected MCP server. Throws when the server is not connected. /// ///
/// @@ -7793,16 +8249,12 @@ 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 McpListToolsResult { + /// Tools exposed by the server. + pub tools: Vec, } -/// Updated working directory and git context. Emitted as the new payload of `session.context_changed`. +/// Identifies the MCP server whose persisted OAuth credentials were updated. /// ///
/// @@ -7812,33 +8264,38 @@ 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 +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 head_commit: Option, - /// Hosting platform type of the repository + 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 host_type: Option, - /// Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) + pub server_name: Option, +} + +#[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 repository: Option, - /// Raw host string from the git remote URL (e.g. "github.com", "dev.azure.com") + pub expires_in: Option, + /// OAuth response variant discriminator. + pub kind: McpOauthPendingRequestResponseTokenKind, + /// OAuth token type. Defaults to bearer when omitted. #[serde(skip_serializing_if = "Option::is_none")] - pub repository_host: Option, + pub token_type: Option, } -/// Updated working-directory/git context to record on the session. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct McpOauthPendingRequestResponseCancelled { + /// OAuth response variant discriminator. + pub kind: McpOauthPendingRequestResponseCancelledKind, +} + +/// Pending MCP OAuth request ID and host-provided token or cancellation response. /// ///
/// @@ -7846,14 +8303,16 @@ pub struct SessionWorkingDirectoryContext { /// 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 MetadataRecordContextChangeRequest { - /// Updated working directory and git context. Emitted as the new payload of `session.context_changed`. - pub context: SessionWorkingDirectoryContext, +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, } -/// 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 pending MCP OAuth response was accepted. /// ///
/// @@ -7863,9 +8322,12 @@ pub struct MetadataRecordContextChangeRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct MetadataRecordContextChangeResult {} +pub struct McpOauthHandlePendingResult { + /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. + pub success: bool, +} -/// 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. +/// Remote MCP server name and optional overrides controlling reauthentication, OAuth client display name, callback success-page copy, and static OAuth client selection. /// ///
/// @@ -7875,12 +8337,33 @@ 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 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, } -/// 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. +/// OAuth authorization URL the caller should open, or empty when cached tokens already authenticated the server. /// ///
/// @@ -7890,12 +8373,13 @@ 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 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, } -/// The repository the remote session targets. +/// Remote MCP server name for a passive OAuth status probe. /// ///
/// @@ -7905,16 +8389,56 @@ 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 McpOauthProbeRequest { + /// Name of the configured remote MCP server to probe. + pub server_name: String, } -/// Remote-session-specific metadata. Populated only when `isRemote` is true. Fields are immutable for the lifetime of the session. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct McpOauthProbeResultNoAuthRequired { + /// HTTP response returned by the server. + pub http_response: McpOauthHttpResponse, + /// Probe outcome variant discriminator. + pub status: McpOauthProbeResultNoAuthRequiredStatus, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct McpOauthProbeResultAuthenticated { + /// HTTP response returned by the server. + pub http_response: McpOauthHttpResponse, + /// Probe outcome variant discriminator. + pub status: McpOauthProbeResultAuthenticatedStatus, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct McpOauthProbeResultNeedsAuth { + /// HTTP 401 or 403 response returned by the server. + pub http_response: McpOauthHttpResponse, + /// Why authentication is needed. + pub reason: McpOauthProbeNeedsAuthReason, + /// Probe outcome variant discriminator. + pub status: McpOauthProbeResultNeedsAuthStatus, + /// Parsed WWW-Authenticate challenge parameters, when present and parseable. + #[serde(skip_serializing_if = "Option::is_none")] + pub www_authenticate_params: Option, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct McpOauthProbeResultFailed { + /// Human-readable probe failure detail. + pub error: String, + /// HTTP response returned by the server, when the probe reached the server and captured the complete response. + #[serde(skip_serializing_if = "Option::is_none")] + pub http_response: Option, + /// Probe outcome variant discriminator. + pub status: McpOauthProbeResultFailedStatus, +} + +/// Pending MCP OAuth request id to respond to. /// ///
/// @@ -7924,21 +8448,12 @@ 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. - #[serde(skip_serializing_if = "Option::is_none")] - pub task_type: Option, +pub struct McpOauthRespondRequest { + /// OAuth request identifier from the mcp.oauth_required event + pub request_id: RequestId, } -/// Active server-driven promotion for a model, including its discount and optional expiry. +/// Indicates whether the pending MCP OAuth response was accepted. /// ///
/// @@ -7948,22 +8463,12 @@ 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. 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, +pub struct McpOauthRespondResult { + /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. + pub success: bool, } -/// Long context tier pricing (available for models with extended context windows) +/// A computed MCP install plan. Nothing has been applied: the plan describes what installing would change, and the plan handle is what a later apply operation would consume. /// ///
/// @@ -7973,35 +8478,16 @@ 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. - #[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, +pub struct McpPlanInstallPlanned { + /// Discriminator: a plan was computed and nothing was changed + pub kind: McpPlanInstallPlannedKind, + /// Protocol version and capabilities the runtime honoured. + pub negotiated: CatalogNegotiatedContract, + /// The normalised plan. + pub plan: McpInstallPlan, } -/// Token-level pricing information for this model +/// Plan from a candidate returned by a previous catalog search. /// ///
/// @@ -8011,41 +8497,16 @@ pub struct ModelBillingTokenPricesLongContext { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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, +pub struct McpPlanInstallSourceCandidate { + /// Single-use candidate handle. Consumed by this call, so a replay of the same handle is rejected. + pub candidate_handle: String, + /// Discriminator: plan from a previously returned candidate + pub kind: McpPlanInstallSourceCandidateKind, + /// The runtime- or authority-minted `searchId` returned with the search that produced this candidate. A search implementation binds it to private candidate-handle context; a planning implementation must verify that context before returning a plan. The unavailable planning implementation in this contract layer validates presence but does not claim the verification has occurred. It identifies a search rather than a person and must never be joined with user identity to re-identify anyone. + pub search_id: String, } -/// Billing information +/// An MCP server card to be retrieved from a URL through the runtime's hardened fetch boundary. /// ///
/// @@ -8055,22 +8516,16 @@ 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. - #[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, +pub struct McpServerCardUrl { + /// Discriminator: the card is URL-backed, and carries no embedded data + pub kind: McpServerCardUrlKind, + /// Media type the card is expected to conform to. + pub media_type: McpServerCardMediaType, + /// Card URL. Retrieved only through the runtime's hardened boundary, with scheme, credential, address-range, redirect, timeout, and response-size controls applied. Never logged. + pub url: String, } -/// Vision-specific limits +/// An MCP server card supplied inline as an inert document. /// ///
/// @@ -8080,19 +8535,16 @@ 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, +pub struct McpServerCardEmbedded { + /// The card document verbatim, treated as inert untrusted bytes. The runtime parses and validates it; the host is not expected to interpret it. Never logged. + pub data: String, + /// Discriminator: the card is embedded, and carries no URL + pub kind: McpServerCardEmbeddedKind, + /// Media type the card is expected to conform to. + pub media_type: McpServerCardMediaType, } -/// Token limits for prompts, outputs, and context window +/// Plan from a card supplied directly by the caller, without a preceding search. /// ///
/// @@ -8100,27 +8552,16 @@ pub struct ModelCapabilitiesLimitsVision { /// 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 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 McpPlanInstallSourceCard { + /// The card to plan from: exactly one of a URL or embedded data. + pub card: McpServerCardReference, + /// Discriminator: plan from a caller-supplied card + pub kind: McpPlanInstallSourceCardKind, } -/// Feature flags indicating what the model supports +/// A side-effect-free request for an MCP install plan. Computing a plan never writes configuration, stores a secret, or reloads MCP servers. /// ///
/// @@ -8128,21 +8569,19 @@ pub struct ModelCapabilitiesLimits { /// 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 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 +pub struct McpPlanInstallRequest { + /// Protocol version and capabilities the caller requires. + pub contract: CatalogClientContract, + /// Configuration scope the plan targets. Defaults to user scope when omitted. #[serde(skip_serializing_if = "Option::is_none")] - pub vision: Option, + pub scope: Option, + /// What to plan: either a candidate handle from a previous search, or a card supplied directly. + pub source: McpPlanInstallSource, } -/// Model capabilities and limits +/// One non-secret scalar value a transport choice needs before it can be applied. /// ///
/// @@ -8152,16 +8591,31 @@ pub struct ModelCapabilitiesSupports { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ModelCapabilities { - /// Token limits for prompts, outputs, and context window +pub struct McpPlanRequiredValueScalar { + /// Where the value is applied when the server is launched. + pub category: McpPlanValueCategory, + /// Default supplied by the card, when the value can be resolved without input. Presence is the authoritative indication that a default exists. Inert untrusted data. #[serde(skip_serializing_if = "Option::is_none")] - pub limits: Option, - /// Feature flags indicating what the model supports + pub default_value: Option, + /// Human-readable explanation from the card. Inert untrusted text. #[serde(skip_serializing_if = "Option::is_none")] - pub supports: Option, + pub description: Option, + /// Whether the value may be supplied more than once. + pub is_repeated: bool, + /// Key the value is supplied under. Inert untrusted data. + pub key: String, + /// Discriminator: this required value uses a scalar type. + pub kind: McpPlanRequiredValueScalarKind, + /// Whether the value must be present for the plan to be applicable. + pub required: bool, + /// Human-readable label from the card. Inert untrusted text. + #[serde(skip_serializing_if = "Option::is_none")] + pub title: Option, + /// Scalar type the value must conform to. + pub value_type: McpPlanScalarValueType, } -/// Policy state (if applicable) +/// One enumerated non-secret value a transport choice needs before it can be applied. The permitted values are structurally required. /// ///
/// @@ -8171,15 +8625,33 @@ 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 McpPlanRequiredValueEnum { + /// Where the value is applied when the server is launched. + pub category: McpPlanValueCategory, + /// Default supplied by the card, when the value can be resolved without input. Presence is the authoritative indication that a default exists. Inert untrusted data. #[serde(skip_serializing_if = "Option::is_none")] - pub terms: Option, + pub default_value: Option, + /// Human-readable explanation from the card. Inert untrusted text. + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + /// Non-empty permitted value set. Inert untrusted data. + pub enum_values: Vec, + /// Whether the value may be supplied more than once. + pub is_repeated: bool, + /// Key the value is supplied under. Inert untrusted data. + pub key: String, + /// Discriminator: this required value uses a fixed enumeration. + pub kind: McpPlanRequiredValueEnumKind, + /// Whether the value must be present for the plan to be applicable. + pub required: bool, + /// Human-readable label from the card. Inert untrusted text. + #[serde(skip_serializing_if = "Option::is_none")] + pub title: Option, + /// Discriminator: the value must be one of `enumValues`. + pub value_type: McpPlanEnumValueType, } -/// Copilot model metadata, including identifier, display name, capabilities, policy, billing, reasoning efforts, and picker categories. +/// A secret a transport choice needs, referenced by placeholder. No secret value ever appears in a plan, and the placeholder resolves against the keychain only when a plan is applied. /// ///
/// @@ -8189,31 +8661,17 @@ 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, - /// 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) +pub struct McpPlanSecretPlaceholder { + /// Key the secret is supplied under. Inert untrusted data. + pub key: String, + /// The runtime-assigned `${secret:}` placeholder written into configuration in place of the value. + pub placeholder: String, + /// Human-readable label from the card. Inert untrusted text. #[serde(skip_serializing_if = "Option::is_none")] - pub supported_reasoning_efforts: Option>, + pub title: Option, } -/// Vision-specific limits +/// An eligible local-package transport choice. Package identity is required and a remote endpoint cannot be represented. /// ///
/// @@ -8223,25 +8681,24 @@ 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 struct McpPlanTransportChoicePackage { + /// Stable identifier for this choice within the plan, used to select it when the plan is applied. + pub choice_id: String, + /// Discriminator: this choice runs a local package + pub install_method: McpPlanPackageInstallMethod, + /// Package identifier. Inert untrusted data. + pub package_identifier: String, + /// Packaging ecosystem, for example `oci` or `npm`. + pub package_type: String, + /// Typed values this choice requires, excluding secrets. + pub required_values: Vec, + /// Secrets this choice requires, referenced by placeholder only. + pub secret_placeholders: Vec, + /// Local process transport this package choice would use. + pub transport: McpPlanPackageTransport, } -/// Token limits for prompts, outputs, and context window +/// An eligible remote-endpoint transport choice. The endpoint is required and package identity cannot be represented. /// ///
/// @@ -8251,25 +8708,22 @@ 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 McpPlanTransportChoiceRemote { + /// Stable identifier for this choice within the plan, used to select it when the plan is applied. + pub choice_id: String, + /// Endpoint URL. Inert untrusted data. + pub endpoint: String, + /// Discriminator: this choice connects to a remote endpoint + pub install_method: McpPlanRemoteInstallMethod, + /// Typed values this choice requires, excluding secrets. + pub required_values: Vec, + /// Secrets this choice requires, referenced by placeholder only. + pub secret_placeholders: Vec, + /// Endpoint transport this remote choice would use. + pub transport: McpPlanRemoteTransport, } -/// Feature flags indicating what the model supports +/// Registration parameters for an external MCP client. /// ///
/// @@ -8279,19 +8733,21 @@ pub struct ModelCapabilitiesOverrideLimits { ///
#[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(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, } -/// Optional capability overrides (vision, tool_calls, reasoning, etc.). +/// In-process MCP reload configuration. /// ///
/// @@ -8301,16 +8757,36 @@ pub struct ModelCapabilitiesOverrideSupports { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ModelCapabilitiesOverride { - /// Token limits for prompts, outputs, and context window +pub(crate) struct McpReloadConfig { #[serde(skip_serializing_if = "Option::is_none")] - pub limits: Option, - /// Feature flags indicating what the model supports + pub active_git_hub_token: Option, + /// Server names the CLI enabled for this session via `--enable-mcp-server`. #[serde(skip_serializing_if = "Option::is_none")] - pub supports: Option, + pub cli_enabled_servers: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub config_filter: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub disabled_servers: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub enabled_servers: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub force_restart: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub github_mcp_tool_options: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub github_mcp_user_override: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub include_workspace_sources: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub mcp3p_enabled: Option, + pub mcp_servers: HashMap, + #[serde(skip_serializing_if = "Option::is_none")] + pub secret_store: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub use_cached_tool_snapshots: Option, } -/// List of Copilot models available to the resolved user, including capabilities and billing metadata. +/// Opaque MCP reload configuration. /// ///
/// @@ -8320,12 +8796,13 @@ pub struct ModelCapabilitiesOverride { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ModelList { - /// List of available models with full metadata - pub models: Vec, +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, } -/// Optional listing options. +/// Indicates whether the auto-managed `github` MCP server was removed (false when nothing to remove). /// ///
/// @@ -8335,13 +8812,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 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, } -/// Reasoning effort level to apply to the currently selected model. +/// Standard MCP resource annotations plus preserved non-standard annotation fields. /// ///
/// @@ -8351,12 +8827,22 @@ 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 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, } -/// 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. +/// A resource icon descriptor plus preserved non-standard icon fields. /// ///
/// @@ -8366,12 +8852,24 @@ 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 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, } -/// Optional GitHub token used to list models for a specific user instead of the global auth context. +/// 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`. /// ///
/// @@ -8381,13 +8879,38 @@ 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 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 git_hub_token: Option, + 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, } -/// Target model identifier and optional reasoning effort, summary, capability overrides, and context tier. +/// MCP resource content with URI, optional MIME type, text or base64 blob, and resource metadata. /// ///
/// @@ -8397,30 +8920,24 @@ 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, - /// 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. +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 reasoning_effort: Option, - /// Reasoning summary mode to request for supported model clients + pub blob: Option, + /// MIME type of the content #[serde(skip_serializing_if = "Option::is_none")] - pub reasoning_summary: Option, - /// Output verbosity level to request for supported models + pub mime_type: Option, + /// Text content (e.g. HTML) #[serde(skip_serializing_if = "Option::is_none")] - pub verbosity: Option, + pub text: Option, + /// The resource URI + pub uri: String, } -/// The model identifier active on the session after the switch. +/// MCP server whose resources to enumerate. /// ///
/// @@ -8430,16 +8947,15 @@ pub struct ModelSwitchToRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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 +pub struct McpResourcesListRequest { + /// Opaque MCP pagination cursor from a prior `nextCursor` value #[serde(skip_serializing_if = "Option::is_none")] - pub model_id: Option, + pub cursor: Option, + /// Name of the MCP server whose resources to enumerate + pub server_name: String, } -/// Agent interaction mode to apply to the session. +/// One page of resources advertised by the named MCP server. /// ///
/// @@ -8449,12 +8965,15 @@ 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 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, } -/// Azure-specific provider options. +/// MCP server whose resource templates to enumerate. /// ///
/// @@ -8464,13 +8983,15 @@ 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. +pub struct McpResourcesListTemplatesRequest { + /// Opaque MCP pagination cursor from a prior `nextCursor` value #[serde(skip_serializing_if = "Option::is_none")] - pub api_version: Option, + pub cursor: Option, + /// Name of the MCP server whose resource templates to enumerate + pub server_name: String, } -/// A named BYOK provider connection (transport + credentials). +/// 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`. /// ///
/// @@ -8480,38 +9001,35 @@ pub struct ProviderConfigAzure { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct NamedProviderConfig { - /// API key. Optional for local providers like Ollama. +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 api_key: Option, - /// Azure-specific provider options. + pub additional_properties: Option>, + /// Model/client annotations associated with this template #[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 annotations: Option, + /// Optional description of what this template is for #[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 description: Option, + /// Icons associated with resources matching this template #[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 icons: Option>, + /// MIME type for resources matching this template, if uniform #[serde(skip_serializing_if = "Option::is_none")] - pub headers: Option>, - /// Stable identifier referenced by BYOK model definitions. Must not contain '/'. + pub mime_type: Option, + /// The programmatic name of the resource template 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". + /// Optional human-readable display title #[serde(skip_serializing_if = "Option::is_none")] - pub wire_api: Option, + pub title: Option, + /// An RFC 6570 URI template for constructing resource URIs + pub uri_template: String, } -/// The session's friendly name, or null when not yet set. +/// One page of resource templates advertised by the named MCP server. /// ///
/// @@ -8521,12 +9039,15 @@ 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 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, } -/// Auto-generated session summary to apply as the session's name when no user-set name exists. +/// MCP server and resource URI to fetch. /// ///
/// @@ -8536,12 +9057,14 @@ 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 McpResourcesReadRequest { + /// Name of the MCP server hosting the resource + pub server_name: String, + /// Resource URI + pub uri: String, } -/// Indicates whether the auto-generated summary was applied as the session's name. +/// Resource contents returned by the MCP server. /// ///
/// @@ -8551,12 +9074,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 McpResourcesReadResult { + /// Resource contents returned by the server + pub contents: Vec, } -/// New friendly name to apply to 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. /// ///
/// @@ -8566,12 +9089,15 @@ 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 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, } -/// Source descriptor for a `session.options.update` content-exclusion rule, with source name and type. +/// Per-field MCP telemetry-obfuscation policy. /// ///
/// @@ -8581,12 +9107,14 @@ pub struct NameSetRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct OptionsUpdateAdditionalContentExclusionPolicyRuleSource { - pub name: String, - pub r#type: String, +pub struct McpSafeForTelemetryFields { + /// Whether MCP tool input names may be included in telemetry without obfuscation. + pub inputs_names: bool, + /// Whether the MCP tool name may be included in telemetry without obfuscation. + pub name: bool, } -/// Single content-exclusion rule supplied to `session.options.update`, with paths, match conditions, and source. +/// Outcome of an MCP sampling execution: success result, failure error, or cancellation. /// ///
/// @@ -8596,17 +9124,18 @@ pub struct OptionsUpdateAdditionalContentExclusionPolicyRuleSource { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct OptionsUpdateAdditionalContentExclusionPolicyRule { +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 if_any_match: Option>, + 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 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 result: Option, } -/// Content-exclusion policy supplied to `session.options.update`, with rules, last-updated data, and scope. +/// MCP server status entry, including config source/plugin source and any connection error. /// ///
/// @@ -8616,32 +9145,26 @@ 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 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, stopped, or not_configured + pub status: McpServerStatus, } -/// List of pending permission requests reconstructed from event history. +/// Authentication settings with optional redirect port configuration. /// ///
/// @@ -8651,12 +9174,13 @@ 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 McpServerAuthConfigRedirectPort { + /// Fixed port for the OAuth redirect callback server. + #[serde(skip_serializing_if = "Option::is_none")] + pub redirect_port: Option, } -/// Permission-decision request variant to approve only the current permission request. +/// Remote MCP server configuration accessed over HTTP or SSE. /// ///
/// @@ -8666,15 +9190,90 @@ pub struct PendingPermissionRequestList { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionApproveOnce { - /// True only when a host surfaced this request to a user who approved it. +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 approved_interactively: Option, - /// Approve this single request only - pub kind: PermissionDecisionApproveOnceKind, + pub auth: Option, + /// Configuration warnings recorded while loading the server. + #[serde(skip_serializing_if = "Option::is_none")] + pub config_warnings: 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, + /// Whether secret masking is disabled for calls to this server. + #[serde(skip_serializing_if = "Option::is_none")] + pub disable_secret_masking: 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, + /// Optional human-readable server name. + #[serde(skip_serializing_if = "Option::is_none")] + pub display_name: Option, + /// Event types this server receives as Copilot notifications. + #[serde(skip_serializing_if = "Option::is_none")] + pub events: Option>, + /// Tool names excluded after the include filter is applied. + #[serde(skip_serializing_if = "Option::is_none")] + pub exclude_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>, + /// Dynamic-header refresh cache lifetime in milliseconds. + #[serde(skip_serializing_if = "Option::is_none")] + pub headers_refresh_ttl_ms: 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, + /// Copilot notification types this server may send to the host. + #[serde(skip_serializing_if = "Option::is_none")] + pub notifications: 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, + /// Telemetry-obfuscation policy for this server's tools. + #[serde(skip_serializing_if = "Option::is_none")] + pub safe_for_telemetry: Option, + /// The origin of this server configuration. + #[serde(skip_serializing_if = "Option::is_none")] + pub source: Option, + /// Source file path recorded while loading the config. + #[serde(skip_serializing_if = "Option::is_none")] + pub source_path: Option, + /// Plugin that provided this server. + #[serde(skip_serializing_if = "Option::is_none")] + pub source_plugin: Option, + /// Whether the providing plugin uses the Open Plugin Spec. + #[serde(skip_serializing_if = "Option::is_none")] + pub source_plugin_spec: Option, + /// Version of the plugin that provided this server. + #[serde(skip_serializing_if = "Option::is_none")] + pub source_plugin_version: Option, + /// Timeout in milliseconds for tool discovery and tool calls. + #[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, } -/// Session-scoped approval details for specific command identifiers. +/// In-process MCP server configuration used by embedded SDK clients. /// ///
/// @@ -8684,14 +9283,72 @@ pub struct PermissionDecisionApproveOnce { ///
#[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(crate) struct McpServerConfigMemory { + /// Configuration warnings recorded while loading the server. + #[serde(skip_serializing_if = "Option::is_none")] + pub config_warnings: Option>, + /// Controls whether tools can be loaded on demand. + #[serde(skip_serializing_if = "Option::is_none")] + pub defer_tools: Option, + /// Whether secret masking is disabled for calls to this server. + #[serde(skip_serializing_if = "Option::is_none")] + pub disable_secret_masking: Option, + /// Whether persisted tool snapshots are disabled. + #[serde(skip_serializing_if = "Option::is_none")] + pub disable_tool_cache: Option, + /// Optional human-readable server name. + #[serde(skip_serializing_if = "Option::is_none")] + pub display_name: Option, + /// Event types this server receives as Copilot notifications. + #[serde(skip_serializing_if = "Option::is_none")] + pub events: Option>, + /// Tool names excluded after the include filter is applied. + #[serde(skip_serializing_if = "Option::is_none")] + pub exclude_tools: Option>, + /// Content filtering mode to apply to this server's tools. + #[serde(skip_serializing_if = "Option::is_none")] + pub filter_mapping: Option, + /// Whether this server is a built-in fallback. + #[serde(skip_serializing_if = "Option::is_none")] + pub is_default_server: Option, + /// Copilot notification types this server may send to the host. + #[serde(skip_serializing_if = "Option::is_none")] + pub notifications: Option>, + /// Set to `true` to use default OIDC settings. + #[serde(skip_serializing_if = "Option::is_none")] + pub oidc: Option, + /// Telemetry-obfuscation policy for this server's tools. + #[serde(skip_serializing_if = "Option::is_none")] + pub safe_for_telemetry: Option, + /// In-process MCP server instance. This value cannot cross a JSON-RPC boundary. + #[doc(hidden)] + pub(crate) server_instance: serde_json::Value, + /// The origin of this server configuration. + #[serde(skip_serializing_if = "Option::is_none")] + pub source: Option, + /// Source file path recorded while loading the config. + #[serde(skip_serializing_if = "Option::is_none")] + pub source_path: Option, + /// Plugin that provided this server. + #[serde(skip_serializing_if = "Option::is_none")] + pub source_plugin: Option, + /// Whether the providing plugin uses the Open Plugin Spec. + #[serde(skip_serializing_if = "Option::is_none")] + pub source_plugin_spec: Option, + /// Version of the plugin that provided this server. + #[serde(skip_serializing_if = "Option::is_none")] + pub source_plugin_version: Option, + /// Timeout in milliseconds for tool discovery and tool calls. + #[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>, + #[doc(hidden)] + pub(crate) r#type: McpServerConfigMemoryType, } -/// Session-scoped approval details for read-only filesystem operations. +/// Stdio MCP server configuration launched as a child process. /// ///
/// @@ -8701,12 +9358,84 @@ 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 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, + /// Configuration warnings recorded while loading the server. + #[serde(skip_serializing_if = "Option::is_none")] + pub config_warnings: Option>, + /// 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, + /// Whether secret masking is disabled for calls to this server. + #[serde(skip_serializing_if = "Option::is_none")] + pub disable_secret_masking: 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, + /// Optional human-readable server name. + #[serde(skip_serializing_if = "Option::is_none")] + pub display_name: Option, + /// Environment variables to pass to the Stdio MCP server process. + #[serde(skip_serializing_if = "Option::is_none")] + pub env: Option>, + /// Event types this server receives as Copilot notifications. + #[serde(skip_serializing_if = "Option::is_none")] + pub events: Option>, + /// Tool names excluded after the include filter is applied. + #[serde(skip_serializing_if = "Option::is_none")] + pub exclude_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, + /// 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, + /// Copilot notification types this server may send to the host. + #[serde(skip_serializing_if = "Option::is_none")] + pub notifications: 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, + /// Telemetry-obfuscation policy for this server's tools. + #[serde(skip_serializing_if = "Option::is_none")] + pub safe_for_telemetry: Option, + /// The origin of this server configuration. + #[serde(skip_serializing_if = "Option::is_none")] + pub source: Option, + /// Source file path recorded while loading the config. + #[serde(skip_serializing_if = "Option::is_none")] + pub source_path: Option, + /// Plugin that provided this server. + #[serde(skip_serializing_if = "Option::is_none")] + pub source_plugin: Option, + /// Whether the providing plugin uses the Open Plugin Spec. + #[serde(skip_serializing_if = "Option::is_none")] + pub source_plugin_spec: Option, + /// Version of the plugin that provided this server. + #[serde(skip_serializing_if = "Option::is_none")] + pub source_plugin_version: Option, + /// Timeout in milliseconds for tool discovery and tool calls. + #[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>, + /// Local transport type. Defaults to stdio when omitted. + #[serde(skip_serializing_if = "Option::is_none")] + pub r#type: Option, } -/// Session-scoped approval details for filesystem write operations. +/// MCP servers configured for the session, with their connection status and host-level state. /// ///
/// @@ -8716,12 +9445,15 @@ 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 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, } -/// Session-scoped approval details for an MCP server tool, or all tools on the server when `toolName` is null. +/// Mode controlling how MCP server env values are resolved (`direct` or `indirect`). /// ///
/// @@ -8731,16 +9463,12 @@ 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 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, } -/// Session-scoped approval details for MCP sampling requests from a server. +/// Env-value mode recorded on the session after the update. /// ///
/// @@ -8750,14 +9478,12 @@ 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 McpSetEnvValueModeResult { + /// Mode recorded on the session after the update + pub mode: McpSetEnvValueModeDetails, } -/// Session-scoped approval details for writes to long-term memory. +/// 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. /// ///
/// @@ -8767,12 +9493,15 @@ 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 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 config: Option, + /// Name of the MCP server to start + pub server_name: String, } -/// Session-scoped approval details for a custom tool, keyed by tool name. +/// MCP server startup filtering result. /// ///
/// @@ -8782,14 +9511,18 @@ 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 McpStartServersResult { + /// Non-default servers allowed by policy + #[serde(skip_serializing_if = "Option::is_none")] + pub allowed_servers: Option>, + /// Servers whose connection attempt failed. + #[serde(skip_serializing_if = "Option::is_none")] + pub failed_servers: Option>, + /// Servers filtered out before startup + pub filtered_servers: Vec, } -/// Session-scoped approval details for extension-management operations, optionally narrowed by operation. +/// Server name for an individual MCP server stop. /// ///
/// @@ -8799,15 +9532,12 @@ 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. - #[serde(skip_serializing_if = "Option::is_none")] - pub operation: Option, -} - -/// Session-scoped factory approval, optionally narrowed by approval key. +pub struct McpStopServerRequest { + /// Name of the MCP server to stop + pub server_name: String, +} + +/// Metadata controlling an MCP task's lifetime. /// ///
/// @@ -8817,15 +9547,13 @@ pub struct PermissionDecisionApproveForSessionApprovalExtensionManagement { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionApproveForSessionApprovalFactory { - /// Optional factory operation name or canonical approval key; when omitted, the approval covers all factory operations. +pub struct McpTaskMetadata { + /// Task time-to-live. #[serde(skip_serializing_if = "Option::is_none")] - pub approval_key: Option, - /// Approval covering factory operations. - pub kind: PermissionDecisionApproveForSessionApprovalFactoryKind, + pub ttl: Option, } -/// Session-scoped approval details for an extension's permission-gated capability access, keyed by extension name. +/// Server name identifying the external client to remove. /// ///
/// @@ -8835,14 +9563,12 @@ pub struct PermissionDecisionApproveForSessionApprovalFactory { ///
#[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(crate) struct McpUnregisterExternalClientRequest { + /// Server name of the external client to unregister + pub server_name: String, } -/// Permission-decision request variant to approve for the rest of the session, with optional tool approval or URL domain. +/// Memory configuration for this session. /// ///
/// @@ -8852,18 +9578,85 @@ 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) +pub struct MemoryConfiguration { + /// Whether memory is enabled for the session. + pub enabled: bool, +} + +/// 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 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, +} + +/// 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 approval: Option, - /// URL domain to approve for the rest of the session (URL prompts only) + 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 domain: Option, - /// Approve and remember for the rest of the session - pub kind: PermissionDecisionApproveForSessionKind, + pub parent_id: Option, + /// Token count currently in context attributable to this entry. + pub tokens: i64, } -/// Location-scoped approval details for specific command identifiers. +/// 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 { + /// 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, +} + +/// Per-source attribution breakdown for the session's current context window, or null if uninitialized. /// ///
/// @@ -8873,14 +9666,12 @@ 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 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, } -/// Location-scoped approval details for read-only filesystem operations. +/// Parameters for the heaviest-messages query. /// ///
/// @@ -8890,12 +9681,13 @@ 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 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, } -/// Location-scoped approval details for filesystem write operations. +/// The heaviest individual messages in the session's context window, most-expensive first. /// ///
/// @@ -8905,12 +9697,14 @@ 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 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, } -/// Location-scoped approval details for an MCP server tool, or all tools on the server when `toolName` is null. +/// Model identifier and token limits used to compute the context-info breakdown. /// ///
/// @@ -8920,16 +9714,43 @@ 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 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 selected_model: Option, } -/// Location-scoped approval details for MCP sampling requests from a server. +/// 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. /// ///
/// @@ -8939,14 +9760,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 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, } -/// Location-scoped approval details for writes to long-term memory. +/// Indicates whether the local session is currently processing a turn or background continuation. /// ///
/// @@ -8956,12 +9775,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 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, } -/// Location-scoped approval details for a custom tool, keyed by tool name. +/// Model identifier to use when re-tokenizing the session's existing messages. /// ///
/// @@ -8971,14 +9790,12 @@ 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 MetadataRecomputeContextTokensRequest { + /// Model identifier used for tokenization. The runtime token-counts both chat-context and system-context messages against this model. + pub model_id: String, } -/// Location-scoped approval details for extension-management operations, optionally narrowed by operation. +/// 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. /// ///
/// @@ -8988,15 +9805,16 @@ 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. - #[serde(skip_serializing_if = "Option::is_none")] - pub operation: 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, } -/// Location-scoped factory approval, optionally narrowed by approval key. +/// Updated working directory and git context. Emitted as the new payload of `session.context_changed`. /// ///
/// @@ -9006,15 +9824,33 @@ pub struct PermissionDecisionApproveForLocationApprovalExtensionManagement { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionApproveForLocationApprovalFactory { - /// Optional factory operation name or canonical approval key; when omitted, the approval covers all factory operations. +pub struct SessionWorkingDirectoryContext { + /// Merge-base commit SHA (fork point from the remote default branch) #[serde(skip_serializing_if = "Option::is_none")] - pub approval_key: Option, - /// Approval covering factory operations. - pub kind: PermissionDecisionApproveForLocationApprovalFactoryKind, + 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") + #[serde(skip_serializing_if = "Option::is_none")] + pub repository_host: Option, } -/// Location-scoped approval details for an extension's permission-gated capability access, keyed by extension name. +/// Updated working-directory/git context to record on the session. /// ///
/// @@ -9024,14 +9860,12 @@ pub struct PermissionDecisionApproveForLocationApprovalFactory { ///
#[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 MetadataRecordContextChangeRequest { + /// Updated working directory and git context. Emitted as the new payload of `session.context_changed`. + pub context: SessionWorkingDirectoryContext, } -/// Permission-decision request variant to approve and persist a permission for a project location, with approval details and location key. +/// 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. /// ///
/// @@ -9039,18 +9873,11 @@ 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 MetadataRecordContextChangeResult {} -/// Permission-decision request variant to permanently approve a URL domain across sessions. +/// 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. /// ///
/// @@ -9060,14 +9887,12 @@ 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, -} - -/// Permission-decision request variant to reject a pending permission request, with optional feedback. +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, +} + +/// 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. /// ///
/// @@ -9077,15 +9902,12 @@ pub struct PermissionDecisionApprovePermanently { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionReject { - /// Optional feedback explaining the rejection - #[serde(skip_serializing_if = "Option::is_none")] - pub feedback: Option, - /// Reject the request - pub kind: PermissionDecisionRejectKind, +pub struct MetadataSetWorkingDirectoryResult { + /// Working directory after the update + pub working_directory: String, } -/// Permission-decision variant indicating no user was available to confirm the request. +/// The repository the remote session targets. /// ///
/// @@ -9095,12 +9917,16 @@ 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 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, } -/// Permission-decision variant indicating the request was approved. +/// Remote-session-specific metadata. Populated only when `isRemote` is true. Fields are immutable for the lifetime of the session. /// ///
/// @@ -9110,12 +9936,21 @@ 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 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. + #[serde(skip_serializing_if = "Option::is_none")] + pub task_type: Option, } -/// Permission-decision variant indicating approval was remembered for the session, with approval details. +/// Active server-driven promotion for a model, including its discount and optional expiry. /// ///
/// @@ -9123,16 +9958,27 @@ 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 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, + /// Whether the service asked hosts to give this promotion a prominent surface, such as a dedicated banner, in addition to listing it with the model. `true` requests that surface and `false` asks for the model list only. Absent means the service expressed no preference — for example a response that predates the field — so hosts should apply their own default rather than read it as `false`. + #[serde(skip_serializing_if = "Option::is_none")] + pub show_banner: Option, } -/// Permission-decision variant indicating approval was persisted for a project location, with approval details and location key. +/// Long context tier pricing (available for models with extended context windows) /// ///
/// @@ -9140,18 +9986,40 @@ 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 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 1-hour cache-write (cache creation) tokens. + #[serde(skip_serializing_if = "Option::is_none")] + pub cache_write1h_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 variant indicating the request was cancelled before use, with an optional reason. +/// Token-level pricing information for this model /// ///
/// @@ -9161,15 +10029,44 @@ 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 +pub struct ModelBillingTokenPrices { + /// Number of tokens per standard billing batch #[serde(skip_serializing_if = "Option::is_none")] - pub reason: Option, + 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 1-hour cache-write (cache creation) tokens. + #[serde(skip_serializing_if = "Option::is_none")] + pub cache_write1h_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, } -/// Permission-decision variant indicating explicit denial by permission rules, with the matching rules. +/// Billing information /// ///
/// @@ -9179,14 +10076,22 @@ 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 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, } -/// Permission-decision variant indicating no approval rule matched and user confirmation was unavailable. +/// Vision-specific limits /// ///
/// @@ -9196,12 +10101,19 @@ 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 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, } -/// Permission-decision variant indicating the user denied an interactive prompt, with optional feedback and force-reject flag. +/// Token limits for prompts, outputs, and context window /// ///
/// @@ -9211,18 +10123,25 @@ pub struct PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser { ///
#[derive(Debug, Clone, Default, 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 +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 force_reject: Option, - /// Denied by the user during an interactive prompt - pub kind: PermissionDecisionDeniedInteractivelyByUserKind, + pub vision: Option, } -/// Permission-decision variant indicating denial by content-exclusion policy, with path and message. +/// Feature flags indicating what the model supports /// ///
/// @@ -9232,16 +10151,19 @@ 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 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, } -/// Permission-decision variant indicating denial by a permission request hook, with optional message and interrupt flag. +/// Model capabilities and limits /// ///
/// @@ -9251,18 +10173,16 @@ pub struct PermissionDecisionDeniedByContentExclusionPolicy { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionDeniedByPermissionRequestHook { - /// Whether to interrupt the current agent turn +pub struct ModelCapabilities { + /// Token limits for prompts, outputs, and context window #[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 limits: Option, + /// Feature flags indicating what the model supports #[serde(skip_serializing_if = "Option::is_none")] - pub message: Option, + pub supports: Option, } -/// Pending permission request ID and the decision to apply (approve/reject and scope). +/// A service-published message about a model, carrying a stable machine-readable code alongside human-readable text. /// ///
/// @@ -9270,16 +10190,16 @@ pub struct PermissionDecisionDeniedByPermissionRequestHook { /// 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 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 struct ModelMessage { + /// Stable machine-readable identifier for the message, such as `client_version_deprecated`. Hosts can key custom presentation off this; unrecognized codes should fall back to displaying `message`. + pub code: String, + /// Human-readable message text intended for display to the user. + pub message: String, } -/// Location-persisted tool approval details for specific command identifiers. +/// Policy state (if applicable) /// ///
/// @@ -9289,14 +10209,15 @@ pub struct PermissionDecisionRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsLocationsAddToolApprovalDetailsCommands { - /// Command identifiers covered by this approval. - pub command_identifiers: Vec, - /// Approval scoped to specific command identifiers. - pub kind: PermissionsLocationsAddToolApprovalDetailsCommandsKind, -} - -/// Location-persisted tool approval details for read-only filesystem operations. +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, +} + +/// Service-published warning text that hosts should display when presenting a model. /// ///
/// @@ -9306,12 +10227,13 @@ pub struct PermissionsLocationsAddToolApprovalDetailsCommands { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsLocationsAddToolApprovalDetailsRead { - /// Approval covering read-only filesystem operations. - pub kind: PermissionsLocationsAddToolApprovalDetailsReadKind, +pub struct ModelWarningText { + /// Data-retention warning for the model. The text may contain Markdown links and should be rendered as Markdown when supported. + #[serde(skip_serializing_if = "Option::is_none")] + pub data_retention: Option, } -/// Location-persisted tool approval details for filesystem write operations. +/// Copilot model metadata, including identifier, display name, capabilities, policy, billing, reasoning efforts, and picker categories. /// ///
/// @@ -9321,12 +10243,46 @@ pub struct PermissionsLocationsAddToolApprovalDetailsRead { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsLocationsAddToolApprovalDetailsWrite { - /// Approval covering filesystem write operations. - pub kind: PermissionsLocationsAddToolApprovalDetailsWriteKind, +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, + /// Informational notices the service published for this model, such as an upcoming change or a recommended alternative. Present only when the service published at least one notice. Hosts should surface these without implying anything is wrong with the model. + #[serde(skip_serializing_if = "Option::is_none")] + pub info_messages: Option>, + /// 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, + /// Context-window tiers this model offers, when the provider advertises them independently of tiered token pricing. Copilot models carry their tiers in `billing.tokenPrices`; a provider that has no pricing to publish (an agent host reached over AHP, for example) declares them here instead, so the model picker can still offer the tier toggle. + #[serde(skip_serializing_if = "Option::is_none")] + pub supported_context_tiers: Option>, + /// Supported reasoning effort levels (only present if model supports reasoning effort) + #[serde(skip_serializing_if = "Option::is_none")] + pub supported_reasoning_efforts: Option>, + /// Warnings the service published for this model, such as a deprecated client version. Present only when the service published at least one warning. The model remains usable; hosts should surface these as advisory rather than blocking. + #[serde(skip_serializing_if = "Option::is_none")] + pub warning_messages: Option>, + /// Warning text the service requires hosts to surface for this model. Present only when the service published at least one warning. + #[serde(skip_serializing_if = "Option::is_none")] + pub warning_text: Option, } -/// Location-persisted tool approval details for an MCP server tool, or all tools when `toolName` is null. +/// Managed, repository, and CLI model overrides to overlay onto the session at startup. /// ///
/// @@ -9336,16 +10292,34 @@ pub struct PermissionsLocationsAddToolApprovalDetailsWrite { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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, +pub struct ModelApplyStartupOverlayRequest { + /// Model explicitly selected by the CLI, when provided. + #[serde(skip_serializing_if = "Option::is_none")] + pub cli_model: Option, + /// Whether the overlay is being applied while resuming a deferred session. + #[serde(skip_serializing_if = "Option::is_none")] + pub deferred_resume: Option, + /// Model required by device-managed policy, when configured. + #[serde(skip_serializing_if = "Option::is_none")] + pub device_managed_model: Option, + /// Startup default model from the enterprise policy helper, when configured. Weakest of the managed sources: it applies only when neither device nor server policy names a model, and an explicit user selection still wins. + #[serde(skip_serializing_if = "Option::is_none")] + pub policy_helper_model: Option, + /// Context tier selected by repository settings, when configured. + #[serde(skip_serializing_if = "Option::is_none")] + pub repo_context_tier: Option, + /// Model selected by repository settings, when configured. + #[serde(skip_serializing_if = "Option::is_none")] + pub repo_model: Option, + /// Reasoning effort selected by repository settings, when configured. + #[serde(skip_serializing_if = "Option::is_none")] + pub repo_reasoning_effort: Option, + /// Model required by server-managed policy, when configured. + #[serde(skip_serializing_if = "Option::is_none")] + pub server_managed_model: Option, } -/// Location-persisted tool approval details for MCP sampling requests from a server. +/// Vision-specific limits /// ///
/// @@ -9355,14 +10329,25 @@ pub struct PermissionsLocationsAddToolApprovalDetailsMcp { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsLocationsAddToolApprovalDetailsMcpSampling { - /// Approval covering MCP sampling requests for a server. - pub kind: PermissionsLocationsAddToolApprovalDetailsMcpSamplingKind, - /// MCP server name. - pub server_name: String, +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>, } -/// Location-persisted tool approval details for writes to long-term memory. +/// Token limits for prompts, outputs, and context window /// ///
/// @@ -9372,12 +10357,25 @@ pub struct PermissionsLocationsAddToolApprovalDetailsMcpSampling { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsLocationsAddToolApprovalDetailsMemory { - /// Approval covering writes to long-term memory. - pub kind: PermissionsLocationsAddToolApprovalDetailsMemoryKind, +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, } -/// Location-persisted tool approval details for a custom tool, keyed by tool name. +/// Feature flags indicating what the model supports /// ///
/// @@ -9387,14 +10385,19 @@ pub struct PermissionsLocationsAddToolApprovalDetailsMemory { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsLocationsAddToolApprovalDetailsCustomTool { - /// Approval covering a custom tool. - pub kind: PermissionsLocationsAddToolApprovalDetailsCustomToolKind, - /// Custom tool name. - pub tool_name: String, +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-persisted tool approval details for extension-management operations, optionally narrowed by operation. +/// Optional capability overrides (vision, tool_calls, reasoning, etc.). /// ///
/// @@ -9404,15 +10407,16 @@ pub struct PermissionsLocationsAddToolApprovalDetailsCustomTool { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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. +pub struct ModelCapabilitiesOverride { + /// Token limits for prompts, outputs, and context window #[serde(skip_serializing_if = "Option::is_none")] - pub operation: Option, + pub limits: Option, + /// Feature flags indicating what the model supports + #[serde(skip_serializing_if = "Option::is_none")] + pub supports: Option, } -/// Location-persisted factory approval, optionally narrowed by approval key. +/// List of Copilot models available to the resolved user, including capabilities and billing metadata. /// ///
/// @@ -9422,15 +10426,12 @@ pub struct PermissionsLocationsAddToolApprovalDetailsExtensionManagement { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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 approval_key: Option, - /// Approval covering factory operations. - pub kind: PermissionsLocationsAddToolApprovalDetailsFactoryKind, +pub struct ModelList { + /// List of available models with full metadata + pub models: Vec, } -/// Location-persisted tool approval details for an extension's permission-gated capability access, keyed by extension name. +/// Optional listing options. /// ///
/// @@ -9440,14 +10441,13 @@ pub struct PermissionsLocationsAddToolApprovalDetailsFactory { ///
#[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 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 tool approval to persist. +/// Filesystem and environment context used to resolve model-picker settings. /// ///
/// @@ -9455,16 +10455,18 @@ 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 ModelPickerSettingsContext { + /// Optional Copilot configuration directory containing persisted settings. + #[serde(skip_serializing_if = "Option::is_none")] + pub config_dir: Option, + /// Environment variables consulted while resolving model-picker settings. + pub environment: serde_json::Value, + /// User home directory used when resolving persisted settings. + pub home_directory: String, } -/// Working directory to load persisted location permissions for. /// ///
/// @@ -9474,12 +10476,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 ModelPickerPersistenceRequest { + /// Whether context tier was explicitly selected and should be persisted. + #[serde(skip_serializing_if = "Option::is_none")] + pub context_tier_explicit: Option, + /// Whether reasoning effort was explicitly selected and should be persisted. + #[serde(skip_serializing_if = "Option::is_none")] + pub reasoning_effort_explicit: Option, + /// Filesystem and environment context used to resolve settings persistence. + pub settings_context: ModelPickerSettingsContext, } -/// Summary of persisted location permissions applied to the session. +/// Reasoning effort level to apply to the currently selected model. /// ///
/// @@ -9489,22 +10497,12 @@ 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 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, } -/// Working directory to resolve into a location-permissions key. +/// 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. /// ///
/// @@ -9514,12 +10512,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 ModelSetReasoningEffortResult { + /// Reasoning effort level recorded on the session after the update + pub reasoning_effort: String, } -/// Resolved location-permissions key and type. +/// Optional opaque account selection or compatibility GitHub token used to list models. /// ///
/// @@ -9529,14 +10527,15 @@ 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 ModelsListRequest { + /// GitHub token accepted for compatibility with existing SDK clients. When provided, resolves this token instead of using the current account. + #[serde(skip_serializing_if = "Option::is_none")] + pub git_hub_token: Option, + /// Opaque account identifier returned by `account.getAllUsers`. When omitted, the current account is used. + #[serde(skip_serializing_if = "Option::is_none")] + pub selection_id: Option, } -/// Directory path to add to the session's allowed directories. /// ///
/// @@ -9546,12 +10545,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 ModelSwitchConfirmation { + /// Current conversation token count before switching models. + pub current_tokens: f64, + /// Target model token limit used by the compaction preflight. + pub target_limit: f64, + /// Display name of the model that requires compaction confirmation. + pub target_model_display_name: String, } -/// Path to evaluate against the session's allowed directories. +/// Target model identifier and optional reasoning effort, summary, capability overrides, and context tier. /// ///
/// @@ -9561,12 +10564,51 @@ 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 ModelSwitchToRequest { + /// Explicit response to a model-switch compaction preflight. Omit to request a confirmation projection when compaction is necessary. + #[serde(skip_serializing_if = "Option::is_none")] + pub compaction_decision: Option, + /// 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, + /// 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, + /// Settings scope used when persisting the selected model. + #[serde(skip_serializing_if = "Option::is_none")] + pub model_change_scope: 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, + /// Optional settings context and explicit-override flags used to persist a picker selection. + #[serde(skip_serializing_if = "Option::is_none")] + pub picker_persistence: Option, + /// 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, + /// Optional repository settings scope to persist after the switch commits. + #[serde(skip_serializing_if = "Option::is_none")] + pub repo_scope: Option, + /// Require the target to be currently available and enabled before applying the switch. + #[serde(skip_serializing_if = "Option::is_none")] + pub require_available: Option, + /// When true, evaluate context-window compaction policy before applying the switch. + #[serde(skip_serializing_if = "Option::is_none")] + pub run_compaction_preflight: Option, + /// Origin to record on the effective `session.model_change` event for trusted in-process calls. Transport SDK calls are always recorded as `sdk`, regardless of this value. + #[serde(skip_serializing_if = "Option::is_none")] + pub source: Option, + /// Output verbosity level to request for supported models + #[serde(skip_serializing_if = "Option::is_none")] + pub verbosity: Option, } -/// Indicates whether the supplied path is within the session's allowed directories. +/// The model identifier active on the session after the switch. /// ///
/// @@ -9576,14 +10618,36 @@ 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, -} - -/// 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. -/// -///
+pub struct ModelSwitchToResult { + /// Compaction confirmation projection when status is confirmation_required + #[serde(skip_serializing_if = "Option::is_none")] + pub confirmation: Option, + /// 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, + /// Deprecation warnings associated with the selected model or options. + #[serde(skip_serializing_if = "Option::is_none")] + pub deprecation_warnings: Option>, + /// User-facing outcome message for the model switch. + #[serde(skip_serializing_if = "Option::is_none")] + pub message: Option, + /// Currently active model identifier after the switch + #[serde(skip_serializing_if = "Option::is_none")] + pub model_id: Option, + /// Persistence failure encountered after applying the model switch. + #[serde(skip_serializing_if = "Option::is_none")] + pub persistence_error: Option, + /// Lifecycle result for the requested switch + #[serde(skip_serializing_if = "Option::is_none")] + pub status: Option, + /// User-facing warning produced while applying the model switch. + #[serde(skip_serializing_if = "Option::is_none")] + pub warning: Option, +} + +/// Agent interaction mode to apply 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. @@ -9591,22 +10655,42 @@ 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). +pub struct ModeSetRequest { + /// Explicit response to a model-switch compaction preflight. #[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. + pub compaction_decision: Option, + /// Session whose plan-mode base state should be inherited. #[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. + pub inherit_plan_base_from_session_id: Option, + /// The session mode the agent is operating in + pub mode: SessionMode, + /// Whether the selected plan model should be persisted. #[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. + pub persist_plan_selection: Option, + /// Settings context used when persisting the selected plan model. #[serde(skip_serializing_if = "Option::is_none")] - pub workspace_path: Option, + pub picker_settings_context: Option, + /// Context tier to use with the dedicated plan model. + #[serde(skip_serializing_if = "Option::is_none")] + pub plan_context_tier: Option, + /// Action to perform when leaving plan mode. + #[serde(skip_serializing_if = "Option::is_none")] + pub plan_exit_action: Option, + /// Dedicated model to use in plan mode, when configured. + #[serde(skip_serializing_if = "Option::is_none")] + pub plan_model: Option, + /// Whether a dedicated plan model is configured. + #[serde(skip_serializing_if = "Option::is_none")] + pub plan_model_configured: Option, + /// Reasoning effort to use with the dedicated plan model. + #[serde(skip_serializing_if = "Option::is_none")] + pub plan_reasoning_effort: Option, + /// Whether leaving plan mode should restore the session's previous model. + #[serde(skip_serializing_if = "Option::is_none")] + pub restore_plan_model: Option, } -/// Snapshot of the session's allow-listed directories and primary working directory. +/// Outcome of a session mode change, including any model switch it triggered and follow-up the host must perform. /// ///
/// @@ -9616,14 +10700,32 @@ 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 ModeSetResult { + /// Whether the host should arm an interactive continuation after the mode change. + #[serde(skip_serializing_if = "Option::is_none")] + pub arm_interactive_continuation: Option, + /// Compaction confirmation required before the mode change can complete. + #[serde(skip_serializing_if = "Option::is_none")] + pub confirmation: Option, + /// Whether the host must defer implementing the requested mode change. + #[serde(skip_serializing_if = "Option::is_none")] + pub defer_implementation: Option, + /// Deprecation warnings associated with the model selected by the mode change. + #[serde(skip_serializing_if = "Option::is_none")] + pub deprecation_warnings: Option>, + /// User-facing outcome message for the model switch triggered by the mode change. + #[serde(skip_serializing_if = "Option::is_none")] + pub message: Option, + /// Whether applying the mode changed the active model. + pub model_changed: bool, + /// Lifecycle status of the requested mode change. + pub status: String, + /// User-facing warning produced while applying the mode change. + #[serde(skip_serializing_if = "Option::is_none")] + pub warning: Option, } -/// Directory path to set as the session's new primary working directory. +/// Result of moving in-flight MCP loading to the background. /// ///
/// @@ -9633,12 +10735,12 @@ 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 MoveMcpLoadingToBackgroundResult { + /// Whether an in-flight MCP load was moved to the background, releasing turns that were waiting on it. False when no MCP load was in flight or the waiting turns had already been released. + pub moved_to_background: bool, } -/// Path to evaluate against the session's workspace (primary) directory. +/// Azure-specific provider options. /// ///
/// @@ -9648,12 +10750,13 @@ 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 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, } -/// Indicates whether the supplied path is within the session's workspace directory. +/// External SDK input for a named custom model provider. Ingested by the native protocol boundary before host dispatch. /// ///
/// @@ -9663,12 +10766,38 @@ pub struct PermissionPathsWorkspaceCheckParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionPathsWorkspaceCheckResult { - /// Whether the path is within the session workspace directory - pub allowed: bool, +pub struct NamedProviderConfig { + /// Static API key used to authenticate provider requests. + #[serde(skip_serializing_if = "Option::is_none")] + pub api_key: Option, + /// Azure authentication configuration for the provider. + #[serde(skip_serializing_if = "Option::is_none")] + pub azure: Option, + /// Base URL for provider API requests. + pub base_url: String, + /// Static bearer token used to authenticate provider requests. + #[serde(skip_serializing_if = "Option::is_none")] + pub bearer_token: Option, + /// Whether the host supplies bearer tokens dynamically. + #[serde(skip_serializing_if = "Option::is_none")] + pub has_bearer_token_provider: Option, + /// Additional HTTP headers included with provider requests. + #[serde(skip_serializing_if = "Option::is_none")] + pub headers: Option>, + /// Unique provider name used to qualify model selection IDs. + pub name: String, + /// Transport used to communicate with the provider. + #[serde(skip_serializing_if = "Option::is_none")] + pub transport: Option, + /// Provider protocol family. + #[serde(skip_serializing_if = "Option::is_none")] + pub r#type: Option, + /// Wire API used to communicate with the provider. + #[serde(skip_serializing_if = "Option::is_none")] + pub wire_api: Option, } -/// Notification payload describing the permission prompt that the client just rendered. +/// The session's friendly name, or null when not yet set. /// ///
/// @@ -9678,12 +10807,12 @@ 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 NameGetResult { + /// The session name (user-set or auto-generated), or null if not yet set + pub name: Option, } -/// Indicates whether the permission decision was applied; false when the request was already resolved. +/// Auto-generated session summary to apply as the session's name when no user-set name exists. /// ///
/// @@ -9693,12 +10822,12 @@ 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 NameSetAutoRequest { + /// Auto-generated session summary. Empty/whitespace-only values are ignored; values are trimmed before persisting. + pub summary: String, } -/// If specified, replaces the session's approved/denied permission rules. Omit to leave the current rules unchanged. +/// Indicates whether the auto-generated summary was applied as the session's name. /// ///
/// @@ -9708,14 +10837,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 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, } -/// Source descriptor for a `session.permissions.configure` content-exclusion rule, with source name and type. +/// New friendly name to apply to the session. /// ///
/// @@ -9725,12 +10852,29 @@ pub struct PermissionRulesSet { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsConfigureAdditionalContentExclusionPolicyRuleSource { +pub struct NameSetRequest { + /// New session name (1–100 characters, trimmed of leading/trailing whitespace) + pub name: String, +} + +/// Source descriptor for a `session.options.update` 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 OptionsUpdateAdditionalContentExclusionPolicyRuleSource { + /// Name of the policy source. pub name: String, + /// Type of the policy source. pub r#type: String, } -/// Single content-exclusion rule supplied to `session.permissions.configure`, with paths, match conditions, and source. +/// Single content-exclusion rule supplied to `session.options.update`, with paths, match conditions, and source. /// ///
/// @@ -9740,17 +10884,20 @@ pub struct PermissionsConfigureAdditionalContentExclusionPolicyRuleSource { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsConfigureAdditionalContentExclusionPolicyRule { +pub struct OptionsUpdateAdditionalContentExclusionPolicyRule { + /// Conditions of which at least one must match. #[serde(skip_serializing_if = "Option::is_none")] pub if_any_match: Option>, + /// Conditions none of which may match. #[serde(skip_serializing_if = "Option::is_none")] pub if_none_match: Option>, + /// Path patterns covered by this rule. pub paths: Vec, - /// Source descriptor for a `session.permissions.configure` content-exclusion rule, with source name and type. - pub source: PermissionsConfigureAdditionalContentExclusionPolicyRuleSource, + /// Source descriptor for a `session.options.update` content-exclusion rule, with source name and type. + pub source: OptionsUpdateAdditionalContentExclusionPolicyRuleSource, } -/// Content-exclusion policy supplied to `session.permissions.configure`, with rules, last-updated data, and scope. +/// Content-exclusion policy supplied to `session.options.update`, with rules, last-updated data, and scope. /// ///
/// @@ -9760,15 +10907,17 @@ pub struct PermissionsConfigureAdditionalContentExclusionPolicyRule { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsConfigureAdditionalContentExclusionPolicy { +pub struct OptionsUpdateAdditionalContentExclusionPolicy { + /// Opaque policy update timestamp supplied by the host. #[serde(rename = "last_updated_at")] pub last_updated_at: serde_json::Value, - pub rules: Vec, - /// Allowed values for the `PermissionsConfigureAdditionalContentExclusionPolicyScope` enumeration. - pub scope: PermissionsConfigureAdditionalContentExclusionPolicyScope, + /// Content-exclusion rules to apply. + pub rules: Vec, + /// Allowed values for the `OptionsUpdateAdditionalContentExclusionPolicyScope` enumeration. + pub scope: OptionsUpdateAdditionalContentExclusionPolicyScope, } -/// 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. +/// Pending permission prompt reconstructed from event history, with request ID and user-facing prompt details. /// ///
/// @@ -9776,18 +10925,16 @@ pub struct PermissionsConfigureAdditionalContentExclusionPolicy { /// 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 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. - #[serde(skip_serializing_if = "Option::is_none")] - pub unrestricted: Option, +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, } -/// Patch of permission policy fields to apply (omit a field to leave it unchanged). +/// List of pending permission requests reconstructed from event history. /// ///
/// @@ -9797,29 +10944,12 @@ 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 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, } -/// Indicates whether the operation succeeded. +/// Permission-decision request variant to approve only the current permission request. /// ///
/// @@ -9829,12 +10959,15 @@ 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 PermissionDecisionApproveOnce { + /// True only when a host surfaced this request to a user who approved it. + #[serde(skip_serializing_if = "Option::is_none")] + pub approved_interactively: Option, + /// Approve this single request only + pub kind: PermissionDecisionApproveOnceKind, } -/// Indicates whether the operation succeeded. +/// Session-scoped approval details for specific command identifiers. /// ///
/// @@ -9844,12 +10977,14 @@ 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 PermissionDecisionApproveForSessionApprovalCommands { + /// Command identifiers covered by this approval. + pub command_identifiers: Vec, + /// Approval scoped to specific command identifiers. + pub kind: PermissionDecisionApproveForSessionApprovalCommandsKind, } -/// No parameters. +/// Session-scoped approval details for read-only filesystem operations. /// ///
/// @@ -9859,9 +10994,12 @@ pub struct PermissionsFolderTrustAddTrustedResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsGetAllowAllRequest {} +pub struct PermissionDecisionApproveForSessionApprovalRead { + /// Approval covering read-only filesystem operations. + pub kind: PermissionDecisionApproveForSessionApprovalReadKind, +} -/// Indicates whether the operation succeeded. +/// Session-scoped approval details for filesystem write operations. /// ///
/// @@ -9871,12 +11009,12 @@ 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 PermissionDecisionApproveForSessionApprovalWrite { + /// Approval covering filesystem write operations. + pub kind: PermissionDecisionApproveForSessionApprovalWriteKind, } -/// Scope and add/remove instructions for modifying session- or location-scoped permission rules. +/// Session-scoped approval details for an MCP server tool, or all tools on the server when `toolName` is null. /// ///
/// @@ -9886,21 +11024,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 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, } -/// Indicates whether the operation succeeded. +/// Session-scoped approval details for MCP sampling requests from a server. /// ///
/// @@ -9910,12 +11043,14 @@ pub struct PermissionsModifyRulesParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsModifyRulesResult { - /// Whether the operation succeeded - pub success: bool, -} - -/// Indicates whether the operation succeeded. +pub struct PermissionDecisionApproveForSessionApprovalMcpSampling { + /// Approval covering MCP sampling requests for a server. + pub kind: PermissionDecisionApproveForSessionApprovalMcpSamplingKind, + /// MCP server name. + pub server_name: String, +} + +/// Session-scoped approval details for writes to long-term memory. /// ///
/// @@ -9925,12 +11060,12 @@ 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 PermissionDecisionApproveForSessionApprovalMemory { + /// Approval covering writes to long-term memory. + pub kind: PermissionDecisionApproveForSessionApprovalMemoryKind, } -/// Indicates whether the operation succeeded. +/// Session-scoped approval details for a custom tool, keyed by tool name. /// ///
/// @@ -9940,12 +11075,14 @@ 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 PermissionDecisionApproveForSessionApprovalCustomTool { + /// Approval covering a custom tool. + pub kind: PermissionDecisionApproveForSessionApprovalCustomToolKind, + /// Custom tool name. + pub tool_name: String, } -/// No parameters; returns the session's allow-listed directories. +/// Session-scoped approval details for extension-management operations, optionally narrowed by operation. /// ///
/// @@ -9955,9 +11092,15 @@ pub struct PermissionsPathsAddResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsPathsListRequest {} +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. + #[serde(skip_serializing_if = "Option::is_none")] + pub operation: Option, +} -/// Indicates whether the operation succeeded. +/// Session-scoped factory approval, optionally narrowed by approval key. /// ///
/// @@ -9967,12 +11110,15 @@ 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 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, } -/// No parameters; returns currently-pending permission requests for the session. +/// Session-scoped approval details for an extension's permission-gated capability access, keyed by extension name. /// ///
/// @@ -9982,9 +11128,14 @@ pub struct PermissionsPathsUpdatePrimaryResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsPendingRequestsRequest {} +pub struct PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess { + /// Extension name. + pub extension_name: String, + /// Approval covering an extension's request to access a permission-gated capability. + pub kind: PermissionDecisionApproveForSessionApprovalExtensionPermissionAccessKind, +} -/// Clears session-scoped tool permission approvals, and optionally the location-scoped ones. +/// Session-scoped approval details for an extension's access to sensitive environment variables, keyed by extension name and the exact set of variable names. /// ///
/// @@ -9994,13 +11145,16 @@ pub struct PermissionsPendingRequestsRequest {} ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsResetSessionApprovalsRequest { - /// Whether location-scoped approvals are cleared too. Defaults to `true`. - #[serde(skip_serializing_if = "Option::is_none")] - pub include_location: Option, +pub struct PermissionDecisionApproveForSessionApprovalExtensionEnvAccess { + /// Names of the sensitive environment variables this approval covers. Values are never persisted. + pub environment_variables: Vec, + /// Extension name. + pub extension_name: String, + /// Approval covering an extension's request to read sensitive environment variables. + pub kind: PermissionDecisionApproveForSessionApprovalExtensionEnvAccessKind, } -/// Indicates whether the operation succeeded. +/// Permission-decision request variant to approve for the rest of the session, with optional tool approval or URL domain. /// ///
/// @@ -10010,12 +11164,18 @@ 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 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, } -/// Allow-all mode to apply for the session. +/// Location-scoped approval details for specific command identifiers. /// ///
/// @@ -10025,22 +11185,14 @@ 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 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 model: Option, - /// Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers. - #[serde(skip_serializing_if = "Option::is_none")] - pub source: Option, +pub struct PermissionDecisionApproveForLocationApprovalCommands { + /// Command identifiers covered by this approval. + pub command_identifiers: Vec, + /// Approval scoped to specific command identifiers. + pub kind: PermissionDecisionApproveForLocationApprovalCommandsKind, } -/// Allow-all toggle for tool permission requests, with an optional telemetry source. +/// Location-scoped approval details for read-only filesystem operations. /// ///
/// @@ -10050,15 +11202,12 @@ 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. - #[serde(skip_serializing_if = "Option::is_none")] - pub source: Option, +pub struct PermissionDecisionApproveForLocationApprovalRead { + /// Approval covering read-only filesystem operations. + pub kind: PermissionDecisionApproveForLocationApprovalReadKind, } -/// Indicates whether the operation succeeded. +/// Location-scoped approval details for filesystem write operations. /// ///
/// @@ -10068,12 +11217,12 @@ pub struct PermissionsSetApproveAllRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsSetApproveAllResult { - /// Whether the operation succeeded - pub success: bool, +pub struct PermissionDecisionApproveForLocationApprovalWrite { + /// Approval covering filesystem write operations. + pub kind: PermissionDecisionApproveForLocationApprovalWriteKind, } -/// Toggles whether permission prompts should be bridged into session events for this client. +/// Location-scoped approval details for an MCP server tool, or all tools on the server when `toolName` is null. /// ///
/// @@ -10083,12 +11232,16 @@ pub struct PermissionsSetApproveAllResult { ///
#[derive(Debug, Clone, Default, 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 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, } -/// Indicates whether the operation succeeded. +/// Location-scoped approval details for MCP sampling requests from a server. /// ///
/// @@ -10098,12 +11251,14 @@ 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 PermissionDecisionApproveForLocationApprovalMcpSampling { + /// Approval covering MCP sampling requests for a server. + pub kind: PermissionDecisionApproveForLocationApprovalMcpSamplingKind, + /// MCP server name. + pub server_name: String, } -/// Indicates whether the operation succeeded. +/// Location-scoped approval details for writes to long-term memory. /// ///
/// @@ -10113,12 +11268,12 @@ 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 PermissionDecisionApproveForLocationApprovalMemory { + /// Approval covering writes to long-term memory. + pub kind: PermissionDecisionApproveForLocationApprovalMemoryKind, } -/// Whether the URL-permission policy should run in unrestricted mode. +/// Location-scoped approval details for a custom tool, keyed by tool name. /// ///
/// @@ -10128,12 +11283,14 @@ 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 PermissionDecisionApproveForLocationApprovalCustomTool { + /// Approval covering a custom tool. + pub kind: PermissionDecisionApproveForLocationApprovalCustomToolKind, + /// Custom tool name. + pub tool_name: String, } -/// Optional message to echo back to the caller. +/// Location-scoped approval details for extension-management operations, optionally narrowed by operation. /// ///
/// @@ -10143,13 +11300,15 @@ pub struct PermissionUrlsSetUnrestrictedModeParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PingRequest { - /// Optional message to echo back +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 message: Option, + pub operation: Option, } -/// Server liveness response, including the echoed message, current server timestamp, and protocol version. +/// Location-scoped factory approval, optionally narrowed by approval key. /// ///
/// @@ -10159,16 +11318,15 @@ 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 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, } -/// Existence, contents, and resolved path of the session plan file. +/// Location-scoped approval details for an extension's permission-gated capability access, keyed by extension name. /// ///
/// @@ -10178,16 +11336,14 @@ 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 PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess { + /// Extension name. + pub extension_name: String, + /// Approval covering an extension's request to access a permission-gated capability. + pub kind: PermissionDecisionApproveForLocationApprovalExtensionPermissionAccessKind, } -/// 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. +/// Location-scoped approval details for an extension's access to sensitive environment variables, keyed by extension name and the exact set of variable names. /// ///
/// @@ -10197,22 +11353,16 @@ 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 PermissionDecisionApproveForLocationApprovalExtensionEnvAccess { + /// Names of the sensitive environment variables this approval covers. Values are never persisted. + pub environment_variables: Vec, + /// Extension name. + pub extension_name: String, + /// Approval covering an extension's request to read sensitive environment variables. + pub kind: PermissionDecisionApproveForLocationApprovalExtensionEnvAccessKind, } -/// Todo rows read from the session SQL database. Empty when no session database is available. +/// Permission-decision request variant to approve and persist a permission for a project location, with approval details and location key. /// ///
/// @@ -10220,14 +11370,18 @@ pub struct PlanSqlTodosRow { /// 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 PlanReadSqlTodosResult { - /// Rows from the session SQL todos table, ordered by creation time and id. - pub rows: Vec, +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, } -/// A single dependency edge read from the session SQL `todo_deps` table, indicating that one todo must complete before another. +/// Permission-decision request variant to permanently approve a URL domain across sessions. /// ///
/// @@ -10237,14 +11391,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 PermissionDecisionApprovePermanently { + /// URL domain to approve permanently + pub domain: String, + /// Approve and persist across sessions (URL prompts only) + pub kind: PermissionDecisionApprovePermanentlyKind, } -/// Todo rows + dependency edges read from the session SQL database. +/// Permission-decision request variant to reject a pending permission request, with optional feedback. /// ///
/// @@ -10254,14 +11408,15 @@ 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 PermissionDecisionReject { + /// Optional feedback explaining the rejection + #[serde(skip_serializing_if = "Option::is_none")] + pub feedback: Option, + /// Reject the request + pub kind: PermissionDecisionRejectKind, } -/// Replacement contents to write to the session plan file. +/// Permission-decision variant indicating no user was available to confirm the request. /// ///
/// @@ -10271,12 +11426,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 PermissionDecisionUserNotAvailable { + /// No user is available to confirm the request + pub kind: PermissionDecisionUserNotAvailableKind, } -/// Session plugin metadata, with name, marketplace, optional version, and enabled state. +/// Permission-decision variant indicating the request was approved. /// ///
/// @@ -10286,19 +11441,48 @@ 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 PermissionDecisionApproved { + /// The permission request was approved + pub kind: PermissionDecisionApprovedKind, } -/// Result of installing a plugin. +/// Permission-decision variant indicating approval was remembered for the session, with approval 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 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, +} + +/// Permission-decision variant indicating approval was persisted for a project location, with approval details and location key. +/// +///
+/// +/// **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 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, +} + +/// Permission-decision variant indicating the request was cancelled before use, with an optional reason. /// ///
/// @@ -10308,20 +11492,15 @@ 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) +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 post_install_message: Option, - /// Number of skills discovered and installed from the plugin - pub skills_installed: i64, + pub reason: Option, } -/// Plugins installed for the session, with their enabled state and version metadata. +/// Permission-decision variant indicating explicit denial by permission rules, with the matching rules. /// ///
/// @@ -10331,12 +11510,14 @@ pub struct PluginInstallResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PluginList { - /// Installed plugins - pub plugins: Vec, +pub struct PermissionDecisionDeniedByRules { + /// Denied because approval rules explicitly blocked it + pub kind: PermissionDecisionDeniedByRulesKind, + /// Rules that denied the request + pub rules: Vec, } -/// Plugins installed in user/global state. +/// Permission-decision variant indicating no approval rule matched and user confirmation was unavailable. /// ///
/// @@ -10346,12 +11527,12 @@ pub struct PluginList { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PluginListResult { - /// Installed plugins - pub plugins: Vec, +pub struct PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser { + /// Denied because no approval rule matched and user confirmation was unavailable + pub kind: PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind, } -/// Plugin names (or specs) to disable. +/// Permission-decision variant indicating the user denied an interactive prompt, with optional feedback and force-reject flag. /// ///
/// @@ -10361,12 +11542,18 @@ 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 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, } -/// Plugin names (or specs) to enable. +/// Permission-decision variant indicating denial by content-exclusion policy, with path and message. /// ///
/// @@ -10376,12 +11563,16 @@ 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 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, } -/// Plugin source and optional working directory for relative-path resolution. +/// Permission-decision variant indicating denial by a permission request hook, with optional message and interrupt flag. /// ///
/// @@ -10391,15 +11582,18 @@ 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. +pub struct PermissionDecisionDeniedByPermissionRequestHook { + /// Whether to interrupt the current agent turn #[serde(skip_serializing_if = "Option::is_none")] - pub working_directory: Option, + 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, } -/// Marketplace source and optional working directory for relative-path resolution. +/// Optional informational context describing how and where the permission decision was made. This does not affect permission behavior. /// ///
/// @@ -10409,15 +11603,19 @@ 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 PermissionDecisionContext { + /// Disposition of the permission request as observed by the responding client. + pub outcome: PermissionDecisionOutcome, + /// Whether the responding client could ask a user interactively, was running headlessly, or had no response path. Omit when the client cannot determine this authoritatively. #[serde(skip_serializing_if = "Option::is_none")] - pub working_directory: Option, + pub response_capability: Option, + /// Controlled reason or actor responsible for the response. + pub source: PermissionDecisionSource, + /// Client surface that submitted the response. + pub surface: PermissionDecisionSurface, } -/// Name of the marketplace whose plugin catalog to fetch. +/// Pending permission request ID and the decision to apply (approve/reject and scope). /// ///
/// @@ -10425,14 +11623,19 @@ pub struct PluginsMarketplacesAddRequest { /// 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 PluginsMarketplacesBrowseRequest { - /// Marketplace name to browse - pub name: String, +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, } -/// Optional marketplace name; omit to refresh all. +/// Location-persisted tool approval details for specific command identifiers. /// ///
/// @@ -10442,13 +11645,14 @@ 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 PermissionsLocationsAddToolApprovalDetailsCommands { + /// Command identifiers covered by this approval. + pub command_identifiers: Vec, + /// Approval scoped to specific command identifiers. + pub kind: PermissionsLocationsAddToolApprovalDetailsCommandsKind, } -/// Name of the marketplace to remove and an optional force flag. +/// Location-persisted tool approval details for read-only filesystem operations. /// ///
/// @@ -10458,15 +11662,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 PermissionsLocationsAddToolApprovalDetailsRead { + /// Approval covering read-only filesystem operations. + pub kind: PermissionsLocationsAddToolApprovalDetailsReadKind, } -/// Optional flags controlling which side effects the reload performs. +/// Location-persisted tool approval details for filesystem write operations. /// ///
/// @@ -10476,25 +11677,12 @@ 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 PermissionsLocationsAddToolApprovalDetailsWrite { + /// Approval covering filesystem write operations. + pub kind: PermissionsLocationsAddToolApprovalDetailsWriteKind, } -/// Name (or spec) of the plugin to uninstall. +/// Location-persisted tool approval details for an MCP server tool, or all tools when `toolName` is null. /// ///
/// @@ -10504,15 +11692,16 @@ 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 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, } -/// Name (or spec) of the plugin to update. +/// Location-persisted tool approval details for MCP sampling requests from a server. /// ///
/// @@ -10522,12 +11711,14 @@ 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 PermissionsLocationsAddToolApprovalDetailsMcpSampling { + /// Approval covering MCP sampling requests for a server. + pub kind: PermissionsLocationsAddToolApprovalDetailsMcpSamplingKind, + /// MCP server name. + pub server_name: String, } -/// Per-plugin result from updating all plugins, with versions, skills installed, success flag, and optional error. +/// Location-persisted tool approval details for writes to long-term memory. /// ///
/// @@ -10537,28 +11728,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 success: bool, +pub struct PermissionsLocationsAddToolApprovalDetailsMemory { + /// Approval covering writes to long-term memory. + pub kind: PermissionsLocationsAddToolApprovalDetailsMemoryKind, } -/// Result of updating all installed plugins. +/// Location-persisted tool approval details for a custom tool, keyed by tool name. /// ///
/// @@ -10568,12 +11743,14 @@ 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 PermissionsLocationsAddToolApprovalDetailsCustomTool { + /// Approval covering a custom tool. + pub kind: PermissionsLocationsAddToolApprovalDetailsCustomToolKind, + /// Custom tool name. + pub tool_name: String, } -/// Result of updating a single plugin. +/// Location-persisted tool approval details for extension-management operations, optionally narrowed by operation. /// ///
/// @@ -10583,18 +11760,15 @@ 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 +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 previous_version: Option, - /// Number of skills discovered and installed after the update - pub skills_installed: i64, + pub operation: Option, } -/// A BYOK model definition referencing a named provider. +/// Location-persisted factory approval, optionally narrowed by approval key. /// ///
/// @@ -10604,35 +11778,15 @@ 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`. +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 wire_model: Option, + pub approval_key: Option, + /// Approval covering factory operations. + pub kind: PermissionsLocationsAddToolApprovalDetailsFactoryKind, } -/// BYOK providers and/or models to add to the session's registry at runtime. Both fields are optional; provide providers, models, or both. +/// Location-persisted tool approval details for an extension's permission-gated capability access, keyed by extension name. /// ///
/// @@ -10642,16 +11796,14 @@ 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 PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess { + /// Extension name. + pub extension_name: String, + /// Approval covering an extension's request to access a permission-gated capability. + pub kind: PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccessKind, } -/// The selectable model entries synthesized for the models added by this call. +/// Location-persisted tool approval details for an extension's access to sensitive environment variables, keyed by extension name and the exact set of variable names. /// ///
/// @@ -10661,12 +11813,16 @@ 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 PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess { + /// Names of the sensitive environment variables this approval covers. Values are never persisted. + pub environment_variables: Vec, + /// Extension name. + pub extension_name: String, + /// Approval covering an extension's request to read sensitive environment variables. + pub kind: PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccessKind, } -/// Custom model-provider configuration (BYOK). +/// Location-scoped tool approval to persist. /// ///
/// @@ -10674,53 +11830,16 @@ pub struct ProviderAddResult { /// 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 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, +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, } -/// 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. +/// Working directory to load persisted location permissions for. /// ///
/// @@ -10730,20 +11849,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 PermissionLocationApplyParams { + /// Working directory whose persisted location permissions should be applied + pub working_directory: String, } -/// A snapshot of the provider endpoint the session is currently configured to talk to. +/// Summary of persisted location permissions applied to the session. /// ///
/// @@ -10753,28 +11864,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. - #[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 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, } -/// Optional model identifier to scope the endpoint snapshot to. +/// Working directory to resolve into a location-permissions key. /// ///
/// @@ -10784,13 +11889,12 @@ 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. - #[serde(skip_serializing_if = "Option::is_none")] - pub model_id: Option, +pub struct PermissionLocationResolveParams { + /// Working directory whose permission location should be resolved + pub working_directory: 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. +/// Resolved location-permissions key and type. /// ///
/// @@ -10800,14 +11904,14 @@ 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 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, } -/// 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. +/// Directory path to add to the session's allowed directories. /// ///
/// @@ -10817,12 +11921,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 PermissionPathsAddParams { + /// Directory to add to the allow-list. The runtime resolves and validates the path before adding, then loads conventional `.github/skills/` and `.github/agents/` definitions under it when their subsystem gates are enabled. Adding the directory is therefore also a trust decision for configuration stored there. + pub path: String, } -/// Blob attachment with inline base64-encoded data +/// Path to evaluate against the session's allowed directories. /// ///
/// @@ -10832,19 +11936,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 PermissionPathsAllowedCheckParams { + /// Path to check against the session's allowed directories + pub path: String, } -/// Directory attachment +/// Indicates whether the supplied path is within the session's allowed directories. /// ///
/// @@ -10854,16 +11951,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 PermissionPathsAllowedCheckResult { + /// Whether the path is within the session's allowed directories + pub allowed: bool, } -/// Optional line range to scope the attachment to a specific section of the file +/// 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. /// ///
/// @@ -10873,14 +11966,22 @@ 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 PermissionPathsConfig { + /// Additional directories to allow tool access to (in addition to the session's working directory). Conventional `.github/skills/` and `.github/agents/` definitions under them also join the session catalogs when their subsystem gates are enabled, so supplying a directory is a trust decision for configuration stored there. 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, } -/// File attachment +/// Snapshot of the session's allow-listed directories and primary working directory. /// ///
/// @@ -10890,19 +11991,14 @@ 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 - #[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 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, } -/// Pointer to a GitHub repository. +/// Directory path to set as the session's new primary working directory. /// ///
/// @@ -10912,17 +12008,12 @@ 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 PermissionPathsUpdatePrimaryParams { + /// Directory to set as the new primary working directory for the session's permission policy. + pub path: String, } -/// Pointer to a GitHub Actions job. +/// Path to evaluate against the session's workspace (primary) directory. /// ///
/// @@ -10932,48 +12023,12 @@ 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, -} - -/// Pointer to a GitHub commit. -/// -///
-/// -/// **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 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 PermissionPathsWorkspaceCheckParams { + /// Path to check against the session workspace directory + pub path: String, } -/// Pointer to a file in a GitHub repository at a specific ref. +/// Indicates whether the supplied path is within the session's workspace directory. /// ///
/// @@ -10983,20 +12038,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 PermissionPathsWorkspaceCheckResult { + /// Whether the path is within the session workspace directory + pub allowed: bool, } -/// One side of a file diff (head or base) +/// Notification payload describing the permission prompt that the client just rendered. /// ///
/// @@ -11006,16 +12053,12 @@ 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 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, } -/// Pointer to a single-file diff. At least one of `head` and `base` must be present. +/// Indicates whether the permission decision was applied; false when the request was already resolved. /// ///
/// @@ -11025,20 +12068,12 @@ 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 PermissionRequestResult { + /// Whether the permission request was handled successfully + pub success: bool, } -/// GitHub issue, pull request, or discussion reference +/// If specified, replaces the session's approved/denied permission rules. Omit to leave the current rules unchanged. /// ///
/// @@ -11048,22 +12083,14 @@ 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 PermissionRulesSet { + /// Rules that auto-approve matching requests + pub approved: Vec, + /// Rules that auto-deny matching requests + pub denied: Vec, } -/// Pointer to a GitHub release. +/// Source descriptor for a `session.permissions.configure` content-exclusion rule, with source name and type. /// ///
/// @@ -11073,20 +12100,14 @@ pub struct PushAttachmentGitHubReference { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PushAttachmentGitHubRelease { - /// Human-readable release name +pub struct PermissionsConfigureAdditionalContentExclusionPolicyRuleSource { + /// Name of the policy source. 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, + /// Type of the policy source. + pub r#type: String, } -/// Pointer to a GitHub repository. +/// Single content-exclusion rule supplied to `session.permissions.configure`, with paths, match conditions, and source. /// ///
/// @@ -11096,22 +12117,20 @@ pub struct PushAttachmentGitHubRelease { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PushAttachmentGitHubRepository { - /// Short description of the repository +pub struct PermissionsConfigureAdditionalContentExclusionPolicyRule { + /// Conditions of which at least one must match. #[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 if_any_match: Option>, + /// Conditions none of which may match. #[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 if_none_match: Option>, + /// Path patterns covered by this rule. + pub paths: Vec, + /// Source descriptor for a `session.permissions.configure` content-exclusion rule, with source name and type. + pub source: PermissionsConfigureAdditionalContentExclusionPolicyRuleSource, } -/// Pointer to a line range inside a file in a GitHub repository. +/// Content-exclusion policy supplied to `session.permissions.configure`, with rules, last-updated data, and scope. /// ///
/// @@ -11121,22 +12140,17 @@ 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 PermissionsConfigureAdditionalContentExclusionPolicy { + /// Opaque policy update timestamp supplied by the host. + #[serde(rename = "last_updated_at")] + pub last_updated_at: serde_json::Value, + /// Content-exclusion rules to apply. + pub rules: Vec, + /// Allowed values for the `PermissionsConfigureAdditionalContentExclusionPolicyScope` enumeration. + pub scope: PermissionsConfigureAdditionalContentExclusionPolicyScope, } -/// One side of a tree comparison (head or base) +/// 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. /// ///
/// @@ -11146,14 +12160,16 @@ 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 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. + #[serde(skip_serializing_if = "Option::is_none")] + pub unrestricted: Option, } -/// Pointer to a comparison between two git revisions. +/// Patch of permission policy fields to apply (omit a field to leave it unchanged). /// ///
/// @@ -11163,18 +12179,29 @@ 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 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, } -/// Generic GitHub URL reference. +/// Indicates whether the operation succeeded. /// ///
/// @@ -11184,14 +12211,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 PermissionsConfigureResult { + /// Whether the operation succeeded + pub success: bool, } -/// End position of the selection +/// Indicates whether the operation succeeded. /// ///
/// @@ -11201,14 +12226,12 @@ 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 PermissionsFolderTrustAddTrustedResult { + /// Whether the operation succeeded + pub success: bool, } -/// Start position of the selection +/// No parameters. /// ///
/// @@ -11218,14 +12241,9 @@ 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 PermissionsGetModeRequest {} -/// Position range of the selection within the file +/// Current permission mode. /// ///
/// @@ -11235,14 +12253,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 PermissionsGetModeResult { + /// Current permission mode + pub mode: PermissionMode, } -/// Code selection attachment from an editor +/// Indicates whether the operation succeeded. /// ///
/// @@ -11252,20 +12268,12 @@ 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 PermissionsLocationsAddToolApprovalResult { + /// Whether the operation succeeded + pub success: bool, } -/// Inputs for starting a deferred-idle drain. +/// Scope and add/remove instructions for modifying session- or location-scoped permission rules. /// ///
/// @@ -11275,12 +12283,21 @@ pub struct PushAttachmentSelection { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct QueueBeginDeferredIdleDrainRequest { - /// Whether the host still has active background work. - pub active_background_work: bool, +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, } -/// Whether a deferred-idle drain should run. +/// Indicates whether the operation succeeded. /// ///
/// @@ -11290,12 +12307,12 @@ pub struct QueueBeginDeferredIdleDrainRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct QueueBeginDeferredIdleDrainResult { - /// True when the host should run finishDeferredIdleDrain asynchronously. - pub should_drain: bool, +pub struct PermissionsModifyRulesResult { + /// Whether the operation succeeded + pub success: bool, } -/// Internal filter for consuming queued system notifications. +/// Indicates whether the operation succeeded. /// ///
/// @@ -11305,12 +12322,12 @@ pub struct QueueBeginDeferredIdleDrainResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct QueueConsumeSystemNotificationsRequest { - /// Opaque runtime-owned filter object. - pub filter: serde_json::Value, +pub struct PermissionsNotifyPromptShownResult { + /// Whether the operation succeeded + pub success: bool, } -/// Queued-command response indicating the host executed the command, with an optional flag to stop queue processing. +/// Indicates whether the operation succeeded. /// ///
/// @@ -11320,15 +12337,12 @@ pub struct QueueConsumeSystemNotificationsRequest { ///
#[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. - #[serde(skip_serializing_if = "Option::is_none")] - pub stop_processing_queue: Option, +pub struct PermissionsPathsAddResult { + /// Whether the operation succeeded + pub success: bool, } -/// Queued-command response indicating the host did not execute the command and the queue may continue. +/// No parameters; returns the session's allow-listed directories. /// ///
/// @@ -11338,12 +12352,9 @@ 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 PermissionsPathsListRequest {} -/// Inputs for marking session.idle deferred in native state. +/// Indicates whether the operation succeeded. /// ///
/// @@ -11353,12 +12364,12 @@ pub struct QueuedCommandNotHandled { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct QueueDeferSessionIdleRequest { - /// Whether the deferred idle was caused by an aborted foreground turn. - pub aborted: bool, +pub struct PermissionsPathsUpdatePrimaryResult { + /// Whether the operation succeeded + pub success: bool, } -/// Parameters for duplicating a queued item. +/// No parameters; returns currently-pending permission requests for the session. /// ///
/// @@ -11368,11 +12379,9 @@ pub struct QueueDeferSessionIdleRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct QueueDuplicateAtRequest { - pub id: String, -} +pub struct PermissionsPendingRequestsRequest {} -/// Result of duplicating a queued item. +/// Clears session-scoped tool permission approvals, and optionally the location-scoped ones. /// ///
/// @@ -11382,12 +12391,13 @@ pub struct QueueDuplicateAtRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct QueueDuplicateAtResult { - /// Fresh stable opaque id assigned to the duplicate. - pub id: String, +pub struct PermissionsResetSessionApprovalsRequest { + /// Whether location-scoped approvals are cleared too. Defaults to `true`. + #[serde(skip_serializing_if = "Option::is_none")] + pub include_location: Option, } -/// Result of enqueueing the resume-pending wake item. +/// Indicates whether the operation succeeded. /// ///
/// @@ -11397,12 +12407,12 @@ pub struct QueueDuplicateAtResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct QueueEnqueueResumePendingResult { - /// True when a wake item was newly queued. - pub queued: bool, +pub struct PermissionsResetSessionApprovalsResult { + /// Whether the operation succeeded + pub success: bool, } -/// Inputs for completing a deferred-idle drain. +/// Allow-all toggle for tool permission requests, with an optional telemetry source. /// ///
/// @@ -11412,14 +12422,15 @@ pub struct QueueEnqueueResumePendingResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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, +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 source: Option, } -/// Action selected by the native deferred-idle drain. +/// Indicates whether the operation succeeded. /// ///
/// @@ -11429,14 +12440,12 @@ pub struct QueueFinishDeferredIdleDrainRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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, +pub struct PermissionsSetApproveAllResult { + /// Whether the operation succeeded + pub success: bool, } -/// Whether the native queue has pending work. +/// Permission mode to apply for the session. /// ///
/// @@ -11446,12 +12455,18 @@ pub struct QueueFinishDeferredIdleDrainResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct QueueHasPendingResult { - /// True when queued or immediate native work is pending. - pub has_pending: bool, +pub struct PermissionsSetModeRequest { + /// Optional judge model id for assisted mode. When omitted, the session resolves the provider default: `gpt-5.5` for CAPI sessions and the active session model for BYOK sessions. + #[serde(skip_serializing_if = "Option::is_none")] + pub assisted_approval_model: Option, + /// Permission mode to apply + pub mode: PermissionMode, + /// Optional source for permission-mode telemetry. Defaults to `rpc` when omitted for SDK callers. + #[serde(skip_serializing_if = "Option::is_none")] + pub source: Option, } -/// Serializable message fields accepted by queue.insertAt. +/// Indicates whether the requested permission mode was applied and reports the authoritative post-mutation mode. /// ///
/// @@ -11461,45 +12476,14 @@ pub struct QueueHasPendingResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct QueueInsertMessage { - /// Optional explicit agent mode. When omitted, the session's current mode is assigned. - #[serde(skip_serializing_if = "Option::is_none")] - pub agent_mode: Option, - /// Optional attachments for the message. - #[serde(skip_serializing_if = "Option::is_none")] - pub attachments: Option>, - /// Whether the message is billable. - #[serde(skip_serializing_if = "Option::is_none")] - 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, +pub struct PermissionsSetModeResult { + /// Authoritative permission mode after the mutation + pub mode: PermissionMode, + /// Whether the operation succeeded + pub success: bool, } -/// Parameters for inserting a queued message at a public visible position. +/// Toggles whether permission prompts should be bridged into session events for this client. /// ///
/// @@ -11509,13 +12493,12 @@ pub struct QueueInsertMessage { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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, +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, } -/// Result of inserting a queued message. +/// Indicates whether the operation succeeded. /// ///
/// @@ -11525,12 +12508,12 @@ pub struct QueueInsertAtRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct QueueInsertAtResult { - /// Fresh stable opaque id assigned to the inserted item. - pub id: String, +pub struct PermissionsSetRequiredResult { + /// Whether the operation succeeded + pub success: bool, } -/// Parameters for moving a queued item by stable id. +/// Indicates whether the operation succeeded. /// ///
/// @@ -11540,14 +12523,12 @@ pub struct QueueInsertAtResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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, +pub struct PermissionsUrlsSetUnrestrictedModeResult { + /// Whether the operation succeeded + pub success: bool, } -/// Result of moving a queued item. +/// Whether the URL-permission policy should run in unrestricted mode. /// ///
/// @@ -11557,12 +12538,12 @@ pub struct QueueMoveItemRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct QueueMoveItemResult { - /// True when the item changed position; false when it was already at the requested position. - pub changed: bool, +pub struct PermissionUrlsSetUnrestrictedModeParams { + /// Whether to allow access to all URLs without prompting. Toggles the runtime's URL-permission policy in place. + pub enabled: bool, } -/// User-facing pending queue entry, with kind and display text for a queued message, slash command, or model change. +/// Optional message to echo back to the caller. /// ///
/// @@ -11572,18 +12553,13 @@ pub struct QueueMoveItemResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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, +pub struct PingRequest { + /// Optional message to echo back + #[serde(skip_serializing_if = "Option::is_none")] + pub message: Option, } -/// Snapshot of the session's pending queued items and immediate-steering messages. +/// Server liveness response, including the echoed message, current server timestamp, and protocol version. /// ///
/// @@ -11593,14 +12569,16 @@ 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 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, } -/// Parameters for removing a queued item by stable id. +/// Existence, contents, and resolved path of the session plan file. /// ///
/// @@ -11610,11 +12588,16 @@ pub struct QueuePendingItemsResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct QueueRemoveAtRequest { - pub id: 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, } -/// Result of removing a queued item. +/// 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. /// ///
/// @@ -11624,12 +12607,25 @@ pub struct QueueRemoveAtRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct QueueRemoveAtResult { - /// True when the addressed item was removed. - pub removed: bool, +pub struct PlanSqlTodosRow { + /// Todo creation time, as stored by the session SQL schema's `datetime('now')` default: `YYYY-MM-DD HH:MM:SS` in UTC. Lets clients attribute todos to the work item that created them (e.g. scoping a goal's progress to the todos it produced) rather than to the whole session. + #[serde(skip_serializing_if = "Option::is_none")] + pub created_at: Option, + /// 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, } -/// Indicates whether a user-facing pending item was removed. +/// Todo rows read from the session SQL database. Empty when no session database is available. /// ///
/// @@ -11639,12 +12635,12 @@ pub struct QueueRemoveAtResult { ///
#[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 PlanReadSqlTodosResult { + /// Rows from the session SQL todos table, ordered by creation time with insertion order used to break ties when available and id used for WITHOUT ROWID tables. + pub rows: Vec, } -/// Parameters for steering a queued message into a live turn. +/// A single dependency edge read from the session SQL `todo_deps` table, indicating that one todo must complete before another. /// ///
/// @@ -11654,11 +12650,14 @@ pub struct QueueRemoveMostRecentResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct QueueSendNowRequest { - pub id: String, -} - -/// Result of trying to steer a queued message into a live turn. +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, +} + +/// Todo rows + dependency edges read from the session SQL database. /// ///
/// @@ -11668,12 +12667,14 @@ pub struct QueueSendNowRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct QueueSendNowResult { - /// True when the item was accepted into the steering lane; false when no main turn was live. - pub steered: bool, +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 with insertion order used to break ties when available and id used for WITHOUT ROWID tables. Empty when no database, no todos table, or the SELECT failed. + pub rows: Vec, } -/// 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. +/// Replacement contents to write to the session plan file. /// ///
/// @@ -11683,11 +12684,12 @@ pub struct QueueSendNowResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct QueueSetDrainPausedRequest { - pub paused: bool, +pub struct PlanUpdateRequest { + /// The new content for the plan file + pub content: String, } -/// Internal snapshot of native queue state for local session orchestration. +/// Session plugin metadata, with name, marketplace, optional version, and enabled state. /// ///
/// @@ -11697,20 +12699,19 @@ pub struct QueueSetDrainPausedRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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`. +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 steering_message_orders: Option>, - /// Immediate steering messages waiting for an active turn. - pub steering_messages: Vec, + pub version: Option, } -/// Parameters for editing a single queued message. +/// Result of installing a plugin. /// ///
/// @@ -11720,14 +12721,20 @@ pub struct QueueSnapshotResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct QueueUpdateTextRequest { +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 display_prompt: Option, - pub id: String, - pub prompt: String, + 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, } -/// Result of editing a queued message. +/// Plugins installed for the session, with their enabled state and version metadata. /// ///
/// @@ -11737,12 +12744,12 @@ pub struct QueueUpdateTextRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct QueueUpdateTextResult { - /// True when the stored text changed. - pub updated: bool, +pub struct PluginList { + /// Installed plugins + pub plugins: Vec, } -/// Event type to register consumer interest for, used by runtime gating logic. +/// Plugins installed in user/global state. /// ///
/// @@ -11752,12 +12759,12 @@ pub struct QueueUpdateTextResult { ///
#[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 PluginListResult { + /// Installed plugins + pub plugins: Vec, } -/// Opaque handle representing an event-type interest registration. +/// Trusted built-in plugin directories to use for this runtime process. /// ///
/// @@ -11767,12 +12774,12 @@ 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 PluginsBuiltinSetRequest { + /// Complete replacement set of trusted built-in plugin directories. Every entry must be an absolute local filesystem path no longer than 4096 characters. + pub paths: Vec, } -/// Optional registration options. +/// Plugin names (or specs) to disable. /// ///
/// @@ -11782,14 +12789,12 @@ 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)] - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) enabled: Option, +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, } -/// Params to attach an extension loader's tools to a session. +/// Plugin names (or specs) to enable. /// ///
/// @@ -11799,18 +12804,12 @@ 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. - #[serde(skip_serializing_if = "Option::is_none")] - pub options: Option, - /// Session to register extension tools on. - pub session_id: SessionId, +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, } -/// Handle for releasing the extension tool registration. +/// Plugin source and optional working directory for relative-path resolution. /// ///
/// @@ -11820,13 +12819,15 @@ 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 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, } -/// Opaque handle previously returned by `registerInterest` to release. +/// Marketplace source and optional working directory for relative-path resolution. /// ///
/// @@ -11836,12 +12837,15 @@ 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 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, } -/// Reattach to an existing MC session without creating a new one. +/// Name of the marketplace whose plugin catalog to fetch. /// ///
/// @@ -11851,14 +12855,12 @@ 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 PluginsMarketplacesBrowseRequest { + /// Marketplace name to browse + pub name: String, } -/// Configuration for the runtime-managed remote-control singleton. +/// Optional marketplace name; omit to refresh all. /// ///
/// @@ -11868,24 +12870,13 @@ 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. - #[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 struct PluginsMarketplacesRefreshRequest { + /// Marketplace name to refresh. When omitted, every registered marketplace is refreshed. #[serde(skip_serializing_if = "Option::is_none")] - pub task_id: Option, + pub name: Option, } -/// Remote control is connected to a local session. +/// Name of the marketplace to remove and an optional force flag. /// ///
/// @@ -11895,27 +12886,15 @@ 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 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(crate) prompt_manager: Option, - /// Remote control state tag: active. - pub state: RemoteControlStatusActiveState, + pub force: Option, + /// Marketplace name to remove + pub name: String, } -/// Remote control is in the middle of initial setup. +/// Optional flags controlling which side effects the reload performs. /// ///
/// @@ -11925,14 +12904,25 @@ 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 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, } -/// The last setup attempt failed. The singleton is otherwise off. +/// Name (or spec) of the plugin to uninstall. /// ///
/// @@ -11942,17 +12932,15 @@ 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. +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 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 direct_source_id: Option, + /// Plugin name or "plugin@marketplace" spec to uninstall. When ambiguous, prefer the fully-qualified spec. + pub name: String, } -/// Remote control is not connected. +/// Name (or spec) of the plugin to update. /// ///
/// @@ -11962,12 +12950,12 @@ 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 PluginsUpdateRequest { + /// Plugin name or "plugin@marketplace" spec to update. + pub name: String, } -/// Wrapper for the singleton's current status. +/// Per-plugin result from updating all plugins, with versions, skills installed, success flag, and optional error. /// ///
/// @@ -11977,12 +12965,28 @@ 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 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, } -/// Outcome of a stopRemoteControl call. +/// Result of updating all installed plugins. /// ///
/// @@ -11992,14 +12996,12 @@ 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 PluginUpdateAllResult { + /// Per-plugin update results in deterministic order. + pub results: Vec, } -/// Outcome of a transferRemoteControl call. +/// Result of updating a single plugin. /// ///
/// @@ -12009,14 +13011,18 @@ 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 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 remote session mode ("off", "export", or "on"); defaults to enabling both export and remote steering. +/// Serializable definition of a caller-implemented tool whose execution is handled over the SDK connection. /// ///
/// @@ -12026,13 +13032,35 @@ 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 ProtocolExternalToolDefinition { + /// Tool-loading deferral policy. #[serde(skip_serializing_if = "Option::is_none")] - pub mode: Option, + pub defer: Option, + /// Model-visible explanation of what the tool does. + pub description: String, + /// Whether the tool executes commands in a terminal. + #[serde(skip_serializing_if = "Option::is_none")] + pub is_terminal: Option, + /// Optional caller-defined metadata associated with the tool. + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option>, + /// Unique model-visible tool name. + pub name: String, + /// Whether this definition replaces a built-in tool with the same name. + #[serde(skip_serializing_if = "Option::is_none")] + pub overrides_built_in_tool: Option, + /// JSON Schema describing the tool's input arguments. + #[serde(skip_serializing_if = "Option::is_none")] + pub parameters: Option>, + /// Whether execution bypasses the normal tool permission prompt. + #[serde(skip_serializing_if = "Option::is_none")] + pub skip_permission: Option, + /// Optional human-readable display title. + #[serde(skip_serializing_if = "Option::is_none")] + pub title: Option, } -/// GitHub URL for the session and a flag indicating whether remote steering is enabled. +/// A BYOK model definition referencing a named provider. /// ///
/// @@ -12042,15 +13070,35 @@ 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 ProviderModelConfig { + /// Optional capability overrides (vision, tool_calls, reasoning, etc.). #[serde(skip_serializing_if = "Option::is_none")] - pub url: 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 configured provider 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, } -/// New remote-steerability state to persist as a `session.remote_steerable_changed` event. +/// BYOK providers and/or models to add to the session's registry at runtime. Both fields are optional; provide providers, models, or both. /// ///
/// @@ -12060,12 +13108,16 @@ 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, +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>, } -/// 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. +/// The selectable model entries synthesized for the models added by this call. /// ///
/// @@ -12075,9 +13127,12 @@ pub struct RemoteNotifySteerableChangedRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct RemoteNotifySteerableChangedResult {} +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, +} -/// Remote session connection result. +/// Custom model-provider configuration (BYOK). /// ///
/// @@ -12087,14 +13142,57 @@ 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 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, + /// Overrides for model capabilities when they cannot be inferred from modelId. + #[serde(skip_serializing_if = "Option::is_none")] + pub model_capabilities: 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 name used for model and telemetry attribution. + #[serde(skip_serializing_if = "Option::is_none")] + pub provider_name: 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, } -/// GitHub repository the remote session belongs to. +/// 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. /// ///
/// @@ -12104,16 +13202,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 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, } -/// Remote session metadata for the session to hand off (typically obtained from `sessions.list` with `source: "remote"`). +/// A snapshot of the provider endpoint the session is currently configured to talk to. /// ///
/// @@ -12123,46 +13225,28 @@ 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. +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 stale_at: Option, - /// Session creation time as an ISO 8601 timestamp. - pub start_time: String, - /// Server-side task state returned by GitHub. + 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 state: Option, - /// Short summary of the session, when one has been derived. + pub session_token: Option, + /// Transport to be used for provider requests. #[serde(skip_serializing_if = "Option::is_none")] - pub summary: Option, - /// Whether the remote task originated from CCA or CLI `--remote`. + 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 task_type: Option, + pub wire_api: Option, } -/// Repository context for the remote session. +/// Optional model identifier to scope the endpoint snapshot to. /// ///
/// @@ -12172,17 +13256,13 @@ pub struct RemoteSessionMetadataValue { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct RemoteSessionRepository { - /// Optional branch associated with the remote session. +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 branch: Option, - /// Repository name. - pub name: String, - /// Repository owner or organization login. - pub owner: String, + pub model_id: Option, } -/// macOS seatbelt experimental options. +/// 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. /// ///
/// @@ -12192,13 +13272,14 @@ 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. - #[serde(skip_serializing_if = "Option::is_none")] - pub keychain_access: Option, +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 the configured provider name. + pub provider_name: String, } -/// Platform-specific experimental policy fields. +/// 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. /// ///
/// @@ -12208,13 +13289,12 @@ 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 ProviderTokenAcquireResult { + /// The bearer token value (without the `Bearer ` prefix). + pub token: String, } -/// Filesystem rules to merge into the base policy. +/// Blob attachment with inline base64-encoded data /// ///
/// @@ -12224,22 +13304,19 @@ 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. +pub struct PushAttachmentBlob { + /// Base64-encoded content + pub data: String, + /// User-facing display name for the attachment #[serde(skip_serializing_if = "Option::is_none")] - pub readwrite_paths: Option>, + pub display_name: Option, + /// MIME type of the inline data + pub mime_type: String, + /// Attachment type discriminator + pub r#type: PushAttachmentBlobType, } -/// HTTP proxy configuration for sandboxed traffic. +/// Directory attachment /// ///
/// @@ -12249,18 +13326,16 @@ pub struct SandboxConfigUserPolicyFilesystem { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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, +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, } -/// Network rules to merge into the base policy. +/// Optional line range to scope the attachment to a specific section of the file /// ///
/// @@ -12270,19 +13345,14 @@ pub struct SandboxConfigUserPolicyNetworkProxy { ///
#[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, - /// 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, +pub struct PushAttachmentFileLineRange { + /// End line number (1-based, inclusive) + pub end: i64, + /// Start line number (1-based) + pub start: i64, } -/// macOS seatbelt-specific options. +/// File attachment /// ///
/// @@ -12292,13 +13362,19 @@ 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. +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 keychain_access: Option, + pub line_range: Option, + /// Absolute file path + pub path: String, + /// Attachment type discriminator + pub r#type: PushAttachmentFileType, } -/// User-managed sandbox policy fragment merged into the auto-discovered base policy. +/// Pointer to a GitHub repository. /// ///
/// @@ -12308,22 +13384,17 @@ 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. +pub struct PushGitHubRepoRef { + /// Numeric GitHub repository id #[serde(skip_serializing_if = "Option::is_none")] - pub seatbelt: Option, + pub id: Option, + /// Repository name (without owner) + pub name: String, + /// Repository owner login (user or organization) + pub owner: String, } -/// Resolved sandbox configuration. +/// Pointer to a GitHub Actions job. /// ///
/// @@ -12333,27 +13404,25 @@ 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 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 exporting CARGO_HOME/GOPATH/etc. Default: true (enabled by default; set to false to opt out). - #[serde(skip_serializing_if = "Option::is_none")] - pub allow_dev_tool_caches: 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. +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 user_policy: 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, } -/// Register an absolute-time scheduled prompt. +/// Pointer to a GitHub commit. /// ///
/// @@ -12363,20 +13432,20 @@ pub struct SandboxConfig { ///
#[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 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 recurring: Option, +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, } -/// Register a cron scheduled prompt. +/// Pointer to a file in a GitHub repository at a specific ref. /// ///
/// @@ -12386,23 +13455,20 @@ pub struct ScheduleAddAtRequest { ///
#[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 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 recurring: Option, - /// IANA timezone for evaluating the cron expression. - #[serde(skip_serializing_if = "Option::is_none")] - pub tz: Option, +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, } -/// Register a relative-interval scheduled prompt. +/// One side of a file diff (head or base) /// ///
/// @@ -12412,20 +13478,16 @@ pub struct ScheduleAddCronRequest { ///
#[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 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 recurring: Option, +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, } -/// Scheduled prompt entry with ID, timing (`intervalMs`, `cron`, or `at`), prompt text, recurrence, and next run time. +/// Pointer to a single-file diff. At least one of `head` and `base` must be present. /// ///
/// @@ -12435,36 +13497,20 @@ pub struct ScheduleAddRequest { ///
#[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. +pub struct PushAttachmentGitHubFileDiff { + /// File location on the base side of the diff. Absent for additions. #[serde(skip_serializing_if = "Option::is_none")] - pub self_paced: Option, - /// IANA timezone the `cron` expression is evaluated in. + pub base: Option, + /// File location on the head side of the diff. Absent for deletions. #[serde(skip_serializing_if = "Option::is_none")] - pub tz: Option, + 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, } -/// Result of registering or re-arming a scheduled prompt. +/// GitHub issue, pull request, or discussion reference /// ///
/// @@ -12474,16 +13520,22 @@ pub struct ScheduleEntry { ///
#[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 entry: Option, - /// User-facing validation error, when registration failed. - #[serde(skip_serializing_if = "Option::is_none")] - pub error: 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, } -/// Register a self-paced scheduled prompt. +/// Pointer to a GitHub release. /// ///
/// @@ -12493,15 +13545,20 @@ pub struct ScheduleAddResult { ///
#[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 display_prompt: Option, - /// Prompt text to enqueue when the schedule fires. - pub prompt: String, +pub struct PushAttachmentGitHubRelease { + /// Human-readable release 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, } -/// Whether the session currently has an active self-paced schedule. +/// Pointer to a GitHub repository. /// ///
/// @@ -12511,27 +13568,47 @@ pub struct ScheduleAddSelfPacedRequest { ///
#[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. -/// -///
-/// +pub struct PushAttachmentGitHubRepository { + /// Short description of the repository + #[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. + #[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, +} + +/// Pointer to a line range inside a file in a GitHub repository. +/// +///
+/// /// **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, +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, } -/// Re-arm a self-paced scheduled prompt. +/// One side of a tree comparison (head or base) /// ///
/// @@ -12541,14 +13618,14 @@ pub struct ScheduleList { ///
#[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, +pub struct PushAttachmentGitHubTreeComparisonSide { + /// Repository the revision belongs to + pub repo: PushGitHubRepoRef, + /// Git revision (branch, tag, or commit SHA) + pub revision: String, } -/// Identifier of the scheduled prompt to remove. +/// Pointer to a comparison between two git revisions. /// ///
/// @@ -12558,12 +13635,18 @@ pub struct ScheduleRearmSelfPacedRequest { ///
#[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 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, } -/// Remove a scheduled prompt by id. The result entry is omitted if the id was unknown. +/// Generic GitHub URL reference. /// ///
/// @@ -12573,13 +13656,14 @@ 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 PushAttachmentGitHubUrl { + /// Attachment type discriminator + pub r#type: PushAttachmentGitHubUrlType, + /// URL to the GitHub resource + pub url: String, } -/// Secret values to add to the redaction filter. +/// End position of the selection /// ///
/// @@ -12589,12 +13673,14 @@ 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 PushAttachmentSelectionDetailsEnd { + /// End character offset within the line (0-based) + pub character: i64, + /// End line number (0-based) + pub line: i64, } -/// Confirmation that the secret values were registered. +/// Start position of the selection /// ///
/// @@ -12604,12 +13690,14 @@ 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 PushAttachmentSelectionDetailsStart { + /// Start character offset within the line (0-based) + pub character: i64, + /// Start line number (0-based) + pub line: i64, } -/// Parameters for session.extensions.sendAttachmentsToMessage. +/// Position range of the selection within the file /// ///
/// @@ -12619,15 +13707,14 @@ 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 PushAttachmentSelectionDetails { + /// End position of the selection + pub end: PushAttachmentSelectionDetailsEnd, + /// Start position of the selection + pub start: PushAttachmentSelectionDetailsStart, } -/// A single user message to append to the session as part of a `session.sendMessages` turn +/// Code selection attachment from an editor /// ///
/// @@ -12637,29 +13724,20 @@ 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 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(crate) source: Option, +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, } -/// 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. +/// Inputs for starting a deferred-idle drain. /// ///
/// @@ -12669,33 +13747,12 @@ 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. 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 wait: Option, +pub struct QueueBeginDeferredIdleDrainRequest { + /// Whether the host still has active background work. + pub active_background_work: bool, } -/// Result of sending zero or more user messages +/// Whether a deferred-idle drain should run. /// ///
/// @@ -12705,12 +13762,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 QueueBeginDeferredIdleDrainResult { + /// True when the host should run finishDeferredIdleDrain asynchronously. + pub should_drain: bool, } -/// Parameters for sending a user message to the session +/// Internal filter for consuming queued system notifications. /// ///
/// @@ -12720,49 +13777,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 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(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. 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 wait: Option, +pub struct QueueConsumeSystemNotificationsRequest { + /// Opaque runtime-owned filter object. + pub filter: serde_json::Value, } -/// Result of sending a user message +/// Queued-command response indicating the host executed the command, with an optional flag to stop queue processing. /// ///
/// @@ -12772,12 +13792,15 @@ 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 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, } -/// Internal request for sending a system notification. +/// Queued-command response indicating the host did not execute the command and the queue may continue. /// ///
/// @@ -12787,18 +13810,12 @@ pub struct SendResult { ///
#[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 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 options: 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, } -/// Agents discovered across user, project, plugin, and remote sources. +/// Inputs for marking session.idle deferred in native state. /// ///
/// @@ -12808,12 +13825,12 @@ pub struct SendSystemNotificationRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ServerAgentList { - /// All discovered agents across all sources - pub agents: Vec, +pub struct QueueDeferSessionIdleRequest { + /// Whether the deferred idle was caused by an aborted foreground turn. + pub aborted: bool, } -/// Instruction sources discovered across user, repository, and plugin sources. +/// Parameters for duplicating a queued item. /// ///
/// @@ -12823,12 +13840,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 QueueDuplicateAtRequest { + /// Stable opaque ID of the queued item to duplicate. + pub id: String, } -/// Server-side skill metadata, including name, description, source, enabled/invocable state, path, project path, and argument hint. +/// Result of duplicating a queued item. /// ///
/// @@ -12838,29 +13855,12 @@ 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 - #[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 - #[serde(skip_serializing_if = "Option::is_none")] - pub path: Option, - /// The project path this skill belongs to (only for project/inherited skills) - #[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 struct QueueDuplicateAtResult { + /// Fresh stable opaque id assigned to the duplicate. + pub id: String, } -/// Skills discovered across global and project sources. +/// Result of enqueueing the resume-pending wake item. /// ///
/// @@ -12870,15 +13870,12 @@ 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 QueueEnqueueResumePendingResult { + /// True when a wake item was newly queued. + pub queued: bool, } -/// Current activity flags for the session. +/// Inputs for completing a deferred-idle drain. /// ///
/// @@ -12888,14 +13885,14 @@ 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 QueueFinishDeferredIdleDrainRequest { + /// Whether the host still has active background work. + pub active_background_work: bool, + /// Whether native queued work remains. + pub has_pending: bool, } -/// Authentication status and account metadata for the session. +/// Action selected by the native deferred-idle drain. /// ///
/// @@ -12905,27 +13902,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 QueueFinishDeferredIdleDrainResult { + /// Whether the deferred idle was caused by an aborted foreground turn. + pub aborted: bool, + /// One of none, processQueue, or emitSessionIdle. + pub action: String, } -/// Map of sessionId -> bytes freed by removing the session's workspace directory. +/// Whether the native queue has pending work. /// ///
/// @@ -12935,39 +13919,60 @@ 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, +pub struct QueueHasPendingResult { + /// True when queued or immediate native work is pending. + pub has_pending: bool, } +/// Serializable message fields accepted by queue.insertAt. +/// +///
+/// +/// **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 SessionContextAttributionEntriesItem { - /// Supplementary per-entry metadata (e.g. `messageCount`, `role`, `evictable`, `pluginSource`). Values are stringified; parse as needed and ignore unrecognized keys. +pub struct QueueInsertMessage { + /// Optional explicit agent mode. When omitted, the session's current mode is assigned. #[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. + pub agent_mode: Option, + /// Optional attachments for the message. #[serde(skip_serializing_if = "Option::is_none")] - pub parent_id: Option, - /// Token count currently in context attributable to this entry. - pub tokens: i64, + pub attachments: Option>, + /// Whether the message is billable. + #[serde(skip_serializing_if = "Option::is_none")] + 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, } -/// Per-source context-window attribution, or null if the session has not yet been initialized (no system prompt or tool metadata cached). +/// Parameters for inserting a queued message at a public visible position. /// ///
/// @@ -12977,16 +13982,14 @@ 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 QueueInsertAtRequest { + /// Queued message contents and delivery metadata. + pub message: QueueInsertMessage, + /// Zero-based position in the public visible queue. Values outside the queue clamp to an end. + pub position: 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). +/// Result of inserting a queued message. /// ///
/// @@ -12996,30 +13999,12 @@ 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 QueueInsertAtResult { + /// Fresh stable opaque id assigned to the inserted item. + pub id: String, } -/// The enriched metadata records, with summary and context fields backfilled where available. Sessions confirmed empty and unnamed are omitted. +/// Parameters for moving a queued item by stable id. /// ///
/// @@ -13029,12 +14014,14 @@ 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 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, } -/// File path, content to append, and optional mode for the client-provided session filesystem. +/// Result of moving a queued item. /// ///
/// @@ -13044,19 +14031,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 QueueMoveItemResult { + /// True when the item changed position; false when it was already at the requested position. + pub changed: bool, } -/// Describes a filesystem error. +/// User-facing pending queue entry, with kind and display text for a queued message, slash command, or model change. /// ///
/// @@ -13066,15 +14046,21 @@ 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 +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, + /// Stable identity of the queued user message. Present for message rows and absent for slash commands and model changes. #[serde(skip_serializing_if = "Option::is_none")] - pub message: Option, + pub message_id: Option, } -/// Path to test for existence in the client-provided session filesystem. +/// Snapshot of the session's pending queued items and immediate-steering messages. /// ///
/// @@ -13084,14 +14070,17 @@ 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 QueuePendingItemsResult { + /// How many leading entries of `steeringMessages` have already been folded into the running turn (and so have an emitted `user.message`), as opposed to still waiting for one. Absent for hosts that do not distinguish the two. + #[serde(skip_serializing_if = "Option::is_none")] + pub in_flight_steering_count: Option, + /// 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, } -/// Indicates whether the requested path exists in the client-provided session filesystem. +/// Parameters for removing a queued item by stable id. /// ///
/// @@ -13101,12 +14090,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 QueueRemoveAtRequest { + /// Stable opaque ID of the queued item to remove. + pub id: String, } -/// Directory path to create in the client-provided session filesystem, with options for recursive creation and POSIX mode. +/// Result of removing a queued item. /// ///
/// @@ -13116,20 +14105,12 @@ 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 QueueRemoveAtResult { + /// True when the addressed item was removed. + pub removed: bool, } -/// Directory path whose entries should be listed from the client-provided session filesystem. +/// Indicates whether a user-facing pending item was removed. /// ///
/// @@ -13139,14 +14120,12 @@ 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 QueueRemoveMostRecentResult { + /// True if a user-facing pending item was removed (LIFO across both queues); false when no removable items remained. + pub removed: bool, } -/// Names of entries in the requested directory, or a filesystem error if the read failed. +/// Parameters for steering a queued message into a live turn. /// ///
/// @@ -13156,15 +14135,12 @@ 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. - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, +pub struct QueueSendNowRequest { + /// Stable opaque ID of the queued item to steer into the live turn. + pub id: String, } -/// Directory entry returned by session filesystem `readdirWithTypes`, with name and entry type. +/// Result of trying to steer a queued message into a live turn. /// ///
/// @@ -13174,14 +14150,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 QueueSendNowResult { + /// True when the item was accepted into the steering lane; false when no main turn was live. + pub steered: bool, } -/// Directory path whose entries (with type information) should be listed from the client-provided session filesystem. +/// 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. /// ///
/// @@ -13191,14 +14165,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 QueueSetDrainPausedRequest { + /// Whether queued-lane draining should be paused. + pub paused: bool, } -/// Entries in the requested directory paired with file/directory type information, or a filesystem error if the read failed. +/// Internal snapshot of native queue state for local session orchestration. /// ///
/// @@ -13208,15 +14180,20 @@ 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. +pub struct QueueSnapshotResult { + /// Insertion orders for queued items, aligned with `items`. #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, + 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, } -/// Path of the file to read from the client-provided session filesystem. +/// Parameters for editing a single queued message. /// ///
/// @@ -13226,14 +14203,17 @@ 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 QueueUpdateTextRequest { + /// Optional replacement prompt displayed to the user. + #[serde(skip_serializing_if = "Option::is_none")] + pub display_prompt: Option, + /// Stable opaque ID of the queued item to edit. + pub id: String, + /// Replacement prompt sent to the model. + pub prompt: String, } -/// File content as a UTF-8 string, or a filesystem error if the read failed. +/// Result of editing a queued message. /// ///
/// @@ -13243,15 +14223,12 @@ 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. - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, +pub struct QueueUpdateTextResult { + /// True when the stored text changed. + pub updated: bool, } -/// Source and destination paths for renaming or moving an entry in the client-provided session filesystem. +/// Event type to register consumer interest for, used by runtime gating logic. /// ///
/// @@ -13261,16 +14238,12 @@ 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 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, } -/// Path to remove from the client-provided session filesystem, with options for recursive removal and force. +/// Opaque handle representing an event-type interest registration. /// ///
/// @@ -13280,20 +14253,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 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, } -/// Optional capabilities declared by the provider +/// Optional registration options. /// ///
/// @@ -13303,13 +14268,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 +pub struct SessionsRegisterExtensionToolsOnSessionOptions { + /// In-process `() => boolean` gating callback used only by the CLI. + #[doc(hidden)] #[serde(skip_serializing_if = "Option::is_none")] - pub sqlite: Option, + pub(crate) enabled: Option, } -/// Initial working directory, session-state path layout, and path conventions used to register the calling SDK client as the session filesystem provider. +/// Params to attach an extension loader's tools to a session. /// ///
/// @@ -13319,19 +14285,18 @@ pub struct SessionFsSetProviderCapabilities { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFsSetProviderRequest { - /// Optional capabilities declared by the provider +pub(crate) struct RegisterExtensionToolsParams { + /// In-process ExtensionLoader handle used only by the CLI and excluded from the public SDK surface. + #[doc(hidden)] + pub(crate) loader: serde_json::Value, + /// Optional registration options. #[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 options: Option, + /// Session to register extension tools on. + pub session_id: SessionId, } -/// Indicates whether the calling client was registered as the session filesystem provider. +/// Handle for releasing the extension tool registration. /// ///
/// @@ -13341,12 +14306,13 @@ 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(crate) struct RegisterExtensionToolsResult { + /// In-process unsubscribe function used only by the CLI. + #[doc(hidden)] + pub(crate) unsubscribe: serde_json::Value, } -/// Indicates whether the per-session SQLite database already exists. +/// Opaque handle previously returned by `registerInterest` to release. /// ///
/// @@ -13356,12 +14322,12 @@ 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 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, } -/// 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. +/// Reattach to an existing MC session without creating a new one. /// ///
/// @@ -13371,19 +14337,14 @@ 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 - #[serde(skip_serializing_if = "Option::is_none")] - pub params: 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, } -/// Query results including rows, columns, and rows affected, or a filesystem error if execution failed. +/// Configuration for the runtime-managed remote-control singleton. /// ///
/// @@ -13393,22 +14354,24 @@ 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. +pub struct RemoteControlConfig { + /// Reattach to an existing MC session without creating a new one. #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, - /// SQLite last_insert_rowid() value for INSERT. + 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 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 task_id: Option, } -/// Classified SQLite transaction failure. busyOrLocked guarantees rollback; postCommitAmbiguous must never be retried. +/// Remote control is connected to a local session. /// ///
/// @@ -13418,12 +14381,27 @@ pub struct SessionFsSqliteQueryResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFsSqliteTransactionError { - pub error_class: SessionFsSqliteTransactionErrorClass, - pub message: String, +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. Retained as an optional compatibility field; native remote control does not populate or consume it. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) prompt_manager: Option, + /// Remote control state tag: active. + pub state: RemoteControlStatusActiveState, } -/// One statement in an atomic SQLite transaction. +/// Remote control is in the middle of initial setup. /// ///
/// @@ -13433,17 +14411,14 @@ pub struct SessionFsSqliteTransactionError { ///
#[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, +pub struct RemoteControlStatusConnecting { + /// Session id the connection is attaching to. + pub attached_session_id: String, + /// Remote control state tag: connecting. + pub state: RemoteControlStatusConnectingState, } -/// Statements to execute atomically. Providers apply busy handling for every call. +/// The last setup attempt failed. The singleton is otherwise off. /// ///
/// @@ -13453,13 +14428,17 @@ pub struct SessionFsSqliteTransactionStatement { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFsSqliteTransactionRequest { - /// Target session identifier - pub session_id: SessionId, - pub statements: Vec, +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, } -/// Per-statement results, or a classified transaction error. +/// Remote control is not connected. /// ///
/// @@ -13469,13 +14448,12 @@ pub struct SessionFsSqliteTransactionRequest { ///
#[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, +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. /// ///
/// @@ -13485,14 +14463,12 @@ pub struct SessionFsSqliteTransactionResult { ///
#[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. /// ///
/// @@ -13502,23 +14478,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. /// ///
/// @@ -13528,19 +14495,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. /// ///
/// @@ -13550,31 +14512,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, - /// 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 +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 or full commit SHA, and optional subpath. +/// GitHub URL for the session and a flag indicating whether remote steering is enabled. /// ///
/// @@ -13584,20 +14528,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, - #[serde(skip_serializing_if = "Option::is_none")] - pub r#ref: Option, - pub repo: String, - /// Optional full 40-character hexadecimal commit SHA. +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 sha: Option, - /// 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. /// ///
/// @@ -13607,13 +14546,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 or full commit SHA, 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. /// ///
/// @@ -13623,20 +14561,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, - /// 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, -} +pub struct RemoteNotifySteerableChangedResult {} -/// Baseline data provenance for a prediction. +/// Remote session connection result. /// ///
/// @@ -13646,14 +14573,14 @@ pub struct SessionInstalledPluginSourceUrl { ///
#[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, +pub struct RemoteSessionConnectionResult { + /// Metadata for a connected remote session. + pub metadata: ConnectedRemoteSessionMetadata, + /// SDK session ID for the connected remote session. + pub session_id: SessionId, } -/// Semantic usage tier and its AI-credit cap. +/// GitHub repository the remote session belongs to. /// ///
/// @@ -13663,13 +14590,16 @@ pub struct SessionLimitPredictionBaselineData { ///
#[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, +pub struct RemoteSessionMetadataRepository { + /// Branch associated with the remote session. + pub branch: String, + /// Repository name. + pub name: String, + /// Repository owner. + pub owner: String, } -/// Explainable AI-credit session-limit prediction. +/// Remote session metadata for the session to hand off (typically obtained from `sessions.list` with `source: "remote"`). /// ///
/// @@ -13679,29 +14609,52 @@ pub struct SessionLimitPredictionTierOption { ///
#[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. +pub struct RemoteSessionMetadataValue { + /// Most recent working directory context. #[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, + pub context: Option, + /// Host-supplied human description of what the session is doing right now ("running tests", "waiting for approval"). Optional in the protocol and absent on hosts that do not publish it, so never rely on it -- it enriches `hostStatus`, it does not replace it. + #[serde(skip_serializing_if = "Option::is_none")] + pub host_activity: Option, + /// Live status as the owning host reports it in its session listing, so a row for a session running elsewhere can show that it is running. Absent for hosts that publish no such status (the cloud task managers), which read as idle. + #[serde(skip_serializing_if = "Option::is_none")] + pub host_status: 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, } -/// Parameters for predicting an AI-credit session limit. Omitting `modelId` uses the session's currently selected model. +/// Repository context for the remote session. /// ///
/// @@ -13711,32 +14664,36 @@ pub struct SessionLimitPredictionDetails { ///
#[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. +pub struct RemoteSessionRepository { + /// Optional branch associated with the remote session. #[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, + 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. For the same capability independent of sandboxing, and matched to the credential's GitHub host, see `shell.credentials`; the two are additive. +/// +///
+/// +/// **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 SessionLimitPredictionResultUnavailable { - pub kind: SessionLimitPredictionResultUnavailableKind, - /// Reason no prediction is available. - pub reason: SessionLimitPredictionUnavailableReason, +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 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 git: Option, } -/// Sessions matching the filter, ordered most-recently-modified first. +/// macOS seatbelt experimental options. /// ///
/// @@ -13746,12 +14703,13 @@ pub struct SessionLimitPredictionResultUnavailable { ///
#[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 SandboxConfigUserPolicyExperimentalSeatbelt { + /// Whether the macOS seatbelt profile may access the keychain. + #[serde(skip_serializing_if = "Option::is_none")] + pub keychain_access: Option, } -/// Optional filter applied to the returned sessions +/// Platform-specific experimental policy fields. /// ///
/// @@ -13761,22 +14719,13 @@ pub struct SessionList { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionListFilter { - /// Match sessions whose context.branch equals this value +pub struct SandboxConfigUserPolicyExperimental { + /// macOS seatbelt experimental options. #[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 seatbelt: Option, } -/// Queued repo-level startup prompts and the total hook command count after loading. +/// Filesystem rules to merge into the base policy. /// ///
/// @@ -13786,49 +14735,22 @@ 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 SandboxConfigUserPolicyFilesystem { + /// Whether to clear the policy when the session exits. #[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 + pub clear_policy_on_exit: Option, + /// Paths explicitly denied. #[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 denied_paths: Option>, + /// Paths granted read-only access. #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, - /// Repository identifier in 'owner/repo' or 'org/project/repo' format, if any + pub readonly_paths: Option>, + /// Paths granted read/write access. #[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 readwrite_paths: Option>, } -/// Point-in-time snapshot of slow-changing session identifier and state fields +/// HTTP proxy configuration for sandboxed traffic. /// ///
/// @@ -13838,45 +14760,40 @@ 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). +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 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 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; an explicit port must be between 1 and 65535. 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 proxy 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 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 username: Option, +} + +/// Network rules to merge into the base 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 SandboxConfigUserPolicyNetwork { + /// Whether traffic to local/loopback addresses is allowed. #[serde(skip_serializing_if = "Option::is_none")] - pub remote_metadata: Option, - /// Currently selected model identifier, if any + pub allow_local_network: Option, + /// Whether outbound network traffic is allowed at all. #[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 allow_outbound: Option, + /// HTTP proxy for sandboxed process traffic. Linux restricts egress to the proxy endpoint, requires that endpoint to be reachable over IPv4 (the `[::]` dual-stack wildcard is accepted and routed through the IPv4 gateway), and does not support proxy credentials. macOS relies on applications honoring proxy environment variables. Windows also configures a per-AppContainer WinHTTP proxy, but enforcement depends on the application's networking stack. Configure supported credentials in the separate `username` and `password` fields. A credential-free http:// loopback URL uses the localhost proxy form, while an https:// or authenticated loopback URL uses the URL form. #[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 proxy: Option, } -/// Cost-category metadata for a CAPI model. +/// macOS seatbelt-specific options. /// ///
/// @@ -13886,12 +14803,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 SandboxConfigUserPolicySeatbelt { + /// Whether the macOS seatbelt profile may access the keychain. + #[serde(skip_serializing_if = "Option::is_none")] + pub keychain_access: Option, } -/// The list of models available to this session. +/// User-managed sandbox policy fragment merged into the auto-discovered base policy. /// ///
/// @@ -13901,18 +14819,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 SandboxConfigUserPolicy { + /// Deprecated legacy location for `seatbelt`; read only when the top-level `seatbelt` is absent. #[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 experimental: Option, + /// Filesystem rules to merge into the base policy. #[serde(skip_serializing_if = "Option::is_none")] - pub quota_snapshots: Option>, + 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, } -/// Source descriptor for a `sessions.open` content-exclusion rule, with source name and type. +/// Resolved sandbox configuration. /// ///
/// @@ -13922,12 +14844,24 @@ 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 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 to auto-grant read access to tool directories discovered on PATH and in toolchain environment variables (GOROOT, JAVA_HOME, VIRTUAL_ENV, and similar), and to common developer-tool caches, config, and toolchains. Writable grants cover scratch caches, the Unix GitHub CLI cache, and Cargo's registry, git store, and lock/tracker files. A relocated CARGO_HOME gets the same narrow split: registry and git are read-write; bin is read-only; the home root, config.toml, and credentials.toml stay ungranted. Set to false to disable every grant listed above; user-installed toolchains and caches then need explicit userPolicy.filesystem readonlyPaths and readwritePaths entries. The working directory (see addCurrentWorkingDirectory), temporary storage, session log paths, and system locations follow their own rules and stay granted. Default: true (enabled by default; set to false to opt out). + #[serde(skip_serializing_if = "Option::is_none")] + pub allow_dev_tool_access: Option, + /// Credential-injection capability flags. + #[serde(skip_serializing_if = "Option::is_none")] + 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 user_policy: Option, } -/// Single content-exclusion rule supplied to `sessions.open` options, with paths, match conditions, and source. +/// Managed sandbox enforcement state for a session. /// ///
/// @@ -13937,17 +14871,17 @@ pub struct SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource { ///
#[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>, +pub struct SandboxEnforcementStatus { + /// Whether an enforcement failure has permanently blocked the session. + pub blocked: bool, + /// The first sandbox enforcement failure that blocked the session. #[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 reason: Option, + /// Whether the effective managed policy requires an available sandbox backend. + pub required: bool, } -/// Content-exclusion policy supplied to `sessions.open` options, with rules, last-updated data, and scope. +/// Register an absolute-time scheduled prompt. /// ///
/// @@ -13957,15 +14891,20 @@ 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 ScheduleAddAtRequest { + /// Epoch milliseconds when the prompt should fire. + pub at: i64, + /// Optional display-only prompt label. + #[serde(skip_serializing_if = "Option::is_none")] + 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 recurring: Option, } -/// A host-provided script sourced before each built-in shell command when its shell target matches the active shell. +/// Register a cron scheduled prompt. /// ///
/// @@ -13975,14 +14914,23 @@ pub struct SessionOpenOptionsAdditionalContentExclusionPolicy { ///
#[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, +pub struct ScheduleAddCronRequest { + /// 5-field cron expression. + pub cron: String, + /// Optional display-only prompt label. + #[serde(skip_serializing_if = "Option::is_none")] + 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 recurring: Option, + /// IANA timezone for evaluating the cron expression. + #[serde(skip_serializing_if = "Option::is_none")] + pub tz: Option, } -/// Per-session settings for built-in shell tools. +/// Register a relative-interval scheduled prompt. /// ///
/// @@ -13992,29 +14940,20 @@ pub struct ShellInitScript { ///
#[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. +pub struct ScheduleAddRequest { + /// Optional display-only prompt label. #[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`. + 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 process_flags: Option>, + pub recurring: Option, } -/// Session construction options. +/// Scheduled prompt entry with ID, timing (`intervalMs`, `cron`, or `at`), prompt text, recurrence, and next run time. /// ///
/// @@ -14024,237 +14963,4253 @@ pub struct ShellOptions { ///
#[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 ScheduleEntry { + /// Absolute fire time (epoch milliseconds) for a one-shot calendar schedule. #[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`). + pub at: Option, + /// 5-field cron expression for a recurring calendar schedule, evaluated in `tz`. #[serde(skip_serializing_if = "Option::is_none")] - pub additional_directories: Option>, - /// Runtime context discriminator for agent filtering. + 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 agent_context: Option, - /// Whether to include instructions from every MCP server in the system prompt instead of only allowlisted servers. + 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 allow_all_mcp_server_instructions: Option, - /// Whether ask_user is explicitly disabled. + 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 ask_user_disabled: Option, - /// Initial authentication info for the session. + pub self_paced: Option, + /// IANA timezone the `cron` expression is evaluated in. #[serde(skip_serializing_if = "Option::is_none")] - pub auth_info: Option, - /// Allowlist of available tool names. + 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 available_tools: Option>, - /// Options scoped to the built-in CAPI (Copilot API) provider. + pub entry: Option, + /// User-facing validation error, when registration failed. #[serde(skip_serializing_if = "Option::is_none")] - pub capi: Option, - /// Structured client kind used for runtime behavior gates. + 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 client_kind: Option, - /// Identifier of the client driving the session. + 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 client_name: Option, - /// Whether commit-message coauthor trailers are enabled. + 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 coauthor_enabled: Option, - /// Override Copilot configuration directory. + 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 config_dir: Option, - /// Whether auto-mode continuation is enabled. + 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 continue_on_auto_mode: Option, - /// Override URL for the Copilot API endpoint. + pub(crate) billable: Option, + /// If provided, this is shown in the timeline instead of `prompt` #[serde(skip_serializing_if = "Option::is_none")] - pub copilot_url: Option, - /// Whether custom agents default to local-only execution. + 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 custom_agents_local_only: Option, - /// Parent engagement ID for detached child telemetry rollup. + 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 detached_from_spawning_parent_engagement_id: Option, - /// Parent session ID for detached child telemetry rollup. + 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 detached_from_spawning_parent_session_id: Option, - /// Instruction source IDs disabled for this session. + 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 disabled_instruction_sources: Option>, - /// Skill IDs disabled 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 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 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 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. + pub request_headers: Option>, + /// W3C Trace Context traceparent header for distributed tracing of this agent turn #[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. + pub traceparent: Option, + /// W3C Trace Context tracestate header for distributed tracing #[serde(skip_serializing_if = "Option::is_none")] - pub enable_managed_settings: Option, - /// Whether on-demand custom instruction discovery is enabled. + 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 enable_on_demand_instruction_discovery: Option, - /// Whether shell-script safety heuristics are enabled. + 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 enable_script_safety: Option, - /// Whether model responses stream as delta events. + 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 enable_streaming: Option, - /// How MCP server environment values are interpreted. + 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 env_value_mode: Option, - /// Override directory for session event logs. + pub billable: Option, + /// If provided, this is shown in the timeline instead of `prompt` #[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. + 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 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. + 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 excluded_builtin_agents: Option>, - /// Denylist of tool names. + 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 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 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 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(crate) exp_assignments: Option, - /// Feature-flag values resolved by the host. + pub(crate) source: Option, + /// W3C Trace Context traceparent header for distributed tracing of this agent turn #[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 traceparent: Option, + /// W3C Trace Context tracestate header for distributed tracing #[serde(skip_serializing_if = "Option::is_none")] - pub included_builtin_agents: Option>, - /// Installed plugins visible to the session. + 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 installed_plugins: Option>, - /// Stable integration identifier for analytics. + 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 integration_id: Option, - /// Whether experimental behavior is enabled. + 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 is_experimental_mode: Option, - /// Whether interactive shell sessions are logged. + 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 log_interactive_shells: Option, - /// Identifier sent to LSP-style integrations. + pub argument_hint: Option, + /// Canonical slash command name used to invoke the skill, without the leading '/' #[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 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 max_inline_binary_bytes: Option, - /// Memory configuration for this session. + pub path: Option, + /// The project path this skill belongs to (only for project/inherited skills) #[serde(skip_serializing_if = "Option::is_none")] - pub memory: Option, - /// Initial model identifier. + 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 model: Option, - /// Initial model capability overrides. + 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, +} + +/// Current authentication information, or null when no authentication is 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 SessionAuthInfoResult { + /// Snapshot of the authenticated user's Copilot subscription info, if known #[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. - /// - ///
+ pub copilot_user: Option, + /// Name of the environment variable that supplied the credential, when applicable + #[serde(skip_serializing_if = "Option::is_none")] + pub env_var: Option, + /// Authentication host + pub host: String, + /// Authenticated login, when available + #[serde(skip_serializing_if = "Option::is_none")] + pub login: Option, + /// Opaque SDK GitHub credential registration backing this identity. Routing metadata only; never a credential. + #[serde(skip_serializing_if = "Option::is_none")] + pub registration_id: Option, + /// Authentication type + pub r#type: AuthInfoType, +} + +/// Internal GitHub login parameters. +/// +///
+/// +/// **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 SessionAuthLoginRequest { + /// GitHub host URL + pub host: String, + /// GitHub login + pub login: String, + /// Whether to persist the token after login + #[serde(skip_serializing_if = "Option::is_none")] + pub persist: Option, + /// GitHub authentication token + pub token: String, +} + +/// Parameters identifying a GitHub authentication to log out. +/// +///
+/// +/// **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 SessionAuthLogoutUserRequest { + /// Authentication information to log out + pub auth_info: AuthInfo, +} + +/// 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, +} + +/// Parameters for switching the session's active authentication. +/// +///
+/// +/// **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 SessionAuthSwitchRequest { + /// Authentication information to activate + pub auth_info: AuthInfo, + /// Optional token paired with the authentication information + #[serde(skip_serializing_if = "Option::is_none")] + pub token: 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 { + /// Machine-readable classification of the transaction failure. + pub error_class: SessionFsSqliteTransactionErrorClass, + /// Human-readable transaction failure message. + 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, + /// Ordered SQL statements to execute in one transaction. + 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 { + /// Classified transaction failure, when execution did not succeed. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// Per-statement query results in input order. + 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, + /// Absolute path of the marketplace directory a live plugin was resolved from. Present only on live, never-persisted records — those synthesized at session start for a directory/local marketplace, whose cache_path points at the real plugin directory on disk rather than a copy under the installed-plugins cache. Its presence is what marks a record as live, and no record carrying it is ever written to the persisted installedPlugins key. + #[serde(rename = "installed_from", skip_serializing_if = "Option::is_none")] + pub installed_from: Option, + /// 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 { + /// Optional repository-relative path to the plugin. + #[serde(skip_serializing_if = "Option::is_none")] + pub path: Option, + /// Optional Git ref to resolve. + #[serde(skip_serializing_if = "Option::is_none")] + pub r#ref: Option, + /// GitHub repository in `owner/repo` form. + 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 { + /// Local filesystem path to the plugin. + 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 { + /// Optional source-relative path to the plugin. + #[serde(skip_serializing_if = "Option::is_none")] + pub path: Option, + /// Optional Git ref to resolve. + #[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, + /// URL of the plugin source. + 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, + /// Semantic usage tier. + 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 { + /// Prediction result variant discriminator. + pub kind: SessionLimitPredictionResultAvailableKind, + /// Predicted session limit details. + pub prediction: SessionLimitPredictionDetails, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionLimitPredictionResultUnavailable { + /// Prediction result variant discriminator. + 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. `allow-auto-only` blocks full allow-all but permits advisory auto-approval. Any other value is accepted rather than failing the session, but is enforced as `disable`: the key is only present to restrict something, so a mode this runtime cannot interpret fails closed to the most restrictive one it knows. Omit the key entirely to impose no restriction. + #[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 { + /// Managed permission policy injected by the SDK host. + #[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 { + /// CAPI model identifier. + pub id: String, + /// Cost category assigned to the model. + 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 { + /// Name of the policy source. + pub name: String, + /// Type of the policy source. + 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 { + /// Conditions of which at least one must match. + #[serde(skip_serializing_if = "Option::is_none")] + pub if_any_match: Option>, + /// Conditions none of which may match. + #[serde(skip_serializing_if = "Option::is_none")] + pub if_none_match: Option>, + /// Path patterns covered by this rule. + 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 { + /// Opaque policy update timestamp supplied by the host. + #[serde(rename = "last_updated_at")] + pub last_updated_at: serde_json::Value, + /// Content-exclusion rules to apply. + pub rules: Vec, + /// Allowed values for the `SessionOpenOptionsAdditionalContentExclusionPolicyScope` enumeration. + pub scope: SessionOpenOptionsAdditionalContentExclusionPolicyScope, +} + +/// Command-scoped GitHub credential injection for the shell commands an agent runs. +/// +/// Each channel is opt-in and independent, and injection is scoped to the individual command +/// spawn: the credential is resolved from the session's *current* authentication at every spawn +/// and reaches only spawns whose script actually invokes `git` or `gh`. Because nothing is +/// retained between spawns, replacing the session credential (`session.gitHubAuth.setCredentials`) +/// changes what the next spawned command presents — which seeding a credential into the runtime +/// process's own environment cannot do, since a child's environment is fixed at `exec`. +/// +/// The credential is matched to the host it authenticates to, so a github.com credential is never +/// presented to a GitHub Enterprise host and vice versa. Where a channel cannot express that +/// boundary it injects nothing rather than crossing it -- see `gh` below. +/// +/// This is independent of `sandboxConfig`: it is a decision about which identity the agent +/// presents, not about what the agent may touch, and it works on every platform whether or not +/// an OS sandboxing backend is available. `sandboxConfig.auth` remains the sandbox-scoped +/// spelling and is additive with this one. +/// +///
+/// +/// **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 ShellCredentials { + /// Whether to authenticate the agent's `gh` commands as the session's GitHub credential, by + /// exporting `GH_TOKEN` to a spawn that runs `gh`. Any inherited `gh` credential is removed from + /// spawns that do not, so the credential stays command-scoped. + /// + /// Applies to a github.com credential only. `gh` picks its credential variable from the host a + /// command targets rather than the one the credential belongs to, and the command can choose that + /// target, so `GH_ENTERPRISE_TOKEN` would offer a single-tenant enterprise credential to every + /// other enterprise host. A session whose credential is enterprise-scoped therefore runs `gh` + /// unauthenticated; its `git` commands are unaffected, because `http..extraheader` is scoped + /// to one host by construction. Default: false (opt-in). + #[serde(skip_serializing_if = "Option::is_none")] + pub gh: Option, + /// Whether to authenticate the agent's `git` commands as the session's GitHub credential, by + /// injecting an `http..extraheader` (plus `insteadOf` rewrites so SSH-spelled remotes for + /// that host use the authenticated HTTPS transport). Applied only to a spawn that runs a + /// remote-contacting `git` subcommand. Default: false (opt-in). + #[serde(skip_serializing_if = "Option::is_none")] + pub git: Option, +} + +/// 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 { + /// Command-scoped GitHub credential injection for shell commands. + #[serde(skip_serializing_if = "Option::is_none")] + pub credentials: Option, + /// 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). Conventional `.github/skills/` and `.github/agents/` definitions under each directory also join the session's project catalogs when their existing subsystem gates are enabled: added-root skills require both `enableConfigDiscovery` and effective `enableSkills`; added-root agents require `enableConfigDiscovery`. Supplying a directory therefore activates configuration from it and should be treated as a trust decision. 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 during session creation and cold resume and is not persisted, so a cold resume must re-supply the directories. + #[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 for supported Anthropic and OpenAI models, 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 skill loading is enabled. When omitted, an SDK skill provider enables skills by default. + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_skills: 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>, + /// Whether the requesting SDK session has a skill provider. The provider remains ephemeral and is never persisted in session options or history. When enableSkills is false, it remains bound but dormant and receives no callbacks. Cloud, relay, handoff, and raw sessions.open flows reject it because they cannot safely pre-register the callback handler. + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) has_skill_provider: 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>, + /// Built-in skill names to include in this session. When specified, only these runtime-bundled skills are available. Skills from other sources with the same name remain available. + #[serde(skip_serializing_if = "Option::is_none")] + pub included_builtin_skills: 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, + /// Origin of the sandbox choice. The runtime uses this only for internal telemetry provenance; managed policy is derived independently. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) sandbox_config_source: 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. Internal because function references cannot cross the JSON-RPC boundary. + #[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, +} + +/// Token authentication accepted by session.gitHubAuth.setCredentials. +/// +///
+/// +/// **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 SettableTokenAuthInfo { + /// 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, + /// 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: SettableTokenAuthInfoType, +} + +/// 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 { + /// Whether the create-pull-request tool is available. + #[serde(skip_serializing_if = "Option::is_none")] + pub create_pull_request: Option, + /// Whether the report-progress tool is available. + #[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 { + /// Whether the named settings predicate evaluated to enabled. + 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 { + /// Availability of job-specific built-in tools. + #[serde(skip_serializing_if = "Option::is_none")] + pub built_in_tool_availability: Option, + /// GitHub Actions event type for the job. + #[serde(skip_serializing_if = "Option::is_none")] + pub event_type: Option, + /// Whether this is the workflow's trigger job. + #[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 { + /// Agent service callback URL for job and progress updates. + #[serde(skip_serializing_if = "Option::is_none")] + pub callback_url: Option, + /// Default reasoning effort for the selected model. + #[serde(skip_serializing_if = "Option::is_none")] + pub default_reasoning_effort: Option, + /// Agent job identifier for the session. + #[serde(skip_serializing_if = "Option::is_none")] + pub instance_id: Option, + /// Selected model identifier. + #[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 { + /// Whether online evaluation is disabled. + #[serde(skip_serializing_if = "Option::is_none")] + pub disable_online_evaluation: Option, + /// Whether online-evaluation output-file generation is enabled. + #[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 { + /// Checked-out repository branch. + #[serde(skip_serializing_if = "Option::is_none")] + pub branch: Option, + /// Checked-out commit SHA. + #[serde(skip_serializing_if = "Option::is_none")] + pub commit: Option, + /// GitHub server host name. + #[serde(skip_serializing_if = "Option::is_none")] + pub host: Option, + /// Protocol used to access the GitHub host. + #[serde(skip_serializing_if = "Option::is_none")] + pub host_protocol: Option, + /// GitHub repository database ID. + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + /// Repository name. + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + /// GitHub repository owner database ID. + #[serde(skip_serializing_if = "Option::is_none")] + pub owner_id: Option, + /// Repository owner login. + #[serde(skip_serializing_if = "Option::is_none")] + pub owner_name: Option, + /// Number of commits in the pull request. + #[serde(skip_serializing_if = "Option::is_none")] + pub pr_commit_count: Option, + /// Whether the repository is writable. + #[serde(skip_serializing_if = "Option::is_none")] + pub read_write: Option, + /// GitHub secret-scanning service URL. + #[serde(skip_serializing_if = "Option::is_none")] + pub secret_scanning_url: Option, + /// GitHub server base URL. + #[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 { + /// Whether advisory validation is enabled. + #[serde(skip_serializing_if = "Option::is_none")] + pub advisory_enabled: Option, + /// Whether CodeQL validation is enabled. + #[serde(skip_serializing_if = "Option::is_none")] + pub codeql_enabled: Option, + /// Whether code-review validation is enabled. + #[serde(skip_serializing_if = "Option::is_none")] + pub code_review_enabled: Option, + /// Model used for code-review validation. + #[serde(skip_serializing_if = "Option::is_none")] + pub code_review_model: Option, + /// Dependabot validation timeout budget in seconds. + #[serde(skip_serializing_if = "Option::is_none")] + pub dependabot_timeout: Option, + /// Whether the memory-store tool is enabled. + #[serde(skip_serializing_if = "Option::is_none")] + pub memory_store_enabled: Option, + /// Whether the memory-vote tool is enabled. + #[serde(skip_serializing_if = "Option::is_none")] + pub memory_vote_enabled: Option, + /// Whether secret-scanning validation is enabled. + #[serde(skip_serializing_if = "Option::is_none")] + pub secret_scanning_enabled: Option, + /// General validation timeout budget in seconds. + #[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 { + /// Name of the SDK client that created the session. + #[serde(skip_serializing_if = "Option::is_none")] + pub client_name: Option, + /// Redacted job settings. + pub job: SessionSettingsJobSnapshot, + /// Redacted model routing settings. + pub model: SessionSettingsModelSnapshot, + /// Online-evaluation settings safe for SDK consumers. + pub online_evaluation: SessionSettingsOnlineEvaluationSnapshot, + /// Redacted repository and host settings. + pub repo: SessionSettingsRepoSnapshot, + /// Session start time as Unix epoch milliseconds. + #[serde(skip_serializing_if = "Option::is_none")] + pub start_time_ms: Option, + /// Session timeout in milliseconds. + #[serde(skip_serializing_if = "Option::is_none")] + pub timeout_ms: Option, + /// Redacted validation and memory-tool settings. + pub validation: SessionSettingsValidationSnapshot, + /// Agent runtime version selector copied from the session settings, such as `latest` or a runtime release identifier. + #[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 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, +} + +/// Optional source filter, metadata-load limit, and context 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 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. + #[serde(skip_serializing_if = "Option::is_none")] + pub throw_on_error: Option, +} + +/// Active session ID whose deferred repo-level hooks should be loaded. +/// +///
+/// +/// **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 SessionsLoadDeferredRepoHooksRequest { + /// Active session ID whose deferred repo-level hooks should be loaded + pub session_id: SessionId, +} + +/// Age threshold and optional flags controlling which old sessions are pruned (or simulated when dryRun is true). +/// +///
+/// +/// **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 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, +} + +/// Pagination options for reading an inactive or active local session's persisted event journal. +/// +///
+/// +/// **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 SessionsReadPersistedEventsRequest { + /// Opaque cursor returned by a previous persisted-event read. Omit on the first call. + #[serde(skip_serializing_if = "Option::is_none")] + pub cursor: Option, + /// Direction to page through persisted history. Forward starts at the beginning; backward starts with the newest events. Events in each page remain chronological. + #[serde(skip_serializing_if = "Option::is_none")] + pub direction: Option, + /// Maximum number of events to return in this batch (1–1000, default 200). + #[serde(skip_serializing_if = "Option::is_none")] + pub max: Option, + /// Session ID whose persisted event journal should be read. + pub session_id: SessionId, +} + +/// Session ID whose in-use lock should be released. +/// +///
+/// +/// **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 SessionsReleaseLockRequest { + /// Session ID whose in-use lock should be released + pub session_id: SessionId, +} + +/// 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. +/// +///
+/// +/// **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 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 defer_repo_hooks: Option, + /// Active session ID to reload hooks for + pub session_id: SessionId, +} + +/// 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. +/// +///
+/// +/// **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 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, +} + +/// Flush a session's pending events to disk. No-op when no writer exists for the session (e.g., already closed). +/// +///
+/// +/// **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 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, +} + +/// 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. +/// +///
+/// +/// **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 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, +} + +/// Parameters for attaching the remote-control singleton to 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 SessionsStartRemoteControlRequest { + /// Configuration for the runtime-managed remote-control singleton. + pub config: RemoteControlConfig, + /// Local session id to attach remote control to. + pub session_id: SessionId, +} + +/// Parameters for stopping the remote-control singleton. +/// +///
+/// +/// **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 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, +} + +/// Parameters for atomically rebinding the remote-control singleton. +/// +///
+/// +/// **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 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, +} + +/// Telemetry engagement ID for the session, 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 SessionTelemetryEngagement { + /// Current telemetry engagement ID, when available. + #[serde(skip_serializing_if = "Option::is_none")] + pub engagement_id: Option, +} + +/// Patch of mutable session options to apply to the running 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 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 models: Option>, - /// Optional human-friendly session name. + 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 skill loading is enabled. Explicit false disables every source, including a bound SDK provider; changing the value invalidates the loaded skill snapshot. When omitted, creation falls back to enableConfigDiscovery unless an SDK skill provider is registered. + #[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>, + /// Built-in skill names to include in this session. When specified, only these runtime-bundled skills are available. Skills from other sources with the same name remain available. Set to null to remove the allowlist restriction. + #[serde(skip_serializing_if = "Option::is_none")] + pub included_builtin_skills: 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, + /// Origin of the sandbox choice. The runtime uses this only for internal telemetry provenance; managed policy is derived independently. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) sandbox_config_source: 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, +} + +/// Indicates whether the session options patch was applied successfully. +/// +///
+/// +/// **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 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, +} + +/// User-requested shell execution cancellation handle. +/// +///
+/// +/// **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 ShellCancelUserRequestedRequest { + /// Request ID previously passed to executeUserRequested + pub request_id: RequestId, +} + +/// Shell command to run, with optional working directory and timeout in milliseconds. +/// +///
+/// +/// **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 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, +} + +/// Identifier of the spawned process, used to correlate streamed output and exit notifications. +/// +///
+/// +/// **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 ShellExecResult { + /// Unique identifier for tracking streamed output + pub process_id: String, +} + +/// User-requested shell command and cancellation handle. +/// +///
+/// +/// **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 ShellExecuteUserRequestedRequest { + /// Shell command to execute + pub command: String, + /// Caller-provided cancellation handle for this execution + pub request_id: RequestId, +} + +/// Identifier of a process previously returned by "shell.exec" and the signal to send. +/// +///
+/// +/// **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 ShellKillRequest { + /// Process identifier returned by shell.exec + pub process_id: String, + /// Signal to send (default: SIGTERM) + #[serde(skip_serializing_if = "Option::is_none")] + pub signal: Option, +} + +/// Indicates whether the signal was delivered; false if the process was unknown or already exited. +/// +///
+/// +/// **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 ShellKillResult { + /// Whether the signal was sent successfully + pub killed: bool, +} + +/// Parameters for shutting down 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 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, +} + +/// Skill metadata available to a session, with name, description, source, enabled/invocable state, path, plugin, 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 Skill { + /// 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 + 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 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, +} + +/// Canonical directory where skills can be discovered or created, with scope, preference, and optional project 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 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, +} + +/// Canonical locations where skills can be created so the runtime will recognize them. +/// +///
+/// +/// **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 SkillDiscoveryPathList { + /// Canonical skill create/discovery directories, in priority order + pub paths: Vec, +} + +/// Skills available to the session, with their enabled 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 SkillList { + /// Available skills + pub skills: Vec, +} + +/// Catalog-only metadata for one SDK-provided skill. The complete SKILL.md is fetched separately and lazily. +/// +///
+/// +/// **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 SkillProviderDescriptor { + /// Optional freeform argument hint used by slash-command catalogs. + #[serde(skip_serializing_if = "Option::is_none")] + pub argument_hint: Option, + /// Description used in skill catalogs without fetching content. + pub description: String, + /// Whether model invocation is disabled. Defaults to false. + #[serde(skip_serializing_if = "Option::is_none")] + pub disable_model_invocation: Option, + /// Invocation and display name. + pub name: String, + /// Whether users may invoke the skill directly. Defaults to true. + #[serde(skip_serializing_if = "Option::is_none")] + pub user_invocable: Option, +} + +/// Catalog metadata returned by an SDK session's skill provider. Catalogs are limited to 1024 descriptors and 1 MiB of aggregate 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(crate) struct SkillProviderListResult { + /// Skill descriptors in provider order. Invocation names must be unique under case-insensitive comparison. + pub skills: Vec, +} + +/// Identifies one SDK-provided skill by invocation name. +/// +///
+/// +/// **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 SkillProviderReadRequest { + /// Target session identifier + pub session_id: SessionId, + /// Invocation name of the skill to read. + pub name: String, +} + +/// Complete text-only SKILL.md content returned by an SDK session's skill provider. Related files and assets are not supported. +/// +///
+/// +/// **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 SkillProviderReadResult { + /// Complete SKILL.md text. The runtime enforces a 1 MiB UTF-8 byte limit. + pub markdown: String, +} + +/// Skill names to mark as disabled in global configuration, replacing any previous list. +/// +///
+/// +/// **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 SkillsConfigSetDisabledSkillsRequest { + /// List of skill names to disable + pub disabled_skills: Vec, +} + +/// Adds or removes a single skill from the global disabled list, leaving every other entry untouched. +/// +///
+/// +/// **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 SkillsConfigSetSkillDisabledRequest { + /// True to disable the skill, false to enable it + pub disabled: bool, + /// Name of the skill to add to or remove from the disabled list + pub name: String, +} + +/// Name of the skill to disable 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 SkillsDisableRequest { + /// Name of the skill to disable + pub name: String, +} + +/// Optional project paths and additional skill directories to include in discovery. +/// +///
+/// +/// **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 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 name: Option, - /// Custom model-provider configuration (BYOK). + pub project_paths: Option>, + /// Optional list of additional skill directory paths to include #[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 skill_directories: Option>, +} + +/// Name of the skill to enable 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 SkillsEnableRequest { + /// Name of the skill to enable + pub name: String, +} + +/// Optional project paths to enumerate. +/// +///
+/// +/// **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 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 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. + 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>, +} + +/// Skill invocation record with name, path, content, allowed tools, and turn number. +/// +///
+/// +/// **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 SkillsInvokedSkill { + /// Tools that should be auto-approved when this skill is active, captured at invocation time + #[serde(skip_serializing_if = "Option::is_none")] + pub allowed_tools: Option>, + /// Full content of the skill file + pub content: String, + /// Whether model invocation was disabled when this skill was invoked + #[serde(skip_serializing_if = "Option::is_none")] + pub disable_model_invocation: Option, + /// 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, or an empty string for an SDK-provided skill without a filesystem identity + pub path: String, +} + +/// Skills invoked during this session, ordered by invocation time (most recent last). +/// +///
+/// +/// **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 SkillsGetInvokedResult { + /// Skills invoked during this session, ordered by invocation time (most recent last) + pub skills: Vec, +} + +/// 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 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, +} + +/// +///
+/// +/// **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 SlashCommandTimelineEntry { + /// Text displayed for the timeline entry. + pub text: String, + /// Timeline entry presentation type. + pub r#type: String, + /// Optional URL associated with the timeline entry. + #[serde(skip_serializing_if = "Option::is_none")] + pub url: Option, +} + +/// +///
+/// +/// **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 SlashCommandAddTimelineEntryResult { + /// Timeline entry the host should append. + pub entry: SlashCommandTimelineEntry, + /// Discriminator for an add-timeline-entry result. + pub kind: SlashCommandAddTimelineEntryResultKind, + /// Optional text the host should prefill into the input editor. + #[serde(skip_serializing_if = "Option::is_none")] + pub prefill_input: Option, + /// Whether command execution changed persisted runtime settings. + #[serde(skip_serializing_if = "Option::is_none")] + pub runtime_settings_changed: Option, +} + +/// Slash-command invocation result that submits an agent prompt, with display prompt, optional mode, optional user-facing notice, and settings-change 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 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, +} + +/// Slash-command invocation result indicating completion, with optional message and settings-change 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 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, + /// Optional target session mode applied without submitting an agent prompt #[serde(skip_serializing_if = "Option::is_none")] - pub reasoning_effort: Option, - /// Initial reasoning summary mode for supported model clients. + pub mode: Option, + /// True when the invocation mutated user runtime settings; consumers caching settings should refresh #[serde(skip_serializing_if = "Option::is_none")] - pub reasoning_summary: Option, - /// Telemetry-only remote-defaulted flag. + pub runtime_settings_changed: Option, +} + +/// Slash-command invocation result containing text output plus Markdown/ANSI rendering flags. +/// +///
+/// +/// **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 SlashCommandTextResult { + /// Text result discriminator + pub kind: SlashCommandTextResultKind, + /// Whether text contains Markdown #[serde(skip_serializing_if = "Option::is_none")] - pub remote_defaulted_on: Option, - /// Telemetry-only remote exporting flag. + pub markdown: Option, + /// Whether ANSI sequences should be preserved #[serde(skip_serializing_if = "Option::is_none")] - pub remote_exporting: Option, - /// Whether this session supports remote steering. + 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 remote_steerable: Option, - /// Whether the host is an interactive UI. + pub runtime_settings_changed: Option, + /// Text output for the client to render + pub text: String, +} + +/// Selectable slash-command subcommand option with name, description, and optional group 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 SlashCommandSelectSubcommandOption { + /// Human-readable description of the subcommand + pub description: String, + /// Optional group label for organizing options #[serde(skip_serializing_if = "Option::is_none")] - pub running_in_interactive_mode: Option, - /// Resolved sandbox configuration. + pub group: Option, + /// Subcommand name to invoke + pub name: String, +} + +/// Slash-command invocation result asking the client to present subcommand options for a parent command. +/// +///
+/// +/// **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 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 sandbox_config: Option, - /// Capabilities enabled for this session. + pub runtime_settings_changed: Option, + /// Human-readable title for the selection UI + pub title: String, +} + +/// +///
+/// +/// **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 SlashCommandModelPickerDialog { + /// Discriminator for a model-picker dialog. + pub kind: SlashCommandModelPickerDialogKind, + /// Model that should be enabled before it can be selected. #[serde(skip_serializing_if = "Option::is_none")] - pub session_capabilities: Option>, - /// Optional stable session identifier to use for a new session. + pub model_to_enable: Option, + /// Settings scope the picker should modify. #[serde(skip_serializing_if = "Option::is_none")] - pub session_id: Option, - /// Initial session limits. + pub scope: Option, + /// Model-selection target represented by the picker. #[serde(skip_serializing_if = "Option::is_none")] - pub session_limits: Option, - /// Per-session settings for built-in shell tools. + pub target: Option, +} + +/// +///
+/// +/// **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 SlashCommandShowDialogResult { + /// Dialog the host should display. + pub dialog: SlashCommandModelPickerDialog, + /// Discriminator for a show-dialog result. + pub kind: SlashCommandShowDialogResultKind, + /// Whether command execution changed persisted runtime settings. #[serde(skip_serializing_if = "Option::is_none")] - pub shell: Option, - /// Use shell.initProfile instead. Shell init profile. - #[doc(hidden)] - #[deprecated] + pub runtime_settings_changed: Option, +} + +/// +///
+/// +/// **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 SlashCommandSetModelResult { + /// Discriminator for a set-model result. + pub kind: SlashCommandSetModelResultKind, + /// Model selected by the command. + pub model: String, + /// Reasoning effort selected for the model. #[serde(skip_serializing_if = "Option::is_none")] - pub shell_init_profile: Option, - /// PowerShell process flags applied to built-in and user-requested shell commands. + pub reasoning_effort: Option, + /// Repository settings scope modified by the command. #[serde(skip_serializing_if = "Option::is_none")] - pub shell_process_flags: Option>, - /// Additional directories to search for skills. + pub repo_scope: Option, + /// User-settings snapshot to restore if the host cancels the model switch. #[serde(skip_serializing_if = "Option::is_none")] - pub skill_directories: Option>, - /// Whether to skip custom instruction sources. + pub revert_on_cancel: Option, + /// Whether command execution changed persisted runtime settings. #[serde(skip_serializing_if = "Option::is_none")] - pub skip_custom_instructions: Option, - /// Optional trajectory output file path. + pub runtime_settings_changed: Option, + /// Settings scope modified by the command. #[serde(skip_serializing_if = "Option::is_none")] - pub trajectory_file: Option, - /// Initial output verbosity level for supported models. + pub scope: Option, + /// User-facing warning produced while selecting the model. #[serde(skip_serializing_if = "Option::is_none")] - pub verbosity: Option, - /// Working directory to anchor the session. + pub warning: Option, +} + +/// +///
+/// +/// **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 SlashCommandSetPlanModelResult { + /// Discriminator for a set-plan-model result. + pub kind: SlashCommandSetPlanModelResultKind, + /// User-facing confirmation message for the plan-model selection. + pub message: String, + /// Dedicated model selected for plan mode. #[serde(skip_serializing_if = "Option::is_none")] - pub working_directory: Option, - /// Pre-resolved working-directory context for session startup. + pub plan_model: Option, + /// Whether command execution changed persisted runtime settings. #[serde(skip_serializing_if = "Option::is_none")] - pub working_directory_context: Option, + pub runtime_settings_changed: Option, } -/// Parameters for creating a new local session. +/// Subagent model, reasoning effort, and context tier settings /// ///
/// @@ -14264,18 +19219,22 @@ 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 SubagentSettingsEntry { + /// Context tier override for matching subagents #[serde(skip_serializing_if = "Option::is_none")] - pub emit_start: Option, - /// Create a new local session. - pub kind: SessionsOpenCreateKind, - /// Session construction options. + pub context_tier: Option, + /// Reasoning effort override for matching subagents #[serde(skip_serializing_if = "Option::is_none")] - pub options: Option, + pub effort_level: Option, + /// Model override for matching subagents + #[serde(skip_serializing_if = "Option::is_none")] + pub model: Option, + /// Whether the configured model strategy is preferred or required + #[serde(skip_serializing_if = "Option::is_none")] + pub model_policy: Option, } -/// Parameters for resuming a specific local session. +/// Subagent settings to apply, or null to clear the live session override /// ///
/// @@ -14285,23 +19244,22 @@ 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. +pub struct SubagentSettings { + /// Per-agent settings keyed by subagent agent_type #[serde(skip_serializing_if = "Option::is_none")] - pub options: Option, - /// Whether to emit session.resume after loading. Defaults to true. + pub agents: Option>, + /// Names of subagents the user has turned off; they cannot be dispatched #[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. + 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 suppress_resume_workspace_metadata_writeback: Option, + 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, } -/// Parameters for resuming the most relevant local session. +/// Tracked background agent task metadata, including IDs, status, timing, agent type, prompt, model, result, and latest response. /// ///
/// @@ -14311,21 +19269,62 @@ 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 TaskAgentInfo { + /// ISO 8601 timestamp when the current active period began #[serde(skip_serializing_if = "Option::is_none")] - pub context: Option, - /// Resume the most relevant existing local session. - pub kind: SessionsOpenResumeLastKind, - /// Session resume options. + pub active_started_at: Option, + /// Accumulated active execution time in milliseconds #[serde(skip_serializing_if = "Option::is_none")] - pub options: Option, - /// Suppress workspace.yaml metadata writeback when resuming from an incidental cwd. + 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 suppress_resume_workspace_metadata_writeback: Option, + 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, + /// Friendly, non-unique name intended for display + #[serde(skip_serializing_if = "Option::is_none")] + pub display_name: Option, + /// 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, } -/// Parameters for attaching to an already-active session by ID. +/// Timestamped display line for task progress output or recent agent activity. /// ///
/// @@ -14335,14 +19334,14 @@ 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 session_id: SessionId, +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, } -/// Parameters for connecting to a live remote session. +/// Progress snapshot for an agent task, with recent activity lines and optional latest intent. /// ///
/// @@ -14352,20 +19351,17 @@ 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 TaskAgentProgress { + /// The most recent intent reported by the agent #[serde(skip_serializing_if = "Option::is_none")] - pub repository: Option, + pub latest_intent: Option, + /// Recent tool execution events converted to display lines + pub recent_activity: Vec, + /// Progress kind + pub r#type: TaskAgentProgressType, } -/// Parameters for creating a new cloud session. +/// Task completion notification with summary from the agent /// ///
/// @@ -14375,25 +19371,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)] +pub struct TaskCompleteData { + /// Active autopilot objective ID evaluated by the completion reviewer #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) on_task_created: Option, - /// Session options for cloud session creation. + pub objective_id: Option, + /// Semantic completion decision. Absent on legacy events and invalid tool calls #[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). + 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 owner: Option, - /// Repository for the cloud session. + 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 repository: Option, + pub success: Option, + /// Summary of the completed task, provided by the agent + #[serde(skip_serializing_if = "Option::is_none")] + pub summary: Option, } -/// Parameters for fetching a remote session and handing it off to a new local session. /// ///
/// @@ -14403,48 +19398,30 @@ 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)] +pub struct TaskCompletionDecision { + /// Objective eligibility token captured when the decision was evaluated. #[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)] + pub completion_eligibility_token: Option, + /// Whether completion was accepted after the reviewer-rejection budget was exhausted. #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) on_progress: Option, - /// Session construction options for the new local session. + pub completion_rejection_budget_exhausted: Option, + /// Active autopilot objective evaluated by the completion reviewer. #[serde(skip_serializing_if = "Option::is_none")] - pub options: Option, - /// Task type determines the handoff strategy (CCA fetches events; CLI prepares a transient session). + pub objective_id: Option, + /// Semantic result of evaluating the task completion request. + pub outcome: TaskCompletionOutcome, + /// Rationale for the completion decision, when one is available. #[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. + pub reason: Option, + /// Whether the rationale was derived from completion-reviewer output. #[serde(skip_serializing_if = "Option::is_none")] - pub message: Option, - /// Step status. - pub status: SessionsOpenProgressStatus, - /// Handoff step. - pub step: SessionsOpenProgressStep, + pub reviewer_derived: Option, + /// Information-flow metadata captured from the completion reviewer. + #[serde(skip_serializing_if = "Option::is_none")] + pub reviewer_result_meta: Option, } -/// Result of opening a session. +/// Background tasks currently tracked by the session. /// ///
/// @@ -14454,31 +19431,12 @@ 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 TaskList { + /// Currently tracked tasks + pub tasks: Vec, } -/// Outcome of the prune operation: deleted IDs, dry-run candidates, skipped IDs, total bytes freed, and the dry-run flag. +/// Identifier of the background task to cancel. /// ///
/// @@ -14488,20 +19446,12 @@ 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 TasksCancelRequest { + /// Task identifier + pub id: String, } -/// Session IDs to close, deactivate, and delete from disk. +/// Indicates whether the background task was successfully cancelled. /// ///
/// @@ -14511,12 +19461,12 @@ 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 TasksCancelResult { + /// Whether the task was successfully cancelled + pub cancelled: bool, } -/// Session IDs to test for live in-use locks. +/// The first sync-waiting task that can currently be promoted to background mode. /// ///
/// @@ -14526,12 +19476,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 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, } -/// Session IDs from the input set that are currently in use by another process. +/// Identifier of the background task to fetch progress for. /// ///
/// @@ -14541,12 +19492,12 @@ 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 TasksGetProgressRequest { + /// Task identifier (agent ID or shell ID) + pub id: String, } -/// Session ID to close. +/// Progress information for the task, or null when no task with that ID is tracked. /// ///
/// @@ -14556,12 +19507,12 @@ 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 TasksGetProgressResult { + /// Progress information for the task, discriminated by type. Returns null when no task with this ID is currently tracked. + pub progress: Option, } -/// 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. +/// Tracked shell task metadata, including ID, command, status, timing, attachment/execution mode, log path, and PID. /// ///
/// @@ -14571,9 +19522,39 @@ pub struct SessionsCloseRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsCloseResult {} +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, +} -/// Session ID to delete from disk. +/// Progress snapshot for a shell task, with recent stdout/stderr output and optional process ID. /// ///
/// @@ -14583,15 +19564,17 @@ pub struct SessionsCloseResult {} ///
#[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 +pub struct TaskShellProgress { + /// Process ID when available #[serde(skip_serializing_if = "Option::is_none")] - pub session_path: Option, + pub pid: Option, + /// Recent stdout/stderr lines from the running shell command + pub recent_output: String, + /// Progress kind + pub r#type: TaskShellProgressType, } -/// Session metadata records to enrich with summary and context information. +/// The promoted task as it now exists in background mode, omitted if no promotable task was waiting. /// ///
/// @@ -14601,12 +19584,13 @@ pub struct SessionsDeleteRequest { ///
#[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 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, } -/// New auth credentials to install on the session. Omit to leave credentials unchanged. +/// Identifier of the task to promote to background mode. /// ///
/// @@ -14616,13 +19600,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 TasksPromoteToBackgroundRequest { + /// Task identifier + pub id: String, } -/// Indicates whether the credential update succeeded. +/// Indicates whether the task was successfully promoted to background mode. /// ///
/// @@ -14632,15 +19615,12 @@ 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 TasksPromoteToBackgroundResult { + /// Whether the task was successfully promoted to background mode + pub promoted: bool, } -/// Availability of built-in job tools surfaced to boundary consumers. +/// 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. /// ///
/// @@ -14650,14 +19630,9 @@ 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, - #[serde(skip_serializing_if = "Option::is_none")] - pub report_progress: Option, -} +pub struct TasksRefreshResult {} -/// Named Rust-owned settings predicate to evaluate for this session. +/// Identifier of the completed or cancelled task to remove from tracking. /// ///
/// @@ -14667,15 +19642,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 TasksRemoveRequest { + /// Task identifier + pub id: String, } -/// Result of evaluating a Rust-owned settings predicate. +/// Indicates whether the task was removed. False when the task does not exist or is still running/idle. /// ///
/// @@ -14685,11 +19657,12 @@ pub struct SessionSettingsEvaluatePredicateRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionSettingsEvaluatePredicateResult { - pub enabled: bool, +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, } -/// Redacted job settings for a session. The job nonce is excluded. +/// Identifier of the target agent task, message content, and optional sender agent ID. /// ///
/// @@ -14699,16 +19672,17 @@ pub struct SessionSettingsEvaluatePredicateResult { ///
#[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, +pub struct TasksSendMessageRequest { + /// Agent ID of the sender, if sent on behalf of another agent #[serde(skip_serializing_if = "Option::is_none")] - pub is_trigger_job: Option, + pub from_agent_id: Option, + /// Agent task identifier + pub id: String, + /// Message content to send to the agent + pub message: String, } -/// Redacted model routing settings for a session. +/// Indicates whether the message was delivered, with an error message when delivery failed. /// ///
/// @@ -14718,18 +19692,15 @@ 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 TasksSendMessageResult { + /// Error message if delivery failed #[serde(skip_serializing_if = "Option::is_none")] - pub model: Option, + pub error: Option, + /// Whether the message was successfully delivered or steered + pub sent: bool, } -/// Online-evaluation settings safe to expose across the SDK boundary. +/// Agent type, prompt, name, and optional description and model override for the new task. /// ///
/// @@ -14739,14 +19710,22 @@ pub struct SessionSettingsModelSnapshot { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionSettingsOnlineEvaluationSnapshot { +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 disable_online_evaluation: Option, + pub description: Option, + /// Optional model override #[serde(skip_serializing_if = "Option::is_none")] - pub enable_online_evaluation_output_file: Option, + pub model: Option, + /// Friendly, non-unique name used when displaying the agent + pub name: String, + /// Task prompt for the agent + pub prompt: String, } -/// Redacted repository and GitHub host settings for a session. +/// Identifier assigned to the newly started background agent task. /// ///
/// @@ -14756,34 +19735,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 TasksStartAgentResult { + /// Generated agent ID for the background task + pub agent_id: String, } -/// Redacted validation and memory-tool settings for a 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). /// ///
/// @@ -14793,28 +19750,9 @@ 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 TasksWaitForPendingResult {} -/// Redacted, serializable view of session runtime settings for SDK boundary consumers. Secrets and raw feature flags are intentionally excluded. +/// Feature override key/value pairs to attach to subsequent telemetry events from this session. /// ///
/// @@ -14824,23 +19762,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 TelemetrySetFeatureOverridesRequest { + /// Override key/value pairs to attach to subsequent telemetry events from this session. Replaces any previously-set overrides. + pub features: HashMap, } -/// UUID prefix to resolve to a unique session ID. +/// Built-in tool metadata with identifier, optional namespaced name, description, input-parameter schema, and usage instructions. /// ///
/// @@ -14850,12 +19777,23 @@ 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 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>, } -/// Session ID matching the prefix, omitted when no unique match exists. +/// Built-in tools available for the requested model, with their parameters and instructions. /// ///
/// @@ -14865,13 +19803,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 ToolList { + /// List of available built-in tools with metadata + pub tools: Vec, } -/// GitHub task ID to look up. +/// A message injected by a tool result. /// ///
/// @@ -14881,12 +19818,14 @@ 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 ToolResultNewMessage { + /// Message content to inject after the tool result. + pub content: String, + /// Source attributed to the injected message. + pub source: String, } -/// ID of the local session bound to the given GitHub task, or omitted when none. +/// Expanded canonical result returned by a session tool. /// ///
/// @@ -14896,13 +19835,59 @@ 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 ToolResultExpanded { + /// Base64-encoded binary results returned to the model. #[serde(skip_serializing_if = "Option::is_none")] - pub session_id: Option, + pub binary_results_for_llm: Option>, + /// Sources returned by the tool that the model may cite. + #[serde(skip_serializing_if = "Option::is_none")] + pub citable_sources: Option>, + /// Structured content blocks returned to the model. + #[serde(skip_serializing_if = "Option::is_none")] + pub contents: Option>, + /// Error message for an unsuccessful execution. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// Metadata propagated with the tool result, including information-flow labels. + #[serde(skip_serializing_if = "Option::is_none")] + pub mcp_meta: Option>, + /// Messages to inject after the tool result. + #[serde(skip_serializing_if = "Option::is_none")] + pub new_messages: Option>, + /// Whether post-tool-use failure hooks have already processed this result. + #[serde(skip_serializing_if = "Option::is_none")] + pub post_tool_use_failure_hooks_processed: Option, + /// Execution outcome classification. + pub result_type: ToolResultType, + /// Detailed log content available for session display. + #[serde(skip_serializing_if = "Option::is_none")] + pub session_log: Option, + /// Skill invocation metadata produced by the tool. + #[serde(skip_serializing_if = "Option::is_none")] + pub skill_invocation: Option, + /// Whether large-output post-processing should be skipped. + #[serde(skip_serializing_if = "Option::is_none")] + pub skip_large_output_processing: Option, + /// Structured result content in addition to the model-facing text. + #[serde(skip_serializing_if = "Option::is_none")] + pub structured_content: Option, + /// Completion-review decision produced by the task-completion tool. + #[serde(skip_serializing_if = "Option::is_none")] + pub task_completion_decision: Option, + /// Text result returned to the model. + pub text_result_for_llm: String, + /// Deferred tool names made available by this result. + #[serde(skip_serializing_if = "Option::is_none")] + pub tool_references: Option>, + /// Tool-specific telemetry payload. + #[serde(skip_serializing_if = "Option::is_none")] + pub tool_telemetry: Option, + /// Optional UI resource produced by the tool. + #[serde(skip_serializing_if = "Option::is_none")] + pub ui_resource: Option, } -/// Source session identifier to fork from, optional event-ID boundary, and optional friendly name for the new session. +/// A tool name and arguments to execute through the session's native invocation pipeline. /// ///
/// @@ -14912,18 +19897,17 @@ 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. +pub struct ToolsExecuteRequest { + /// Arguments supplied to the tool. + pub arguments: serde_json::Value, + /// Name of the currently offered tool to execute. + pub name: String, + /// Optional identifier used to correlate this invocation with its tool call. #[serde(skip_serializing_if = "Option::is_none")] - pub to_event_id: Option, + pub tool_call_id: Option, } -/// Identifier and optional friendly name assigned to the newly forked session. +/// Shell-specific names and description lines used to materialize built-in shell tool descriptors. /// ///
/// @@ -14933,15 +19917,24 @@ 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 ToolsShellDescriptorConfig { + /// Additional model-facing shell description lines. + pub description_lines: Vec, + /// Human-readable shell name. + pub display_name: String, + /// Tool name used to list active shells. + pub list_shells_tool_name: String, + /// Tool name used to read shell output. + pub read_shell_tool_name: String, + /// Tool name used to start shell commands. + pub shell_tool_name: String, + /// Stable shell type identifier. + pub shell_type: String, + /// Tool name used to stop shell commands. + pub stop_shell_tool_name: String, } -/// Session ID whose board entry count should be returned. +/// Options controlling how Rust-owned built-in tool descriptors are materialized. /// ///
/// @@ -14951,12 +19944,31 @@ 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 ToolsGetBuiltinDescriptorsRequest { + /// Whether background task completion notifications are enabled. + #[serde(skip_serializing_if = "Option::is_none")] + pub background_task_notifications_enabled: Option, + /// Whether tool descriptors should include authoring metadata. + #[serde(skip_serializing_if = "Option::is_none")] + pub include_author: Option, + /// Whether descriptors should favor fewer user-intervention prompts. + #[serde(skip_serializing_if = "Option::is_none")] + pub reduce_user_intervention: Option, + /// Shell-specific names and description lines for shell tools. + #[serde(skip_serializing_if = "Option::is_none")] + pub shell_config: Option, + /// Whether the configured shell supports PowerShell 7 syntax. + #[serde(skip_serializing_if = "Option::is_none")] + pub shell_supports_power_shell7_syntax: Option, + /// Default shell timeout in milliseconds. + #[serde(skip_serializing_if = "Option::is_none")] + pub shell_timeout_ms: Option, + /// Whether semantic skill lookup is available. + #[serde(skip_serializing_if = "Option::is_none")] + pub skill_embedding_enabled: Option, } -/// Dynamic-context board entry count, when available. +/// Rust-owned built-in tool descriptors for the session. /// ///
/// @@ -14966,13 +19978,12 @@ pub struct SessionsGetBoardEntryCountRequest { ///
#[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, +pub struct ToolsGetBuiltinDescriptorsResult { + /// Built-in tool descriptors materialized for the session. + pub tools: Vec, } -/// Session ID whose event-log file path to compute. +/// Current lightweight tool metadata snapshot for the session. /// ///
/// @@ -14982,12 +19993,12 @@ 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 ToolsGetCurrentMetadataResult { + /// Current tool metadata, or null when tools have not been initialized yet + pub tools: Option>, } -/// Absolute path to the session's events.jsonl file on disk. +/// 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. /// ///
/// @@ -14997,12 +20008,9 @@ 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 ToolsInitializeAndValidateResult {} -/// Optional working-directory context used to score session relevance. +/// Optional model identifier whose tool overrides should be applied to the listing. /// ///
/// @@ -15012,13 +20020,13 @@ 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 ToolsListRequest { + /// Optional model ID — when provided, the returned tool list reflects model-specific overrides #[serde(skip_serializing_if = "Option::is_none")] - pub context: Option, + pub model: Option, } -/// Most-relevant session ID for the supplied context, or omitted when no sessions exist. +/// Complete externally implemented tool list for the calling connection. An empty list removes every tool previously supplied by that connection. /// ///
/// @@ -15028,13 +20036,12 @@ 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 - #[serde(skip_serializing_if = "Option::is_none")] - pub session_id: Option, +pub struct ToolsSetRequest { + /// Complete replacement list for the calling connection. + pub tools: Vec, } -/// Session ID whose persisted metadata should be read. +/// Empty result after replacing the calling connection's externally implemented tools. /// ///
/// @@ -15044,12 +20051,9 @@ pub struct SessionsGetLastForContextResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsGetMetadataRequest { - /// Session ID to inspect - pub session_id: SessionId, -} +pub struct ToolsSetResult {} -/// Persisted local session metadata when the session exists. +/// Task-completion tool arguments and final result used to build a label-safe session event payload. /// ///
/// @@ -15058,14 +20062,15 @@ pub struct SessionsGetMetadataRequest { /// ///
#[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, +#[serde(rename_all = "camelCase")] +pub struct ToolsTaskCompleteEventDataRequest { + /// Final expanded result returned by the task_complete tool. + pub final_result: ToolResultExpanded, + /// Arguments supplied to the completed task_complete tool call. + pub tool_args: serde_json::Value, } -/// Session ID to look up the persisted remote-steerable flag for. +/// Empty result after applying subagent settings /// ///
/// @@ -15075,12 +20080,9 @@ pub struct SessionsGetMetadataResult { ///
#[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 ToolsUpdateSubagentSettingsResult {} -/// The session's persisted remote-steerable flag, or omitted when no value has been persisted. +/// Selectable option for a UI elicitation multi-select array item, with submitted value and display label. /// ///
/// @@ -15090,13 +20092,14 @@ 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 - #[serde(skip_serializing_if = "Option::is_none")] - pub remote_steerable: Option, +pub struct UIElicitationArrayAnyOfFieldItemsAnyOf { + /// Value submitted when this option is selected. + pub r#const: String, + /// Display label for this option. + pub title: String, } -/// Map of sessionId -> on-disk size in bytes for each session's workspace directory. +/// Schema applied to each item in the array. /// ///
/// @@ -15106,12 +20109,12 @@ 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 UIElicitationArrayAnyOfFieldItems { + /// Selectable options, each with a value and a display label. + pub any_of: Vec, } -/// Limit for non-empty local session IDs. +/// Multi-select string field where each option pairs a value with a display label. /// ///
/// @@ -15121,13 +20124,29 @@ pub struct SessionSizes { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsListNonEmptySessionIdsRequest { - /// Maximum number of session IDs to return. +pub struct UIElicitationArrayAnyOfField { + /// Default values selected when the form is first shown. #[serde(skip_serializing_if = "Option::is_none")] - pub limit: Option, + 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, } -/// Recent local session IDs that contain user-visible history. +/// Schema applied to each item in the array. /// ///
/// @@ -15137,12 +20156,14 @@ pub struct SessionsListNonEmptySessionIdsRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsListNonEmptySessionIdsResult { - /// Session IDs ordered newest-first. - pub session_ids: Vec, +pub struct UIElicitationArrayEnumFieldItems { + /// Allowed string values for each selected item. + pub r#enum: Vec, + /// Type discriminator. Always "string". + pub r#type: UIElicitationArrayEnumFieldItemsType, } -/// Optional source filter, metadata-load limit, and context filter applied to the returned sessions. +/// Multi-select string field whose allowed values are defined inline. /// ///
/// @@ -15152,25 +20173,29 @@ pub struct SessionsListNonEmptySessionIdsResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsListRequest { - /// Optional filter applied to the returned sessions +pub struct UIElicitationArrayEnumField { + /// Default values selected when the form is first shown. #[serde(skip_serializing_if = "Option::is_none")] - pub filter: Option, - /// When true, include detached maintenance sessions. Defaults to false for user-facing session lists. + pub default: Option>, + /// Help text describing the field. #[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). + 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 metadata_limit: Option, - /// Which session sources to include. Defaults to `local` for backward compatibility. + pub max_items: Option, + /// Minimum number of items the user must select. #[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 min_items: Option, + /// Human-readable label for the field. #[serde(skip_serializing_if = "Option::is_none")] - pub throw_on_error: Option, + pub title: Option, + /// Type discriminator. Always "array". + pub r#type: UIElicitationArrayEnumFieldType, } -/// Active session ID whose deferred repo-level hooks should be loaded. +/// JSON Schema describing the form fields to present to the user /// ///
/// @@ -15180,12 +20205,17 @@ 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 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, } -/// Age threshold and optional flags controlling which old sessions are pruned (or simulated when dryRun is true). +/// Prompt message and JSON schema describing the form fields to elicit from the user. /// ///
/// @@ -15195,21 +20225,23 @@ 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 +pub struct UIElicitationRequest { + /// MCP request metadata. + #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] + pub meta: Option>, + /// Message describing what information is needed from the user + pub message: String, + /// Elicitation mode. Omitted and form are equivalent for structured elicitation. #[serde(skip_serializing_if = "Option::is_none")] - pub exclude_session_ids: Option>, - /// When true, named sessions (set via /rename) are also eligible for pruning + pub mode: Option, + /// JSON Schema describing the form fields to present to the user + pub requested_schema: UIElicitationSchema, + /// MCP task metadata. #[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 task: Option, } -/// Session ID whose in-use lock should be released. +/// The elicitation response (accept with form values, decline, or cancel) /// ///
/// @@ -15219,12 +20251,18 @@ 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 UIElicitationResponse { + /// MCP response metadata. + #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] + pub meta: Option>, + /// 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>, } -/// 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. +/// Indicates whether the elicitation response was accepted; false if it was already resolved by another client. /// ///
/// @@ -15234,9 +20272,12 @@ pub struct SessionsReleaseLockRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsReleaseLockResult {} +pub struct UIElicitationResult { + /// Whether the response was accepted. False if the request was already resolved by another client. + pub success: bool, +} -/// Active session ID and an optional flag for deferring repo-level hooks until folder trust. +/// Boolean field rendered as a yes/no toggle. /// ///
/// @@ -15246,15 +20287,21 @@ 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. +pub struct UIElicitationSchemaPropertyBoolean { + /// Default value selected when the form is first shown. #[serde(skip_serializing_if = "Option::is_none")] - pub defer_repo_hooks: Option, - /// Active session ID to reload hooks for - pub session_id: SessionId, + 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, } -/// 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. +/// Numeric field accepting either a number or an integer. /// ///
/// @@ -15264,9 +20311,27 @@ pub struct SessionsReloadPluginHooksRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsReloadPluginHooksResult {} +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, +} -/// Session ID whose pending events should be flushed to disk. +/// Free-text string field with optional length and format constraints. /// ///
/// @@ -15276,12 +20341,30 @@ 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 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, } -/// Flush a session's pending events to disk. No-op when no writer exists for the session (e.g., already closed). +/// Single-select string field whose allowed values are defined inline. /// ///
/// @@ -15291,9 +20374,26 @@ pub struct SessionsSaveRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsSaveResult {} +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, +} -/// Manager-wide additional plugins to register; replaces any previously-configured set. +/// Selectable option for a UI elicitation single-select string field, with submitted value and display label. /// ///
/// @@ -15303,12 +20403,14 @@ 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 UIElicitationStringOneOfFieldOneOf { + /// Value submitted when this option is selected. + pub r#const: String, + /// Display label for this option. + pub title: String, } -/// 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. +/// Single-select string field where each option pairs a value with a display label. /// ///
/// @@ -15318,9 +20420,23 @@ pub struct SessionsSetAdditionalPluginsRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsSetAdditionalPluginsResult {} +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, +} -/// Patch for the singleton's steering state. +/// Transient question to answer without adding it to conversation history. /// ///
/// @@ -15330,12 +20446,20 @@ 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 UIEphemeralQueryRequest { + /// In-process `AbortSignal` forwarded to the model client to cancel an in-flight request. Internal and excluded from the public SDK surface. + #[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. Internal and excluded from the public SDK surface. + #[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, } -/// Parameters for attaching the remote-control singleton to a session. +/// Completed transient query. Ordered chunks and the terminal outcome are also delivered through `ui.ephemeral_query` session events while it runs. /// ///
/// @@ -15345,14 +20469,12 @@ 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 UIEphemeralQueryResult { + /// Answer returned by the model + pub answer: String, } -/// Parameters for stopping the remote-control singleton. +/// User response for a pending exit-plan-mode request, with approval state, selected action, auto-approve flag, and feedback. /// ///
/// @@ -15362,34 +20484,24 @@ 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). +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 expected_session_id: Option, - /// When true, the singleton is unconditionally torn down regardless of `expectedSessionId`. Use during shutdown or explicit `/remote off`. + 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 force: Option, -} - -/// Parameters for atomically rebinding the remote-control singleton. -/// -///
-/// -/// **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 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). + pub defer_implementation: Option, + /// Feedback from the user when they declined the plan or requested changes. #[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 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, } -/// Telemetry engagement ID for the session, when available. +/// Request ID of a pending `auto_mode_switch.requested` event and the user's response. /// ///
/// @@ -15399,13 +20511,14 @@ pub struct SessionsTransferRemoteControlRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionTelemetryEngagement { - /// Current telemetry engagement ID, when available. - #[serde(skip_serializing_if = "Option::is_none")] - pub engagement_id: Option, +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, } -/// Patch of mutable session options to apply to the running session. +/// Pending elicitation request ID and the user's response (accept/decline/cancel + form values). /// ///
/// @@ -15415,191 +20528,14 @@ 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`. - #[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, +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, } -/// Indicates whether the session options patch was applied successfully. +/// Request ID of a pending `exit_plan_mode.requested` event and the user's response. /// ///
/// @@ -15609,15 +20545,29 @@ 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 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, +} + +/// Indicates whether the pending UI request was resolved by this 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 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, } -/// User-requested shell execution cancellation handle. +/// Optional sampling result payload. Omit to reject/cancel the sampling request without providing a result. /// ///
/// @@ -15627,12 +20577,27 @@ pub struct SessionUpdateOptionsResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ShellCancelUserRequestedRequest { - /// Request ID previously passed to executeUserRequested +pub struct UIHandlePendingSamplingResponse {} + +/// Request ID of a pending `sampling.requested` event and an optional sampling result payload (omit to reject). +/// +///
+/// +/// **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 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, } -/// Shell command to run, with optional working directory and timeout in milliseconds. +/// The user's selected action for an exhausted session limit. /// ///
/// @@ -15642,18 +20607,18 @@ 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 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 cwd: Option, - /// Timeout in milliseconds (default: 30000) + pub additional_ai_credits: Option, + /// New absolute max AI Credits when action is 'set'. #[serde(skip_serializing_if = "Option::is_none")] - pub timeout: Option, + pub max_ai_credits: Option, } -/// Identifier of the spawned process, used to correlate streamed output and exit notifications. +/// Request ID of a pending `session_limits_exhausted.requested` event and the user's selected limit action. /// ///
/// @@ -15663,12 +20628,14 @@ 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 UIHandlePendingSessionLimitsExhaustedRequest { + /// The unique request ID from the session_limits_exhausted.requested event + pub request_id: RequestId, + /// The selected session-limit action. + pub response: UISessionLimitsExhaustedResponse, } -/// User-requested shell command and cancellation handle. +/// User response for a pending user-input request, with answer text and whether it was typed freeform. /// ///
/// @@ -15678,14 +20645,31 @@ 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 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, +} + +/// Request ID of a pending `user_input.requested` event and the user's 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, 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, } -/// Identifier of a process previously returned by "shell.exec" and the signal to send. +/// 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). /// ///
/// @@ -15695,15 +20679,12 @@ 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) - #[serde(skip_serializing_if = "Option::is_none")] - pub signal: Option, +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, } -/// Indicates whether the signal was delivered; false if the process was unknown or already exited. +/// Opaque handle previously returned by `registerDirectAutoModeSwitchHandler` to release. /// ///
/// @@ -15713,12 +20694,12 @@ 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 UIUnregisterDirectAutoModeSwitchHandlerRequest { + /// Handle previously returned by `registerDirectAutoModeSwitchHandler` + pub handle: String, } -/// Parameters for shutting down the session +/// Indicates whether the handle was active and the registration count was decremented. /// ///
/// @@ -15728,16 +20709,30 @@ 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'. +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 reason: Option, - /// Why the session is being shut down. Defaults to "routine" when omitted. + pub agents: Option>, + /// Names of subagents the user has turned off; they cannot be dispatched #[serde(skip_serializing_if = "Option::is_none")] - pub r#type: Option, + 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, } -/// Skill metadata available to a session, with name, description, source, enabled/invocable state, path, plugin, and argument hint. +/// Subagent settings to apply to the current session /// ///
/// @@ -15747,29 +20742,68 @@ 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 - #[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 - #[serde(skip_serializing_if = "Option::is_none")] - pub path: Option, - /// Name of the plugin that provides the skill, when source is 'plugin' +pub struct UpdateSubagentSettingsRequest { + /// Subagent settings to apply, or null to clear the live session override + pub subagents: Option, +} + +/// Request count and cost metrics for this 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 UsageMetricsModelMetricRequests { + /// User-initiated premium request cost (with multiplier applied) + pub cost: f64, + /// Number of API requests made with this model + pub count: i64, +} + +/// Per-model token-detail entry containing the accumulated token count for one token 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 UsageMetricsModelMetricTokenDetail { + /// Accumulated token count for this token type + pub token_count: i64, +} + +/// Token usage metrics for this 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 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 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 reasoning_tokens: Option, } -/// Canonical directory where skills can be discovered or created, with scope, preference, and optional project path. +/// Per-model usage metrics, including request counts/costs, token usage, nano-AI units, and per-token-type details. /// ///
/// @@ -15779,19 +20813,23 @@ 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) +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 project_path: Option, - /// Which tier this directory belongs to - pub scope: SkillDiscoveryScope, + 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, } -/// Canonical locations where skills can be created so the runtime will recognize them. +/// Usage attributed to one agent instance, including its identity, API duration, AI units, and per-model breakdown. /// ///
/// @@ -15801,12 +20839,22 @@ 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 UsageMetricsAgentMetric { + /// Human-readable label for this subagent invocation, copied from the originating `subagent.started` event. For task-tool subagents this is the invocation's task description rather than the agent's configured display name, so group by `agentName` for stable per-agent labels. + #[serde(skip_serializing_if = "Option::is_none")] + pub agent_display_name: Option, + /// Configured agent name, when this is a subagent + #[serde(skip_serializing_if = "Option::is_none")] + pub agent_name: Option, + /// Per-model usage for this agent, keyed by model identifier + pub model_metrics: HashMap, + /// Time spent in model API calls by this agent, in milliseconds + pub total_api_duration_ms: i64, + /// Accumulated nano-AI units cost for this agent + pub total_nano_aiu: f64, } -/// Skills available to the session, with their enabled state. +/// Aggregated code change metrics /// ///
/// @@ -15816,12 +20864,18 @@ pub struct SkillDiscoveryPathList { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SkillList { - /// Available skills - pub skills: Vec, +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, } -/// Skill names to mark as disabled in global configuration, replacing any previous list. +/// Session-wide token-detail entry containing the accumulated token count for one token type. /// ///
/// @@ -15831,12 +20885,12 @@ 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 UsageMetricsTokenDetail { + /// Accumulated token count for this token type + pub token_count: i64, } -/// Name of the skill to disable for the session. +/// Accumulated session usage metrics, including premium request cost, token counts, model breakdown, and code-change totals. /// ///
/// @@ -15846,12 +20900,38 @@ 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 UsageGetMetricsResult { + /// Per-agent usage metrics, keyed by agent instance identifier. The main conversation uses the stable key `main`. + #[serde(skip_serializing_if = "Option::is_none")] + pub agent_metrics: Option>, + /// 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, } -/// Optional project paths and additional skill directories to include in discovery. +/// Result of a user-requested shell command. /// ///
/// @@ -15861,19 +20941,22 @@ 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 +pub struct UserRequestedShellCommandResult { + /// Error output when the execution failed #[serde(skip_serializing_if = "Option::is_none")] - pub project_paths: Option>, - /// Optional list of additional skill directory paths to include + pub error: Option, + /// Process exit code, when available #[serde(skip_serializing_if = "Option::is_none")] - pub skill_directories: Option>, + 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, } -/// Name of the skill to enable for the session. +/// A single user setting's effective value alongside its default, so consumers can render settings left at their default. /// ///
/// @@ -15883,12 +20966,16 @@ 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 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, } -/// Optional project paths to enumerate. +/// 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. /// ///
/// @@ -15898,16 +20985,12 @@ 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. - #[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>, +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, } -/// Skill invocation record with name, path, content, allowed tools, and turn number. +/// 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. /// ///
/// @@ -15917,21 +21000,12 @@ 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 - #[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 struct UserSettingsSetRequest { + /// Partial user settings to write, as a free-form object keyed by setting name + pub settings: serde_json::Value, } -/// Skills invoked during this session, ordered by invocation time (most recent last). +/// Outcome of writing user settings. /// ///
/// @@ -15941,12 +21015,12 @@ 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 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, } -/// Diagnostics from reloading skill definitions, with warnings and errors as separate lists. +/// Current sharing status and shareable GitHub URL for a session. /// ///
/// @@ -15956,14 +21030,18 @@ 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 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, } -/// Slash-command invocation result that submits an agent prompt, with display prompt, optional mode, optional user-facing notice, and settings-change flag. +/// Desired sharing status for the session. /// ///
/// @@ -15973,25 +21051,12 @@ 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 - #[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, +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, } -/// Slash-command invocation result indicating completion, with optional message and settings-change flag. +/// Effective sharing status and shareable GitHub URL after updating session visibility. /// ///
/// @@ -16001,18 +21066,18 @@ 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 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 message: Option, - /// True when the invocation mutated user runtime settings; consumers caching settings should refresh + 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 runtime_settings_changed: Option, + 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, } -/// Slash-command invocation result containing text output plus Markdown/ANSI rendering flags. +/// A single changed file and its unified diff. /// ///
/// @@ -16022,23 +21087,22 @@ 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 +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 preserve_ansi: Option, - /// True when the invocation mutated user runtime settings; consumers caching settings should refresh + pub is_truncated: Option, + /// Original file path for renamed files. #[serde(skip_serializing_if = "Option::is_none")] - pub runtime_settings_changed: Option, - /// Text output for the client to render - pub text: String, + 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, } -/// Selectable slash-command subcommand option with name, description, and optional group label. +/// Workspace diff result for the requested mode. /// ///
/// @@ -16048,17 +21112,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 WorkspaceDiffResult { + /// Default branch used for a branch diff, when branch mode was requested. #[serde(skip_serializing_if = "Option::is_none")] - pub group: Option, - /// Subcommand name to invoke - pub name: String, + 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, } -/// Slash-command invocation result asking the client to present subcommand options for a parent command. +/// Compaction summary checkpoint to persist. /// ///
/// @@ -16068,21 +21139,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 struct WorkspacesAddSummaryRequest { + /// Markdown summary content to persist. + pub content: String, + /// Summary title shown in checkpoint listings. pub title: String, } -/// Subagent model, reasoning effort, and context tier settings +/// Persisted summary metadata and refreshed workspace metadata. /// ///
/// @@ -16092,19 +21156,16 @@ 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 +pub struct WorkspacesAddSummaryResult { + /// Metadata for the persisted summary. #[serde(skip_serializing_if = "Option::is_none")] - pub effort_level: Option, - /// Model override for matching subagents + pub summary: Option, + /// Refreshed metadata for the containing workspace. #[serde(skip_serializing_if = "Option::is_none")] - pub model: Option, + pub workspace: Option, } -/// Subagent settings to apply, or null to clear the live session override +/// Whether the autopilot objective file exists. /// ///
/// @@ -16114,22 +21175,12 @@ 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 WorkspacesAutopilotObjectiveExistsResult { + /// True when the objective file exists. + pub exists: bool, } -/// Tracked background agent task metadata, including IDs, status, timing, agent type, prompt, model, result, and latest response. +/// Workspace checkpoint metadata with assigned number, human-readable title, and checkpoint filename. /// ///
/// @@ -16139,59 +21190,16 @@ 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 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, } -/// Timestamped display line for task progress output or recent agent activity. +/// Relative path and UTF-8 content for the workspace file to create or overwrite. /// ///
/// @@ -16201,14 +21209,14 @@ 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 WorkspacesCreateFileRequest { + /// File content to write as a UTF-8 string + pub content: String, + /// Relative path within the workspace files directory + pub path: String, } -/// Progress snapshot for an agent task, with recent activity lines and optional latest intent. +/// Result of deleting the autopilot objective file. /// ///
/// @@ -16218,17 +21226,12 @@ pub struct TaskProgressLine { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TaskAgentProgress { - /// The most recent intent reported by the agent - #[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 struct WorkspacesDeleteAutopilotObjectiveResult { + /// True when a file was deleted. + pub deleted: bool, } -/// Background tasks currently tracked by the session. +/// Parameters for computing a workspace diff. /// ///
/// @@ -16238,12 +21241,15 @@ pub struct TaskAgentProgress { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TaskList { - /// Currently tracked tasks - pub tasks: Vec, +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, } -/// Identifier of the background task to cancel. +/// Optional session context used when creating a local workspace. /// ///
/// @@ -16253,12 +21259,71 @@ pub struct TaskList { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TasksCancelRequest { - /// Task identifier +pub struct WorkspacesEnsureRequest { + /// Opaque workspace context supplied by the session host. + #[serde(skip_serializing_if = "Option::is_none")] + pub context: Option, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct WorkspacesGetWorkspaceResultWorkspace { + /// Current Git branch. + #[serde(skip_serializing_if = "Option::is_none")] + pub branch: Option, + /// Whether the per-session Chronicle upgrade prompt was dismissed for the workspace. + #[serde( + rename = "chronicle_sync_dismissed", + skip_serializing_if = "Option::is_none" + )] + pub chronicle_sync_dismissed: Option, + /// Name of the client that created the workspace. + #[serde(rename = "client_name", skip_serializing_if = "Option::is_none")] + pub client_name: Option, + /// Timestamp when the workspace was created. + #[serde(rename = "created_at", skip_serializing_if = "Option::is_none")] + pub created_at: Option, + /// Current working directory associated with the workspace. + #[serde(skip_serializing_if = "Option::is_none")] + pub cwd: Option, + /// Git repository root associated with the workspace. + #[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, + /// Stable workspace identifier. pub id: String, + /// Most recent Mission Control event identifier observed for the workspace. + #[serde(rename = "mc_last_event_id", skip_serializing_if = "Option::is_none")] + pub mc_last_event_id: Option, + /// Mission Control session identifier associated with the workspace. + #[serde(rename = "mc_session_id", skip_serializing_if = "Option::is_none")] + pub mc_session_id: Option, + /// Mission Control task identifier associated with the workspace. + #[serde(rename = "mc_task_id", skip_serializing_if = "Option::is_none")] + pub mc_task_id: Option, + /// Workspace display name. + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + /// Whether the workspace session can be steered remotely. + #[serde(rename = "remote_steerable", skip_serializing_if = "Option::is_none")] + pub remote_steerable: Option, + /// Repository identifier associated with the workspace. + #[serde(skip_serializing_if = "Option::is_none")] + pub repository: Option, + /// Number of persisted summaries in the workspace. + #[serde(rename = "summary_count", skip_serializing_if = "Option::is_none")] + pub summary_count: Option, + /// Timestamp when the workspace was last updated. + #[serde(rename = "updated_at", skip_serializing_if = "Option::is_none")] + pub updated_at: Option, + /// Whether the workspace name was explicitly chosen by the user. + #[serde(rename = "user_named", skip_serializing_if = "Option::is_none")] + pub user_named: Option, } -/// Indicates whether the background task was successfully cancelled. +/// Current workspace metadata for the session, including its absolute filesystem path when available. /// ///
/// @@ -16268,12 +21333,15 @@ 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 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, } -/// The first sync-waiting task that can currently be promoted to background mode. +/// Workspace checkpoints in chronological order; empty when the workspace is not enabled. /// ///
/// @@ -16283,13 +21351,12 @@ 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 WorkspacesListCheckpointsResult { + /// Workspace checkpoints in chronological order. Empty when workspace is not enabled. + pub checkpoints: Vec, } -/// Identifier of the background task to fetch progress for. +/// Relative paths of files stored in the session workspace files directory. /// ///
/// @@ -16299,12 +21366,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 WorkspacesListFilesResult { + /// Relative file paths in the workspace files directory + pub files: Vec, } -/// Progress information for the task, or null when no task with that ID is tracked. +/// Autopilot objective file content, or null when missing. /// ///
/// @@ -16314,12 +21381,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 WorkspacesReadAutopilotObjectiveResult { + /// Autopilot objective file content, or null when missing. + pub content: Option, } -/// Tracked shell task metadata, including ID, command, status, timing, attachment/execution mode, log path, and PID. +/// Checkpoint number to read. /// ///
/// @@ -16329,39 +21396,12 @@ 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 - #[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, +pub struct WorkspacesReadCheckpointRequest { + /// Checkpoint number to read + pub number: i64, } -/// Progress snapshot for a shell task, with recent stdout/stderr output and optional process ID. +/// Checkpoint content as a UTF-8 string, or null when the checkpoint or workspace is missing. /// ///
/// @@ -16371,17 +21411,27 @@ 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 WorkspacesReadCheckpointResult { + /// Checkpoint content as a UTF-8 string, or null when the checkpoint or workspace is missing + pub content: Option, +} + +/// Relative path of the workspace file to 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 WorkspacesReadFileRequest { + /// Relative path within the workspace files directory + pub path: String, } -/// The promoted task as it now exists in background mode, omitted if no promotable task was waiting. +/// Contents of the requested workspace file as a UTF-8 string. /// ///
/// @@ -16391,13 +21441,12 @@ 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 WorkspacesReadFileResult { + /// File content as a UTF-8 string + pub content: String, } -/// Identifier of the task to promote to background mode. +/// Pasted content to save as a UTF-8 file in the session workspace. /// ///
/// @@ -16407,12 +21456,23 @@ pub struct TasksPromoteCurrentToBackgroundResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TasksPromoteToBackgroundRequest { - /// Task identifier - pub id: String, +pub struct WorkspacesSaveLargePasteRequest { + /// Pasted content to save as a UTF-8 file + pub content: String, } -/// Indicates whether the task was successfully promoted to background mode. +#[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. /// ///
/// @@ -16422,12 +21482,12 @@ 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 WorkspacesSaveLargePasteResult { + /// Saved-paste descriptor, or null when the workspace is unavailable (e.g. CCA runtime, non-infinite sessions, remote sessions) + pub saved: 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. +/// Rollback point for local workspace summaries. /// ///
/// @@ -16437,9 +21497,12 @@ pub struct TasksPromoteToBackgroundResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TasksRefreshResult {} +pub struct WorkspacesTruncateSummariesRequest { + /// Number of newest summaries to keep. + pub keep_count: i64, +} -/// Identifier of the completed or cancelled task to remove from tracking. +/// 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). /// ///
/// @@ -16449,12 +21512,39 @@ pub struct TasksRefreshResult {} ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TasksRemoveRequest { - /// Task identifier +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, } -/// Indicates whether the task was removed. False when the task does not exist or is still running/idle. +/// Workspace metadata fields to update. /// ///
/// @@ -16464,12 +21554,16 @@ 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 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, } -/// Identifier of the target agent task, message content, and optional sender agent ID. +/// Autopilot objective file content to persist. /// ///
/// @@ -16479,17 +21573,12 @@ 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 - #[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 struct WorkspacesWriteAutopilotObjectiveRequest { + /// Autopilot objective file content. + pub content: String, } -/// Indicates whether the message was delivered, with an error message when delivery failed. +/// Result of writing the autopilot objective file. /// ///
/// @@ -16499,15 +21588,12 @@ pub struct TasksSendMessageRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TasksSendMessageResult { - /// 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 WorkspacesWriteAutopilotObjectiveResult { + /// Filesystem operation performed. + pub operation: String, } -/// Agent type, prompt, name, and optional description and model override for the new task. +/// List of Copilot models available to the resolved user, including capabilities and billing metadata. /// ///
/// @@ -16517,22 +21603,12 @@ 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 ModelsListResult { + /// List of available models with full metadata + pub models: Vec, } -/// Identifier assigned to the newly started background agent task. +/// The running runtime's complete catalog of well-known built-in model IDs, including supported models and additional IDs with built-in metadata. /// ///
/// @@ -16542,12 +21618,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 ModelsGetBuiltInCatalogResult { + /// Built-in model entries. + pub models: Vec, } -/// 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). +/// Built-in tools available for the requested model, with their parameters and instructions. /// ///
/// @@ -16557,9 +21633,12 @@ pub struct TasksStartAgentResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TasksWaitForPendingResult {} +pub struct ToolsListResult { + /// List of available built-in tools with metadata + pub tools: Vec, +} -/// Feature override key/value pairs to attach to subsequent telemetry events from this session. +/// User-configured MCP servers, keyed by server name. /// ///
/// @@ -16569,12 +21648,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 McpConfigListResult { + /// All MCP servers from user config, keyed by name + pub servers: HashMap, } -/// Authentication-info variant for SDK-configured token authentication, carrying host and the secret token value. +/// Extensions discovered from persisted Copilot home state and their effective loading mode. Launch-scoped additional plugins are not included. /// ///
/// @@ -16584,19 +21663,14 @@ 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. - #[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 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, } -/// Built-in tool metadata with identifier, optional namespaced name, description, input-parameter schema, and usage instructions. +/// Plugins installed in user/global state. /// ///
/// @@ -16606,23 +21680,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 PluginsListResult { + /// Installed plugins + pub plugins: Vec, } -/// Built-in tools available for the requested model, with their parameters and instructions. +/// Result of installing a plugin. /// ///
/// @@ -16632,12 +21695,20 @@ 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 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, } -/// Current lightweight tool metadata snapshot for the session. +/// Result of updating a single plugin. /// ///
/// @@ -16647,12 +21718,18 @@ 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 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, } -/// 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. +/// Result of updating all installed plugins. /// ///
/// @@ -16662,9 +21739,12 @@ pub struct ToolsGetCurrentMetadataResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ToolsInitializeAndValidateResult {} +pub struct PluginsUpdateAllResult { + /// Per-plugin update results in deterministic order. + pub results: Vec, +} -/// Optional model identifier whose tool overrides should be applied to the listing. +/// All registered marketplaces, including built-in defaults. /// ///
/// @@ -16674,13 +21754,12 @@ 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 PluginsMarketplacesListResult { + /// Registered marketplaces + pub marketplaces: Vec, } -/// Empty result after applying subagent settings +/// Result of registering a new marketplace. /// ///
/// @@ -16690,9 +21769,12 @@ pub struct ToolsListRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ToolsUpdateSubagentSettingsResult {} +pub struct PluginsMarketplacesAddResult { + /// Final name of the marketplace as resolved from its manifest + pub name: String, +} -/// Selectable option for a UI elicitation multi-select array item, with submitted value and display label. +/// Outcome of the remove attempt, including dependent-plugin info when applicable. /// ///
/// @@ -16702,14 +21784,15 @@ 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 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, } -/// Schema applied to each item in the array. +/// Plugins advertised by the marketplace. /// ///
/// @@ -16719,12 +21802,12 @@ 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 PluginsMarketplacesBrowseResult { + /// Plugins advertised by the marketplace + pub plugins: Vec, } -/// Multi-select string field where each option pairs a value with a display label. +/// Result of refreshing one or more marketplace catalogs. /// ///
/// @@ -16734,29 +21817,12 @@ 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 PluginsMarketplacesRefreshResult { + /// Per-marketplace refresh results in deterministic order. + pub results: Vec, } -/// Schema applied to each item in the array. +/// Skills discovered across global and project sources. /// ///
/// @@ -16766,14 +21832,15 @@ 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 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 errors: Option>, + /// All discovered skills across all sources + pub skills: Vec, } -/// Multi-select string field whose allowed values are defined inline. +/// Canonical locations where skills can be created so the runtime will recognize them. /// ///
/// @@ -16783,29 +21850,12 @@ 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. - #[serde(skip_serializing_if = "Option::is_none")] - pub title: Option, - /// Type discriminator. Always "array". - pub r#type: UIElicitationArrayEnumFieldType, +pub struct SkillsGetDiscoveryPathsResult { + /// Canonical skill create/discovery directories, in priority order + pub paths: Vec, } -/// JSON Schema describing the form fields to present to the user +/// Agents discovered across user, project, plugin, and remote sources. /// ///
/// @@ -16815,17 +21865,12 @@ 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 - #[serde(skip_serializing_if = "Option::is_none")] - pub required: Option>, - /// Schema type indicator (always 'object') - pub r#type: UIElicitationSchemaType, +pub struct AgentsDiscoverResult { + /// All discovered agents across all sources + pub agents: Vec, } -/// Prompt message and JSON schema describing the form fields to elicit from the user. +/// Canonical locations where custom agents can be created so the runtime will recognize them. /// ///
/// @@ -16835,14 +21880,12 @@ pub struct UIElicitationSchema { ///
#[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 AgentsGetDiscoveryPathsResult { + /// Canonical agent create/discovery directories, in priority order + pub paths: Vec, } -/// The elicitation response (accept with form values, decline, or cancel) +/// Instruction sources discovered across user, repository, and plugin sources. /// ///
/// @@ -16852,15 +21895,12 @@ 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') - #[serde(skip_serializing_if = "Option::is_none")] - pub content: Option>, +pub struct InstructionsDiscoverResult { + /// All discovered instruction sources + pub sources: Vec, } -/// Indicates whether the elicitation response was accepted; false if it was already resolved by another client. +/// Canonical files and directories where custom instructions can be created so the runtime will recognize them. /// ///
/// @@ -16870,12 +21910,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 InstructionsGetDiscoveryPathsResult { + /// Canonical instruction create/discovery files and directories, in priority order + pub paths: Vec, } -/// Boolean field rendered as a yes/no toggle. +/// Slash commands available in the session, after applying any include/exclude filters. /// ///
/// @@ -16885,21 +21925,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 CommandsListResult { + /// Commands available in this session + pub commands: Vec, } -/// Numeric field accepting either a number or an integer. +/// Result of opening a session. /// ///
/// @@ -16909,27 +21940,31 @@ 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. +pub struct SessionsOpenResult { + /// Remote session metadata, present when status is `connected`. #[serde(skip_serializing_if = "Option::is_none")] - pub default: Option, - /// Help text describing the field. + pub metadata: Option, + /// Handoff progress steps, present when status is `handed_off`. #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, - /// Maximum allowed value (inclusive). + pub progress: Option>, + /// Remote session ID, present when status is `connected`. #[serde(skip_serializing_if = "Option::is_none")] - pub maximum: Option, - /// Minimum allowed value (inclusive). + 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 minimum: Option, - /// Human-readable label for the field. + pub(crate) session_api: Option, + /// Opened session ID. Omitted when status is `not_found`. #[serde(skip_serializing_if = "Option::is_none")] - pub title: Option, - /// Numeric type accepted by the field. - pub r#type: UIElicitationSchemaPropertyNumberType, + 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, } -/// Free-text string field with optional length and format constraints. +/// Remote session connection result. /// ///
/// @@ -16939,30 +21974,14 @@ 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 SessionsConnectResult { + /// Metadata for a connected remote session. + pub metadata: ConnectedRemoteSessionMetadata, + /// SDK session ID for the connected remote session. + pub session_id: SessionId, } -/// Single-select string field whose allowed values are defined inline. +/// Sessions matching the filter, ordered most-recently-modified first. /// ///
/// @@ -16972,26 +21991,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 SessionsListResult { + /// Sessions ordered most-recently-modified first. Discriminated by `isRemote`. + pub sessions: Vec, } -/// Selectable option for a UI elicitation single-select string field, with submitted value and display label. +/// Batch of session events returned by a read, with cursor and continuation metadata. /// ///
/// @@ -17001,14 +22006,18 @@ 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 SessionsReadPersistedEventsResult { + /// 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 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, + /// 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 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, } -/// Single-select string field where each option pairs a value with a display label. +/// ID of the local session bound to the given GitHub task, or omitted when none. /// ///
/// @@ -17018,23 +22027,28 @@ 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. +pub struct SessionsFindByTaskIdResult { + /// Omitted when no local session is bound to that GitHub task #[serde(skip_serializing_if = "Option::is_none")] - pub title: Option, - /// Type discriminator. Always "string". - pub r#type: UIElicitationStringOneOfFieldType, + pub session_id: 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 SessionsGetSizesResult { + /// Map of sessionId -> on-disk size in bytes for the session's workspace directory + pub sizes: HashMap, } -/// Transient question to answer without adding it to conversation history. +/// Map of sessionId -> bytes freed by removing the session's workspace directory. /// ///
/// @@ -17044,20 +22058,12 @@ 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 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, } -/// Transient answer generated from current conversation context. +/// Outcome of the prune operation: deleted IDs, dry-run candidates, skipped IDs, total bytes freed, and the dry-run flag. /// ///
/// @@ -17067,12 +22073,20 @@ 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 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, } -/// User response for a pending exit-plan-mode request, with approval state, selected action, auto-approve flag, and feedback. +/// The enriched metadata records, with summary and context fields backfilled where available. Sessions confirmed empty and unnamed are omitted. /// ///
/// @@ -17082,24 +22096,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, - /// 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, +pub struct SessionsEnrichMetadataResult { + /// Enriched records, with summary and context backfilled. Sessions confirmed empty and unnamed may be omitted. + pub sessions: Vec, } -/// Request ID of a pending `auto_mode_switch.requested` event and the user's response. +/// Queued repo-level startup prompts and the total hook command count after loading. /// ///
/// @@ -17109,14 +22111,14 @@ 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 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, } -/// Pending elicitation request ID and the user's response (accept/decline/cancel + form values). +/// Wrapper for the singleton's current status. /// ///
/// @@ -17126,14 +22128,12 @@ 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 SessionsStartRemoteControlResult { + /// State of the runtime-managed remote-control singleton. + pub status: serde_json::Value, } -/// Request ID of a pending `exit_plan_mode.requested` event and the user's response. +/// Outcome of a transferRemoteControl call. /// ///
/// @@ -17143,14 +22143,14 @@ 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 SessionsTransferRemoteControlResult { + /// State of the runtime-managed remote-control singleton. + pub status: serde_json::Value, + /// Whether the rebinding actually happened. + pub transferred: bool, } -/// Indicates whether the pending UI request was resolved by this call. +/// Wrapper for the singleton's current status. /// ///
/// @@ -17160,12 +22160,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 SessionsSetRemoteControlSteeringResult { + /// State of the runtime-managed remote-control singleton. + pub status: serde_json::Value, } -/// Optional sampling result payload. Omit to reject/cancel the sampling request without providing a result. +/// Outcome of a stopRemoteControl call. /// ///
/// @@ -17175,9 +22175,14 @@ pub struct UIHandlePendingResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UIHandlePendingSamplingResponse {} +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, +} -/// Request ID of a pending `sampling.requested` event and an optional sampling result payload (omit to reject). +/// Wrapper for the singleton's current status. /// ///
/// @@ -17187,15 +22192,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 SessionsGetRemoteControlStatusResult { + /// State of the runtime-managed remote-control singleton. + pub status: serde_json::Value, } -/// The user's selected action for an exhausted session limit. +/// Handle for releasing the extension tool registration. /// ///
/// @@ -17205,18 +22207,13 @@ 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(crate) struct SessionsRegisterExtensionToolsOnSessionResult { + /// In-process unsubscribe function used only by the CLI. + #[doc(hidden)] + pub(crate) unsubscribe: serde_json::Value, } -/// Request ID of a pending `session_limits_exhausted.requested` event and the user's selected limit action. +/// Identifies the target session. /// ///
/// @@ -17226,14 +22223,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 SessionSuspendParams { + /// Target session identifier + pub session_id: SessionId, } -/// User response for a pending user-input request, with answer text and whether it was typed freeform. +/// Result of sending a user message /// ///
/// @@ -17243,14 +22238,12 @@ 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 SessionSendResult { + /// Unique identifier assigned to the message + pub message_id: String, } -/// Request ID of a pending `user_input.requested` event and the user's response. +/// Result of sending zero or more user messages /// ///
/// @@ -17260,14 +22253,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 SessionSendMessagesResult { + /// Unique identifiers assigned to the messages, one per provided message in order. Empty when no messages were provided. + pub message_ids: 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). +/// Identifies the target session. /// ///
/// @@ -17277,12 +22268,12 @@ 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 SessionSandboxGetEnforcementStatusParams { + /// Target session identifier + pub session_id: SessionId, } -/// Opaque handle previously returned by `registerDirectAutoModeSwitchHandler` to release. +/// Managed sandbox enforcement state for a session. /// ///
/// @@ -17292,12 +22283,17 @@ 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 SessionSandboxGetEnforcementStatusResult { + /// Whether an enforcement failure has permanently blocked the session. + pub blocked: bool, + /// The first sandbox enforcement failure that blocked the session. + #[serde(skip_serializing_if = "Option::is_none")] + pub reason: Option, + /// Whether the effective managed policy requires an available sandbox backend. + pub required: bool, } -/// Indicates whether the handle was active and the registration count was decremented. +/// Result of aborting the current turn /// ///
/// @@ -17307,30 +22303,30 @@ 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, +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, } -/// Configured per-agent subagent overrides +/// Result of interrupting the main agent 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 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 SessionInterruptMainTurnResult { + /// Whether an in-flight main agent turn was interrupted. False when the main loop was not processing. + pub interrupted: bool, } -/// Subagent settings to apply to the current session +/// Identifies the target session. /// ///
/// @@ -17340,12 +22336,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 SessionCancelAllBackgroundAgentsParams { + /// Target session identifier + pub session_id: SessionId, } -/// Aggregated code change metrics +/// Identifies the target session. /// ///
/// @@ -17355,18 +22351,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 SessionGitHubAuthGetStatusParams { + /// Target session identifier + pub session_id: SessionId, } -/// Request count and cost metrics for this model +/// Authentication status and account metadata for the session. /// ///
/// @@ -17376,14 +22366,27 @@ 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 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, } -/// Per-model token-detail entry containing the accumulated token count for one token type. +/// Indicates whether the credential update succeeded. /// ///
/// @@ -17393,12 +22396,15 @@ 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 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, } -/// Token usage metrics for this model +/// Identifies the target session. /// ///
/// @@ -17408,21 +22414,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 SessionGitHubAuthGetCurrentAuthInfoParams { + /// Target session identifier + pub session_id: SessionId, } -/// Per-model usage metrics, including request counts/costs, token usage, nano-AI units, and per-token-type details. +/// Identifies the target session. /// ///
/// @@ -17432,23 +22429,12 @@ 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 - #[serde(skip_serializing_if = "Option::is_none")] - pub total_nano_aiu: Option, - /// Token usage metrics for this model - pub usage: UsageMetricsModelMetricUsage, +pub struct SessionGitHubAuthGetAllAuthAvailableParams { + /// Target session identifier + pub session_id: SessionId, } -/// Session-wide token-detail entry containing the accumulated token count for one token type. +/// Identifies the target session. /// ///
/// @@ -17458,12 +22444,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 SessionGitHubAuthRefreshCopilotUserParams { + /// Target session identifier + pub session_id: SessionId, } -/// Accumulated session usage metrics, including premium request cost, token counts, model breakdown, and code-change totals. +/// Identifies the target session. /// ///
/// @@ -17473,35 +22459,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 SessionGitHubAuthLogoutParams { + /// Target session identifier + pub session_id: SessionId, } -/// Authentication-info variant for OAuth user auth, with host and login; the token remains in the runtime secret store. +/// Identifies the target session. /// ///
/// @@ -17511,19 +22474,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 SessionGitHubAuthLastAuthErrorsParams { + /// Target session identifier + pub session_id: SessionId, } -/// Result of a user-requested shell command. +/// Result of collecting a redacted debug bundle. /// ///
/// @@ -17533,22 +22489,19 @@ 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 +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 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 skipped_entries: Option>, } -/// A single user setting's effective value alongside its default, so consumers can render settings left at their default. +/// Identifies the target session. /// ///
/// @@ -17558,16 +22511,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 SessionCanvasListParams { + /// Target session identifier + pub session_id: SessionId, } -/// 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. +/// Declared canvases available in this session. /// ///
/// @@ -17577,12 +22526,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 SessionCanvasListResult { + /// Declared canvases available in this session + pub canvases: 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. +/// Identifies the target session. /// ///
/// @@ -17592,12 +22541,12 @@ 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 SessionCanvasListOpenParams { + /// Target session identifier + pub session_id: SessionId, } -/// Outcome of writing user settings. +/// Live open-canvas snapshot. /// ///
/// @@ -17607,12 +22556,12 @@ 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 SessionCanvasListOpenResult { + /// Currently open canvas instances + pub open_canvases: Vec, } -/// Current sharing status and shareable GitHub URL for a session. +/// Open canvas instance snapshot. /// ///
/// @@ -17622,18 +22571,34 @@ 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. +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 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). + pub extension_name: Option, + /// Host-local PNG path for the canvas icon, when supplied #[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 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, } -/// Desired sharing status for the session. +/// Canvas action invocation result. /// ///
/// @@ -17643,12 +22608,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 SessionCanvasActionInvokeResult { + /// Provider-supplied action result + #[serde(skip_serializing_if = "Option::is_none")] + pub result: Option, } -/// Effective sharing status and shareable GitHub URL after updating session visibility. +/// Complete current or terminal factory run envelope. /// ///
/// @@ -17658,18 +22624,32 @@ 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. +pub struct SessionFactoryRunResult { + /// One-based execution attempt represented by this envelope. Absent before the first attempt starts or when returned by an older runtime. #[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). + pub attempt: Option, + /// Error message for an errored run. #[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 error: Option, + /// Machine-readable failure details for a halted or 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, } -/// A single changed file and its unified diff. +/// Resolved persisted factory identity and resumed run envelope. /// ///
/// @@ -17679,22 +22659,14 @@ 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 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, +pub struct SessionFactoryResumeResult { + /// Persisted factory name resolved for the resumed run. + pub factory_name: String, + /// Terminal resumed run envelope. + pub run: FactoryRunResult, } -/// Workspace diff result for the requested mode. +/// Complete current or terminal factory run envelope. /// ///
/// @@ -17704,24 +22676,32 @@ 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. +pub struct SessionFactoryRunFromToolResult { + /// One-based execution attempt represented by this envelope. Absent before the first attempt starts or when returned by an older runtime. + #[serde(skip_serializing_if = "Option::is_none")] + pub attempt: Option, + /// Error message for an errored run. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// Machine-readable failure details for a halted or 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 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. + 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 unavailable_reason: Option, + pub snapshot: Option, + /// Current or terminal factory run status. + pub status: FactoryRunStatus, } -/// Compaction summary checkpoint to persist. +/// Resolved persisted factory identity and resumed run envelope. /// ///
/// @@ -17731,14 +22711,14 @@ pub struct WorkspaceDiffResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct WorkspacesAddSummaryRequest { - /// Markdown summary content to persist. - pub content: String, - /// Summary title shown in checkpoint listings. - pub title: String, +pub struct SessionFactoryResumeFromToolResult { + /// Persisted factory name resolved for the resumed run. + pub factory_name: String, + /// Terminal resumed run envelope. + pub run: FactoryRunResult, } -/// Persisted summary metadata and refreshed workspace metadata. +/// Complete current or terminal factory run envelope. /// ///
/// @@ -17748,14 +22728,32 @@ pub struct WorkspacesAddSummaryRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct WorkspacesAddSummaryResult { +pub struct SessionFactoryGetRunResult { + /// One-based execution attempt represented by this envelope. Absent before the first attempt starts or when returned by an older runtime. #[serde(skip_serializing_if = "Option::is_none")] - pub summary: Option, + pub attempt: Option, + /// Error message for an errored run. #[serde(skip_serializing_if = "Option::is_none")] - pub workspace: Option, + pub error: Option, + /// Machine-readable failure details for a halted or 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, } -/// Whether the autopilot objective file exists. +/// A page of factory runs in durable creation order. /// ///
/// @@ -17765,12 +22763,24 @@ pub struct WorkspacesAddSummaryResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct WorkspacesAutopilotObjectiveExistsResult { - /// True when the objective file exists. - pub exists: bool, +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, + /// Factory run summaries in durable creation order. + pub runs: Vec, } -/// Workspace checkpoint metadata with assigned number, human-readable title, and checkpoint filename. +/// Full factory run observability detail. /// ///
/// @@ -17780,16 +22790,54 @@ pub struct WorkspacesAutopilotObjectiveExistsResult { ///
#[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 SessionFactoryGetRunDetailResult { + /// Epoch milliseconds when the current active segment started, or null while inactive. + pub active_segment_started_at: Option, + /// Durable identities and live statuses for direct factory agents. + pub agents: Vec, + /// Approved effective resource ceilings, or null until approved. + pub approved: Option, + /// Epoch milliseconds when the run completed, or null while nonterminal. + pub completed_at: Option, + /// Durable resource consumption. + pub consumed: FactoryRunConsumed, + /// Epoch milliseconds when the run was created. + pub created_at: i64, + /// Current phase identity, or null before any phase is entered. + pub current_phase: Option, + /// Resource ceilings declared by the factory. + pub declared_limits: FactoryDeclaredLimits, + /// Number of phases declared by the factory. + pub declared_phase_count: i64, + /// Human-readable factory description. + pub description: String, + /// Registered factory name. + pub factory_name: String, + /// Number of direct factory agents currently live. + pub live_agent_count: i64, + /// Epoch milliseconds when this live-overlay snapshot was observed. + pub observed_at: i64, + /// Lifecycle and timing observations for each factory phase. + pub phases: Vec, + /// Bidirectional page of durable factory progress. + pub progress: FactoryProgressPage, + /// Monotonic durable run revision. + pub revision: i64, + /// Factory run identifier. + pub run_id: String, + /// Epoch milliseconds when execution first started, or null before start. + pub started_at: Option, + /// Current factory run status. + pub status: FactoryRunStatus, + /// Terminal run outcome, or null while nonterminal. + pub terminal: Option, + /// Total direct factory agents spawned across all attempts. + pub total_spawned_agent_count: i64, + /// Epoch milliseconds when the durable run was last updated. + pub updated_at: i64, } -/// Relative path and UTF-8 content for the workspace file to create or overwrite. +/// A bidirectional page of factory progress. /// ///
/// @@ -17799,14 +22847,22 @@ 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 SessionFactoryGetRunProgressResult { + /// Whether progress records newer than this page exist. + pub has_more_newer: bool, + /// Whether progress records older than this page exist. + pub has_more_older: bool, + /// Newest sequence number in this page, or null when empty. + pub newest_seq: Option, + /// Oldest sequence number in this page, or null when empty. + pub oldest_seq: Option, + /// Progress records in sequence order. + pub records: Vec, + /// Run revision reflected by this page. + pub revision: i64, } -/// Result of deleting the autopilot objective file. +/// Complete current or terminal factory run envelope. /// ///
/// @@ -17816,12 +22872,32 @@ pub struct WorkspacesCreateFileRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct WorkspacesDeleteAutopilotObjectiveResult { - /// True when a file was deleted. - pub deleted: bool, +pub struct SessionFactoryCancelResult { + /// One-based execution attempt represented by this envelope. Absent before the first attempt starts or when returned by an older runtime. + #[serde(skip_serializing_if = "Option::is_none")] + pub attempt: Option, + /// Error message for an errored run. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// Machine-readable failure details for a halted or 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, } -/// Parameters for computing a workspace diff. +/// Acknowledgement that a factory request was accepted. /// ///
/// @@ -17831,15 +22907,9 @@ pub struct WorkspacesDeleteAutopilotObjectiveResult { ///
#[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, -} +pub struct SessionFactoryLogResult {} -/// Optional session context used when creating a local workspace. +/// Result of one factory-scoped subagent call. /// ///
/// @@ -17849,55 +22919,13 @@ pub struct WorkspacesDiffRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct WorkspacesEnsureRequest { - /// Opaque workspace context supplied by the session host. - #[serde(skip_serializing_if = "Option::is_none")] - pub context: Option, -} - -#[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, +pub struct SessionFactoryAgentResult { + /// Agent result, omitted when the agent produced no result. #[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 result: Option, } -/// Current workspace metadata for the session, including its absolute filesystem path when available. +/// Result of reading a factory journal entry. /// ///
/// @@ -17907,15 +22935,15 @@ 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). +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 path: Option, - /// Current workspace metadata, or null if not available - pub workspace: Option, + pub result_json: Option, } -/// Workspace checkpoints in chronological order; empty when the workspace is not enabled. +/// Acknowledgement that a factory request was accepted. /// ///
/// @@ -17925,12 +22953,9 @@ 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 SessionFactoryJournalPutResult {} -/// Relative paths of files stored in the session workspace files directory. +/// Identifies the target session. /// ///
/// @@ -17940,12 +22965,12 @@ 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 SessionModelGetCurrentParams { + /// Target session identifier + pub session_id: SessionId, } -/// Autopilot objective file content, or null when missing. +/// 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. /// ///
/// @@ -17955,12 +22980,19 @@ pub struct WorkspacesListFilesResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct WorkspacesReadAutopilotObjectiveResult { - /// Autopilot objective file content, or null when missing. - pub content: Option, +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, } -/// Checkpoint number to read. +/// The model identifier active on the session after the switch. /// ///
/// @@ -17970,12 +23002,34 @@ pub struct WorkspacesReadAutopilotObjectiveResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct WorkspacesReadCheckpointRequest { - /// Checkpoint number to read - pub number: i64, +pub struct SessionModelSwitchToResult { + /// Compaction confirmation projection when status is confirmation_required + #[serde(skip_serializing_if = "Option::is_none")] + pub confirmation: Option, + /// 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, + /// Deprecation warnings associated with the selected model or options. + #[serde(skip_serializing_if = "Option::is_none")] + pub deprecation_warnings: Option>, + /// User-facing outcome message for the model switch. + #[serde(skip_serializing_if = "Option::is_none")] + pub message: Option, + /// Currently active model identifier after the switch + #[serde(skip_serializing_if = "Option::is_none")] + pub model_id: Option, + /// Persistence failure encountered after applying the model switch. + #[serde(skip_serializing_if = "Option::is_none")] + pub persistence_error: Option, + /// Lifecycle result for the requested switch + #[serde(skip_serializing_if = "Option::is_none")] + pub status: Option, + /// User-facing warning produced while applying the model switch. + #[serde(skip_serializing_if = "Option::is_none")] + pub warning: Option, } -/// Checkpoint content as a UTF-8 string, or null when the checkpoint or workspace is missing. +/// The model identifier active on the session after the switch. /// ///
/// @@ -17985,12 +23039,34 @@ 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 struct SessionModelApplyStartupOverlayResult { + /// Compaction confirmation projection when status is confirmation_required + #[serde(skip_serializing_if = "Option::is_none")] + pub confirmation: Option, + /// 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, + /// Deprecation warnings associated with the selected model or options. + #[serde(skip_serializing_if = "Option::is_none")] + pub deprecation_warnings: Option>, + /// User-facing outcome message for the model switch. + #[serde(skip_serializing_if = "Option::is_none")] + pub message: Option, + /// Currently active model identifier after the switch + #[serde(skip_serializing_if = "Option::is_none")] + pub model_id: Option, + /// Persistence failure encountered after applying the model switch. + #[serde(skip_serializing_if = "Option::is_none")] + pub persistence_error: Option, + /// Lifecycle result for the requested switch + #[serde(skip_serializing_if = "Option::is_none")] + pub status: Option, + /// User-facing warning produced while applying the model switch. + #[serde(skip_serializing_if = "Option::is_none")] + pub warning: Option, } -/// Relative path of the workspace file to read. +/// 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. /// ///
/// @@ -18000,12 +23076,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 SessionModelSetReasoningEffortResult { + /// Reasoning effort level recorded on the session after the update + pub reasoning_effort: String, } -/// Contents of the requested workspace file as a UTF-8 string. +/// The list of models available to this session. /// ///
/// @@ -18015,12 +23091,18 @@ 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 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>, } -/// Pasted content to save as a UTF-8 file in the session workspace. +/// Identifies the target session. /// ///
/// @@ -18030,23 +23112,12 @@ 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, -} - -#[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 SessionModeGetParams { + /// Target session identifier + pub session_id: SessionId, } -/// Descriptor for the saved paste file, or null when the workspace is unavailable. +/// Outcome of a session mode change, including any model switch it triggered and follow-up the host must perform. /// ///
/// @@ -18056,12 +23127,32 @@ 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 SessionModeSetResult { + /// Whether the host should arm an interactive continuation after the mode change. + #[serde(skip_serializing_if = "Option::is_none")] + pub arm_interactive_continuation: Option, + /// Compaction confirmation required before the mode change can complete. + #[serde(skip_serializing_if = "Option::is_none")] + pub confirmation: Option, + /// Whether the host must defer implementing the requested mode change. + #[serde(skip_serializing_if = "Option::is_none")] + pub defer_implementation: Option, + /// Deprecation warnings associated with the model selected by the mode change. + #[serde(skip_serializing_if = "Option::is_none")] + pub deprecation_warnings: Option>, + /// User-facing outcome message for the model switch triggered by the mode change. + #[serde(skip_serializing_if = "Option::is_none")] + pub message: Option, + /// Whether applying the mode changed the active model. + pub model_changed: bool, + /// Lifecycle status of the requested mode change. + pub status: String, + /// User-facing warning produced while applying the mode change. + #[serde(skip_serializing_if = "Option::is_none")] + pub warning: Option, } -/// Rollback point for local workspace summaries. +/// Identifies the target session. /// ///
/// @@ -18071,12 +23162,12 @@ pub struct WorkspacesSaveLargePasteResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct WorkspacesTruncateSummariesRequest { - /// Number of newest summaries to keep. - pub keep_count: i64, +pub struct SessionNameGetParams { + /// Target session identifier + pub session_id: SessionId, } -/// 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). +/// The session's friendly name, or null when not yet set. /// ///
/// @@ -18086,39 +23177,12 @@ pub struct WorkspacesTruncateSummariesRequest { ///
#[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 struct SessionNameGetResult { + /// The session name (user-set or auto-generated), or null if not yet set 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, } -/// Workspace metadata fields to update. +/// Indicates whether the auto-generated summary was applied as the session's name. /// ///
/// @@ -18128,16 +23192,12 @@ pub struct WorkspaceSummary { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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, +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, } -/// Autopilot objective file content to persist. +/// Identifies the target session. /// ///
/// @@ -18147,12 +23207,12 @@ pub struct WorkspacesUpdateMetadataRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct WorkspacesWriteAutopilotObjectiveRequest { - /// Autopilot objective file content. - pub content: String, +pub struct SessionPlanReadParams { + /// Target session identifier + pub session_id: SessionId, } -/// Result of writing the autopilot objective file. +/// Existence, contents, and resolved path of the session plan file. /// ///
/// @@ -18162,12 +23222,16 @@ pub struct WorkspacesWriteAutopilotObjectiveRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct WorkspacesWriteAutopilotObjectiveResult { - /// Filesystem operation performed. - pub operation: String, +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, } -/// List of Copilot models available to the resolved user, including capabilities and billing metadata. +/// Identifies the target session. /// ///
/// @@ -18177,12 +23241,12 @@ pub struct WorkspacesWriteAutopilotObjectiveResult { ///
#[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 SessionPlanDeleteParams { + /// Target session identifier + pub session_id: SessionId, } -/// The running runtime's complete catalog of well-known built-in model IDs, including supported models and additional IDs with built-in metadata. +/// Identifies the target session. /// ///
/// @@ -18192,12 +23256,12 @@ pub struct ModelsListResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ModelsGetBuiltInCatalogResult { - /// Built-in model entries. - pub models: Vec, +pub struct SessionPlanReadSqlTodosParams { + /// Target session identifier + pub session_id: SessionId, } -/// Built-in tools available for the requested model, with their parameters and instructions. +/// Todo rows read from the session SQL database. Empty when no session database is available. /// ///
/// @@ -18207,12 +23271,12 @@ pub struct ModelsGetBuiltInCatalogResult { ///
#[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 SessionPlanReadSqlTodosResult { + /// Rows from the session SQL todos table, ordered by creation time with insertion order used to break ties when available and id used for WITHOUT ROWID tables. + pub rows: Vec, } -/// User-configured MCP servers, keyed by server name. +/// Identifies the target session. /// ///
/// @@ -18222,12 +23286,12 @@ 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 SessionPlanReadSqlTodosWithDependenciesParams { + /// Target session identifier + pub session_id: SessionId, } -/// Extensions discovered from persisted Copilot home state and their effective loading mode. Launch-scoped additional plugins are not included. +/// Todo rows + dependency edges read from the session SQL database. /// ///
/// @@ -18237,14 +23301,14 @@ pub struct McpConfigListResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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, +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 with insertion order used to break ties when available and id used for WITHOUT ROWID tables. Empty when no database, no todos table, or the SELECT failed. + pub rows: Vec, } -/// Plugins installed in user/global state. +/// Identifies the target session. /// ///
/// @@ -18254,35 +23318,70 @@ pub struct ExtensionsDiscoverResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PluginsListResult { - /// Installed plugins - pub plugins: Vec, +pub struct SessionWorkspacesGetWorkspaceParams { + /// Target session identifier + pub session_id: SessionId, } -/// Result of installing a plugin. -/// -///
-/// -/// **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 PluginsInstallResult { - /// Set when the install path is deprecated (e.g. direct repo / URL / local installs). Callers should surface this to end users. +pub struct SessionWorkspacesGetWorkspaceResultWorkspace { + /// Current Git branch. #[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) + pub branch: Option, + /// Whether the per-session Chronicle upgrade prompt was dismissed for the workspace. + #[serde( + rename = "chronicle_sync_dismissed", + skip_serializing_if = "Option::is_none" + )] + pub chronicle_sync_dismissed: Option, + /// Name of the client that created the workspace. + #[serde(rename = "client_name", skip_serializing_if = "Option::is_none")] + pub client_name: Option, + /// Timestamp when the workspace was created. + #[serde(rename = "created_at", skip_serializing_if = "Option::is_none")] + pub created_at: Option, + /// Current working directory associated with the workspace. #[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 cwd: Option, + /// Git repository root associated with the workspace. + #[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, + /// Stable workspace identifier. + pub id: String, + /// Most recent Mission Control event identifier observed for the workspace. + #[serde(rename = "mc_last_event_id", skip_serializing_if = "Option::is_none")] + pub mc_last_event_id: Option, + /// Mission Control session identifier associated with the workspace. + #[serde(rename = "mc_session_id", skip_serializing_if = "Option::is_none")] + pub mc_session_id: Option, + /// Mission Control task identifier associated with the workspace. + #[serde(rename = "mc_task_id", skip_serializing_if = "Option::is_none")] + pub mc_task_id: Option, + /// Workspace display name. + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + /// Whether the workspace session can be steered remotely. + #[serde(rename = "remote_steerable", skip_serializing_if = "Option::is_none")] + pub remote_steerable: Option, + /// Repository identifier associated with the workspace. + #[serde(skip_serializing_if = "Option::is_none")] + pub repository: Option, + /// Number of persisted summaries in the workspace. + #[serde(rename = "summary_count", skip_serializing_if = "Option::is_none")] + pub summary_count: Option, + /// Timestamp when the workspace was last updated. + #[serde(rename = "updated_at", skip_serializing_if = "Option::is_none")] + pub updated_at: Option, + /// Whether the workspace name was explicitly chosen by the user. + #[serde(rename = "user_named", skip_serializing_if = "Option::is_none")] + pub user_named: Option, } -/// Result of updating a single plugin. +/// Current workspace metadata for the session, including its absolute filesystem path when available. /// ///
/// @@ -18292,33 +23391,73 @@ 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 +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 previous_version: Option, - /// Number of skills discovered and installed after the update - pub skills_installed: i64, + pub path: Option, + /// Current workspace metadata, or null if not available + pub workspace: Option, } -/// Result of updating all installed plugins. -/// -///
-/// -/// **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 PluginsUpdateAllResult { - /// Per-plugin update results in deterministic order. - pub results: Vec, +pub struct SessionWorkspacesUpdateMetadataResultWorkspace { + /// Current Git branch. + #[serde(skip_serializing_if = "Option::is_none")] + pub branch: Option, + /// Whether the per-session Chronicle upgrade prompt was dismissed for the workspace. + #[serde( + rename = "chronicle_sync_dismissed", + skip_serializing_if = "Option::is_none" + )] + pub chronicle_sync_dismissed: Option, + /// Name of the client that created the workspace. + #[serde(rename = "client_name", skip_serializing_if = "Option::is_none")] + pub client_name: Option, + /// Timestamp when the workspace was created. + #[serde(rename = "created_at", skip_serializing_if = "Option::is_none")] + pub created_at: Option, + /// Current working directory associated with the workspace. + #[serde(skip_serializing_if = "Option::is_none")] + pub cwd: Option, + /// Git repository root associated with the workspace. + #[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, + /// Stable workspace identifier. + pub id: String, + /// Most recent Mission Control event identifier observed for the workspace. + #[serde(rename = "mc_last_event_id", skip_serializing_if = "Option::is_none")] + pub mc_last_event_id: Option, + /// Mission Control session identifier associated with the workspace. + #[serde(rename = "mc_session_id", skip_serializing_if = "Option::is_none")] + pub mc_session_id: Option, + /// Mission Control task identifier associated with the workspace. + #[serde(rename = "mc_task_id", skip_serializing_if = "Option::is_none")] + pub mc_task_id: Option, + /// Workspace display name. + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + /// Whether the workspace session can be steered remotely. + #[serde(rename = "remote_steerable", skip_serializing_if = "Option::is_none")] + pub remote_steerable: Option, + /// Repository identifier associated with the workspace. + #[serde(skip_serializing_if = "Option::is_none")] + pub repository: Option, + /// Number of persisted summaries in the workspace. + #[serde(rename = "summary_count", skip_serializing_if = "Option::is_none")] + pub summary_count: Option, + /// Timestamp when the workspace was last updated. + #[serde(rename = "updated_at", skip_serializing_if = "Option::is_none")] + pub updated_at: Option, + /// Whether the workspace name was explicitly chosen by the user. + #[serde(rename = "user_named", skip_serializing_if = "Option::is_none")] + pub user_named: Option, } -/// All registered marketplaces, including built-in defaults. +/// Current workspace metadata for the session, including its absolute filesystem path when available. /// ///
/// @@ -18328,27 +23467,73 @@ pub struct PluginsUpdateAllResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PluginsMarketplacesListResult { - /// Registered marketplaces - pub marketplaces: Vec, +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, } -/// Result of registering a new marketplace. -/// -///
-/// -/// **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 PluginsMarketplacesAddResult { - /// Final name of the marketplace as resolved from its manifest - pub name: String, +pub struct SessionWorkspacesEnsureResultWorkspace { + /// Current Git branch. + #[serde(skip_serializing_if = "Option::is_none")] + pub branch: Option, + /// Whether the per-session Chronicle upgrade prompt was dismissed for the workspace. + #[serde( + rename = "chronicle_sync_dismissed", + skip_serializing_if = "Option::is_none" + )] + pub chronicle_sync_dismissed: Option, + /// Name of the client that created the workspace. + #[serde(rename = "client_name", skip_serializing_if = "Option::is_none")] + pub client_name: Option, + /// Timestamp when the workspace was created. + #[serde(rename = "created_at", skip_serializing_if = "Option::is_none")] + pub created_at: Option, + /// Current working directory associated with the workspace. + #[serde(skip_serializing_if = "Option::is_none")] + pub cwd: Option, + /// Git repository root associated with the workspace. + #[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, + /// Stable workspace identifier. + pub id: String, + /// Most recent Mission Control event identifier observed for the workspace. + #[serde(rename = "mc_last_event_id", skip_serializing_if = "Option::is_none")] + pub mc_last_event_id: Option, + /// Mission Control session identifier associated with the workspace. + #[serde(rename = "mc_session_id", skip_serializing_if = "Option::is_none")] + pub mc_session_id: Option, + /// Mission Control task identifier associated with the workspace. + #[serde(rename = "mc_task_id", skip_serializing_if = "Option::is_none")] + pub mc_task_id: Option, + /// Workspace display name. + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + /// Whether the workspace session can be steered remotely. + #[serde(rename = "remote_steerable", skip_serializing_if = "Option::is_none")] + pub remote_steerable: Option, + /// Repository identifier associated with the workspace. + #[serde(skip_serializing_if = "Option::is_none")] + pub repository: Option, + /// Number of persisted summaries in the workspace. + #[serde(rename = "summary_count", skip_serializing_if = "Option::is_none")] + pub summary_count: Option, + /// Timestamp when the workspace was last updated. + #[serde(rename = "updated_at", skip_serializing_if = "Option::is_none")] + pub updated_at: Option, + /// Whether the workspace name was explicitly chosen by the user. + #[serde(rename = "user_named", skip_serializing_if = "Option::is_none")] + pub user_named: Option, } -/// Outcome of the remove attempt, including dependent-plugin info when applicable. +/// Current workspace metadata for the session, including its absolute filesystem path when available. /// ///
/// @@ -18358,15 +23543,15 @@ 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 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 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 path: Option, + /// Current workspace metadata, or null if not available + pub workspace: Option, } -/// Plugins advertised by the marketplace. +/// Identifies the target session. /// ///
/// @@ -18376,12 +23561,12 @@ 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 SessionWorkspacesListFilesParams { + /// Target session identifier + pub session_id: SessionId, } -/// Result of refreshing one or more marketplace catalogs. +/// Relative paths of files stored in the session workspace files directory. /// ///
/// @@ -18391,12 +23576,12 @@ 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 SessionWorkspacesListFilesResult { + /// Relative file paths in the workspace files directory + pub files: Vec, } -/// Skills discovered across global and project sources. +/// Contents of the requested workspace file as a UTF-8 string. /// ///
/// @@ -18406,15 +23591,12 @@ 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. - #[serde(skip_serializing_if = "Option::is_none")] - pub errors: Option>, - /// All discovered skills across all sources - pub skills: Vec, +pub struct SessionWorkspacesReadFileResult { + /// File content as a UTF-8 string + pub content: String, } -/// Canonical locations where skills can be created so the runtime will recognize them. +/// Identifies the target session. /// ///
/// @@ -18424,12 +23606,12 @@ 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 SessionWorkspacesListCheckpointsParams { + /// Target session identifier + pub session_id: SessionId, } -/// Agents discovered across user, project, plugin, and remote sources. +/// Workspace checkpoints in chronological order; empty when the workspace is not enabled. /// ///
/// @@ -18439,12 +23621,12 @@ 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 SessionWorkspacesListCheckpointsResult { + /// Workspace checkpoints in chronological order. Empty when workspace is not enabled. + pub checkpoints: Vec, } -/// Canonical locations where custom agents can be created so the runtime will recognize them. +/// Checkpoint content as a UTF-8 string, or null when the checkpoint or workspace is missing. /// ///
/// @@ -18454,12 +23636,12 @@ 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 SessionWorkspacesReadCheckpointResult { + /// Checkpoint content as a UTF-8 string, or null when the checkpoint or workspace is missing + pub content: Option, } -/// Instruction sources discovered across user, repository, and plugin sources. +/// Persisted summary metadata and refreshed workspace metadata. /// ///
/// @@ -18469,27 +23651,74 @@ 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 SessionWorkspacesAddSummaryResult { + /// Metadata for the persisted summary. + #[serde(skip_serializing_if = "Option::is_none")] + pub summary: Option, + /// Refreshed metadata for the containing workspace. + #[serde(skip_serializing_if = "Option::is_none")] + pub workspace: Option, } -/// Canonical files and directories where custom instructions can be created so the runtime will recognize them. -/// -///
-/// -/// **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 InstructionsGetDiscoveryPathsResult { - /// Canonical instruction create/discovery files and directories, in priority order - pub paths: Vec, +pub struct SessionWorkspacesTruncateSummariesResultWorkspace { + /// Current Git branch. + #[serde(skip_serializing_if = "Option::is_none")] + pub branch: Option, + /// Whether the per-session Chronicle upgrade prompt was dismissed for the workspace. + #[serde( + rename = "chronicle_sync_dismissed", + skip_serializing_if = "Option::is_none" + )] + pub chronicle_sync_dismissed: Option, + /// Name of the client that created the workspace. + #[serde(rename = "client_name", skip_serializing_if = "Option::is_none")] + pub client_name: Option, + /// Timestamp when the workspace was created. + #[serde(rename = "created_at", skip_serializing_if = "Option::is_none")] + pub created_at: Option, + /// Current working directory associated with the workspace. + #[serde(skip_serializing_if = "Option::is_none")] + pub cwd: Option, + /// Git repository root associated with the workspace. + #[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, + /// Stable workspace identifier. + pub id: String, + /// Most recent Mission Control event identifier observed for the workspace. + #[serde(rename = "mc_last_event_id", skip_serializing_if = "Option::is_none")] + pub mc_last_event_id: Option, + /// Mission Control session identifier associated with the workspace. + #[serde(rename = "mc_session_id", skip_serializing_if = "Option::is_none")] + pub mc_session_id: Option, + /// Mission Control task identifier associated with the workspace. + #[serde(rename = "mc_task_id", skip_serializing_if = "Option::is_none")] + pub mc_task_id: Option, + /// Workspace display name. + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + /// Whether the workspace session can be steered remotely. + #[serde(rename = "remote_steerable", skip_serializing_if = "Option::is_none")] + pub remote_steerable: Option, + /// Repository identifier associated with the workspace. + #[serde(skip_serializing_if = "Option::is_none")] + pub repository: Option, + /// Number of persisted summaries in the workspace. + #[serde(rename = "summary_count", skip_serializing_if = "Option::is_none")] + pub summary_count: Option, + /// Timestamp when the workspace was last updated. + #[serde(rename = "updated_at", skip_serializing_if = "Option::is_none")] + pub updated_at: Option, + /// Whether the workspace name was explicitly chosen by the user. + #[serde(rename = "user_named", skip_serializing_if = "Option::is_none")] + pub user_named: Option, } -/// Slash commands available in the session, after applying any include/exclude filters. +/// Current workspace metadata for the session, including its absolute filesystem path when available. /// ///
/// @@ -18499,12 +23728,15 @@ 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 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 path: Option, + /// Current workspace metadata, or null if not available + pub workspace: Option, } -/// Result of opening a session. +/// Identifies the target session. /// ///
/// @@ -18514,31 +23746,12 @@ 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. - #[serde(skip_serializing_if = "Option::is_none")] - pub startup_prompts: Option>, - /// Outcome of the open request. - pub status: SessionsOpenStatus, +pub struct SessionWorkspacesReadAutopilotObjectiveParams { + /// Target session identifier + pub session_id: SessionId, } -/// Remote session connection result. +/// Autopilot objective file content, or null when missing. /// ///
/// @@ -18548,14 +23761,12 @@ 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 session_id: SessionId, +pub struct SessionWorkspacesReadAutopilotObjectiveResult { + /// Autopilot objective file content, or null when missing. + pub content: Option, } -/// Sessions matching the filter, ordered most-recently-modified first. +/// Result of writing the autopilot objective file. /// ///
/// @@ -18565,12 +23776,12 @@ 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 SessionWorkspacesWriteAutopilotObjectiveResult { + /// Filesystem operation performed. + pub operation: String, } -/// ID of the local session bound to the given GitHub task, or omitted when none. +/// Identifies the target session. /// ///
/// @@ -18580,13 +23791,12 @@ 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 - #[serde(skip_serializing_if = "Option::is_none")] - pub session_id: Option, +pub struct SessionWorkspacesDeleteAutopilotObjectiveParams { + /// Target session identifier + pub session_id: SessionId, } -/// Map of sessionId -> on-disk size in bytes for each session's workspace directory. +/// Result of deleting the autopilot objective file. /// ///
/// @@ -18596,12 +23806,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 SessionWorkspacesDeleteAutopilotObjectiveResult { + /// True when a file was deleted. + pub deleted: bool, } -/// Map of sessionId -> bytes freed by removing the session's workspace directory. +/// Identifies the target session. /// ///
/// @@ -18611,12 +23821,12 @@ 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 SessionWorkspacesAutopilotObjectiveExistsParams { + /// Target session identifier + pub session_id: SessionId, } -/// Outcome of the prune operation: deleted IDs, dry-run candidates, skipped IDs, total bytes freed, and the dry-run flag. +/// Whether the autopilot objective file exists. /// ///
/// @@ -18626,20 +23836,23 @@ 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 SessionWorkspacesAutopilotObjectiveExistsResult { + /// True when the objective file exists. + pub exists: bool, } -/// The enriched metadata records, with summary and context fields backfilled where available. Sessions confirmed empty and unnamed are omitted. +#[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. /// ///
/// @@ -18649,12 +23862,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 SessionWorkspacesSaveLargePasteResult { + /// Saved-paste descriptor, or null when the workspace is unavailable (e.g. CCA runtime, non-infinite sessions, remote sessions) + pub saved: Option, } -/// Queued repo-level startup prompts and the total hook command count after loading. +/// Workspace diff result for the requested mode. /// ///
/// @@ -18664,14 +23877,24 @@ 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 SessionWorkspacesDiffResult { + /// 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, } -/// Wrapper for the singleton's current status. +/// Identifies the target session. /// ///
/// @@ -18681,12 +23904,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 SessionCompletionsGetTriggerCharactersParams { + /// Target session identifier + pub session_id: SessionId, } -/// Outcome of a transferRemoteControl call. +/// 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`). /// ///
/// @@ -18696,14 +23919,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 SessionCompletionsGetTriggerCharactersResult { + /// Trigger characters advertised by the host (e.g. `["@", "#"]`). Empty disables host-driven completions for the session. + pub trigger_characters: Vec, } -/// Wrapper for the singleton's current status. +/// Host-driven completion items for the current composer input. Empty when the host returns no items or does not support completions. /// ///
/// @@ -18713,12 +23934,12 @@ 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 SessionCompletionsRequestResult { + /// Completion items in host-ranked order. + pub items: Vec, } -/// Outcome of a stopRemoteControl call. +/// Identifies the target session. /// ///
/// @@ -18728,14 +23949,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 SessionInstructionsGetSourcesParams { + /// Target session identifier + pub session_id: SessionId, } -/// Wrapper for the singleton's current status. +/// Instruction sources loaded for the session, in merge order. /// ///
/// @@ -18745,12 +23964,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 SessionInstructionsGetSourcesResult { + /// Instruction sources for the session + pub sources: Vec, } -/// Handle for releasing the extension tool registration. +/// Indicates whether fleet mode was successfully activated. /// ///
/// @@ -18760,13 +23979,12 @@ 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 SessionFleetStartResult { + /// Whether fleet mode was successfully activated + pub started: bool, } -/// Identifies the target session. +/// Agents available to the session. /// ///
/// @@ -18776,12 +23994,12 @@ pub(crate) struct SessionsRegisterExtensionToolsOnSessionResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionSuspendParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionAgentListResult { + /// Available agents + pub agents: Vec, } -/// Result of sending a user message +/// Identifies the target session. /// ///
/// @@ -18791,12 +24009,12 @@ 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 SessionAgentGetCurrentParams { + /// Target session identifier + pub session_id: SessionId, } -/// Result of sending zero or more user messages +/// The currently selected custom agent, or null when using the default agent. /// ///
/// @@ -18806,12 +24024,12 @@ 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 SessionAgentGetCurrentResult { + /// Currently selected custom agent, or null if using the default agent + pub agent: AgentInfo, } -/// Result of aborting the current turn +/// The newly selected custom agent. /// ///
/// @@ -18821,15 +24039,12 @@ pub struct SessionSendMessagesResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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, +pub struct SessionAgentSelectResult { + /// The newly selected custom agent + pub agent: AgentInfo, } -/// Result of interrupting the main agent turn. +/// Identifies the target session. /// ///
/// @@ -18839,9 +24054,9 @@ pub struct SessionAbortResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionInterruptMainTurnResult { - /// Whether an in-flight main agent turn was interrupted. False when the main loop was not processing. - pub interrupted: bool, +pub struct SessionAgentDeselectParams { + /// Target session identifier + pub session_id: SessionId, } /// Identifies the target session. @@ -18854,12 +24069,12 @@ pub struct SessionInterruptMainTurnResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionCancelAllBackgroundAgentsParams { +pub struct SessionAgentReloadParams { /// Target session identifier pub session_id: SessionId, } -/// Identifies the target session. +/// Custom agents available to the session after reloading definitions from disk. /// ///
/// @@ -18869,12 +24084,12 @@ pub struct SessionCancelAllBackgroundAgentsParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionGitHubAuthGetStatusParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionAgentReloadResult { + /// Reloaded custom agents + pub agents: Vec, } -/// Authentication status and account metadata for the session. +/// Identifier assigned to the newly started background agent task. /// ///
/// @@ -18884,27 +24099,12 @@ pub struct SessionGitHubAuthGetStatusParams { ///
#[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 - #[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 SessionTasksStartAgentResult { + /// Generated agent ID for the background task + pub agent_id: String, } -/// Indicates whether the credential update succeeded. +/// Identifies the target session. /// ///
/// @@ -18914,15 +24114,12 @@ pub struct SessionGitHubAuthGetStatusResult { ///
#[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). - #[serde(skip_serializing_if = "Option::is_none")] - pub copilot_user_resolved: Option, - /// Whether the operation succeeded - pub success: bool, +pub struct SessionTasksListParams { + /// Target session identifier + pub session_id: SessionId, } -/// Result of collecting a redacted debug bundle. +/// Background tasks currently tracked by the session. /// ///
/// @@ -18932,16 +24129,9 @@ 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. - #[serde(skip_serializing_if = "Option::is_none")] - pub skipped_entries: Option>, +pub struct SessionTasksListResult { + /// Currently tracked tasks + pub tasks: Vec, } /// Identifies the target session. @@ -18954,12 +24144,12 @@ pub struct SessionDebugCollectLogsResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionCanvasListParams { +pub struct SessionTasksRefreshParams { /// Target session identifier pub session_id: SessionId, } -/// Declared canvases available in this 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. /// ///
/// @@ -18969,10 +24159,7 @@ 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 SessionTasksRefreshResult {} /// Identifies the target session. /// @@ -18984,12 +24171,12 @@ pub struct SessionCanvasListResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionCanvasListOpenParams { +pub struct SessionTasksWaitForPendingParams { /// Target session identifier pub session_id: SessionId, } -/// Live open-canvas snapshot. +/// 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). /// ///
/// @@ -18999,12 +24186,9 @@ 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 SessionTasksWaitForPendingResult {} -/// Open canvas instance snapshot. +/// Progress information for the task, or null when no task with that ID is tracked. /// ///
/// @@ -19014,34 +24198,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 SessionTasksGetProgressResult { + /// Progress information for the task, discriminated by type. Returns null when no task with this ID is currently tracked. + pub progress: Option, } -/// Canvas action invocation result. +/// Identifies the target session. /// ///
/// @@ -19051,13 +24213,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 SessionTasksGetCurrentPromotableParams { + /// Target session identifier + pub session_id: SessionId, } -/// Complete current or terminal factory run envelope. +/// The first sync-waiting task that can currently be promoted to background mode. /// ///
/// @@ -19067,29 +24228,13 @@ 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. - #[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 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 snapshot: Option, - /// Current or terminal factory run status. - pub status: FactoryRunStatus, + pub task: Option, } -/// Resolved persisted factory identity and resumed run envelope. +/// Indicates whether the task was successfully promoted to background mode. /// ///
/// @@ -19099,14 +24244,12 @@ pub struct SessionFactoryRunResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFactoryResumeResult { - /// Persisted factory name resolved for the resumed run. - pub factory_name: String, - /// Terminal resumed run envelope. - pub run: FactoryRunResult, +pub struct SessionTasksPromoteToBackgroundResult { + /// Whether the task was successfully promoted to background mode + pub promoted: bool, } -/// Complete current or terminal factory run envelope. +/// Identifies the target session. /// ///
/// @@ -19116,29 +24259,12 @@ pub struct SessionFactoryResumeResult { ///
#[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. - #[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 SessionTasksPromoteCurrentToBackgroundParams { + /// Target session identifier + pub session_id: SessionId, } -/// Factory runs in durable creation order. +/// The promoted task as it now exists in background mode, omitted if no promotable task was waiting. /// ///
/// @@ -19148,11 +24274,13 @@ pub struct SessionFactoryGetRunResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFactoryListRunsResult { - pub runs: 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, } -/// Full factory run observability detail. +/// Indicates whether the background task was successfully cancelled. /// ///
/// @@ -19162,32 +24290,12 @@ pub struct SessionFactoryListRunsResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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, +pub struct SessionTasksCancelResult { + /// Whether the task was successfully cancelled + pub cancelled: bool, } -/// A bidirectional page of factory progress. +/// Indicates whether the task was removed. False when the task does not exist or is still running/idle. /// ///
/// @@ -19197,17 +24305,12 @@ pub struct SessionFactoryGetRunDetailResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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, +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, } -/// Complete current or terminal factory run envelope. +/// Indicates whether the message was delivered, with an error message when delivery failed. /// ///
/// @@ -19217,29 +24320,15 @@ pub struct SessionFactoryGetRunProgressResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFactoryCancelResult { - /// Error message for an errored run. +pub struct SessionTasksSendMessageResult { + /// Error message if delivery failed #[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, + /// Whether the message was successfully delivered or steered + pub sent: bool, } -/// Acknowledgement that a factory request was accepted. +/// Identifies the target session. /// ///
/// @@ -19249,9 +24338,12 @@ pub struct SessionFactoryCancelResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFactoryLogResult {} +pub struct SessionSkillsListParams { + /// Target session identifier + pub session_id: SessionId, +} -/// Result of one factory-scoped subagent call. +/// Skills available to the session, with their enabled state. /// ///
/// @@ -19261,13 +24353,42 @@ 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 SessionSkillsListResult { + /// Available skills + pub skills: 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 SessionSkillsGetInvokedParams { + /// Target session identifier + pub session_id: SessionId, +} + +/// Skills invoked during this session, ordered by invocation time (most recent last). +/// +///
+/// +/// **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 SessionSkillsGetInvokedResult { + /// Skills invoked during this session, ordered by invocation time (most recent last) + pub skills: Vec, } -/// Result of reading a factory journal entry. +/// Identifies the target session. /// ///
/// @@ -19277,15 +24398,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 SessionSkillsReloadParams { + /// Target session identifier + pub session_id: SessionId, } -/// Acknowledgement that a factory request was accepted. +/// Diagnostics from reloading skill definitions, with warnings and errors as separate lists. /// ///
/// @@ -19295,7 +24413,12 @@ pub struct SessionFactoryJournalGetResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFactoryJournalPutResult {} +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. /// @@ -19307,12 +24430,12 @@ pub struct SessionFactoryJournalPutResult {} ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionModelGetCurrentParams { +pub struct SessionSkillsEnsureLoadedParams { /// Target session identifier pub session_id: SessionId, } -/// 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. /// ///
/// @@ -19322,19 +24445,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 SessionMcpListParams { + /// Target session identifier + pub session_id: SessionId, } -/// The model identifier active on the session after the switch. +/// MCP servers configured for the session, with their connection status and host-level state. /// ///
/// @@ -19344,16 +24460,15 @@ pub struct SessionModelGetCurrentResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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 deferred: Option, - /// Currently active model identifier after the switch +pub struct SessionMcpListResult { + /// Host-level state, omitted when no MCP host is initialized. #[serde(skip_serializing_if = "Option::is_none")] - pub model_id: Option, + pub host: Option, + /// Configured MCP servers + pub servers: Vec, } -/// 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. +/// Tools exposed by the connected MCP server. Throws when the server is not connected. /// ///
/// @@ -19363,12 +24478,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 SessionMcpListToolsResult { + /// Tools exposed by the server. + pub tools: Vec, } -/// The list of models available to this session. +/// Identifies the target session. /// ///
/// @@ -19378,15 +24493,9 @@ 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. - #[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>, +pub struct SessionMcpReloadParams { + /// Target session identifier + pub session_id: SessionId, } /// Identifies the target session. @@ -19399,12 +24508,12 @@ pub struct SessionModelListResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionModeGetParams { +pub struct SessionMcpMoveLoadingToBackgroundParams { /// Target session identifier pub session_id: SessionId, } -/// Identifies the target session. +/// Result of moving in-flight MCP loading to the background. /// ///
/// @@ -19414,12 +24523,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 SessionMcpMoveLoadingToBackgroundResult { + /// Whether an in-flight MCP load was moved to the background, releasing turns that were waiting on it. False when no MCP load was in flight or the waiting turns had already been released. + pub moved_to_background: bool, } -/// The session's friendly name, or null when not yet set. +/// MCP server startup filtering result. /// ///
/// @@ -19429,12 +24538,18 @@ 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 SessionMcpReloadWithConfigResult { + /// Non-default servers allowed by policy + #[serde(skip_serializing_if = "Option::is_none")] + pub allowed_servers: Option>, + /// Servers whose connection attempt failed. + #[serde(skip_serializing_if = "Option::is_none")] + pub failed_servers: Option>, + /// Servers filtered out before startup + pub filtered_servers: Vec, } -/// Indicates whether the auto-generated summary was applied as the session's name. +/// Outcome of an MCP sampling execution: success result, failure error, or cancellation. /// ///
/// @@ -19444,12 +24559,18 @@ 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 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, } -/// Identifies the target session. +/// Indicates whether an in-flight sampling execution with the given requestId was found and cancelled. /// ///
/// @@ -19459,12 +24580,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 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, } -/// Existence, contents, and resolved path of the session plan file. +/// Env-value mode recorded on the session after the update. /// ///
/// @@ -19474,13 +24595,9 @@ 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 SessionMcpSetEnvValueModeResult { + /// Mode recorded on the session after the update + pub mode: McpSetEnvValueModeDetails, } /// Identifies the target session. @@ -19493,12 +24610,12 @@ pub struct SessionPlanReadResult { /// #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPlanDeleteParams { +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). /// ///
/// @@ -19508,12 +24625,12 @@ pub struct SessionPlanDeleteParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPlanReadSqlTodosParams { - /// 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, } -/// Todo rows read from the session SQL database. Empty when no session database is available. +/// Result of configuring GitHub MCP. /// ///
/// @@ -19523,12 +24640,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 SessionMcpConfigureGitHubResult { + /// Whether GitHub MCP configuration changed. + pub changed: bool, } -/// Identifies the target session. +/// Whether the named MCP server is running. /// ///
/// @@ -19538,12 +24655,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 SessionMcpIsServerRunningResult { + /// True if the server has an active client and transport. + pub running: bool, } -/// Todo rows + dependency edges read from the session SQL database. +/// Indicates whether the pending MCP OAuth response was accepted. /// ///
/// @@ -19553,14 +24670,12 @@ 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 SessionMcpOauthHandlePendingRequestResult { + /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. + pub success: bool, } -/// Identifies the target session. +/// OAuth authorization URL the caller should open, or empty when cached tokens already authenticated the server. /// ///
/// @@ -19570,54 +24685,13 @@ pub struct SessionPlanReadSqlTodosWithDependenciesResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionWorkspacesGetWorkspaceParams { - /// Target session identifier - pub session_id: SessionId, -} - -#[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, +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 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 authorization_url: Option, } -/// Current workspace metadata for the session, including its absolute filesystem path when available. +/// Indicates whether the pending MCP OAuth response was accepted. /// ///
/// @@ -19627,57 +24701,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, -} - -#[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 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 SessionMcpOauthRespondResult { + /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. + pub success: bool, } -/// Current workspace metadata for the session, including its absolute filesystem path when available. +/// Indicates whether the pending MCP headers refresh response was accepted. /// ///
/// @@ -19687,57 +24716,27 @@ pub struct SessionWorkspacesUpdateMetadataResultWorkspace { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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, +pub struct SessionMcpHeadersHandlePendingHeadersRefreshRequestResult { + /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. + pub success: bool, } +/// Resource contents returned by the 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. +/// +///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionWorkspacesEnsureResultWorkspace { - #[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 SessionMcpAppsReadResourceResult { + /// Resource contents returned by the server + pub contents: Vec, } -/// Current workspace metadata for the session, including its absolute filesystem path when available. +/// App-callable tools from the named MCP server. /// ///
/// @@ -19747,12 +24746,9 @@ pub struct SessionWorkspacesEnsureResultWorkspace { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -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 path: Option, - /// Current workspace metadata, or null if not available - pub workspace: Option, +pub struct SessionMcpAppsListToolsResult { + /// App-callable tools from the server + pub tools: Vec>, } /// Identifies the target session. @@ -19765,12 +24761,12 @@ pub struct SessionWorkspacesEnsureResult { /// #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionWorkspacesListFilesParams { +pub struct SessionMcpAppsGetHostContextParams { /// Target session identifier pub session_id: SessionId, } -/// Relative paths of files stored in the session workspace files directory. +/// Current host context advertised to MCP App guests. /// ///
/// @@ -19780,12 +24776,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 SessionMcpAppsGetHostContextResult { + /// Current host context + pub context: McpAppsHostContextDetails, } -/// Contents of the requested workspace file as a UTF-8 string. +/// Diagnostic snapshot of MCP Apps wiring for the named server. /// ///
/// @@ -19795,12 +24791,14 @@ 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 SessionMcpAppsDiagnoseResult { + /// Capability negotiation snapshot + pub capability: McpAppsDiagnoseCapability, + /// What the server returned for this session + pub server: McpAppsDiagnoseServer, } -/// Identifies the target session. +/// Resource contents returned by the MCP server. /// ///
/// @@ -19810,12 +24808,12 @@ pub struct SessionWorkspacesReadFileResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionWorkspacesListCheckpointsParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionMcpResourcesReadResult { + /// Resource contents returned by the server + pub contents: Vec, } -/// Workspace checkpoints in chronological order; empty when the workspace is not enabled. +/// One page of resources advertised by the named MCP server. /// ///
/// @@ -19825,12 +24823,15 @@ 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 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, } -/// Checkpoint content as a UTF-8 string, or null when the checkpoint or workspace is missing. +/// One page of resource templates advertised by the named MCP server. /// ///
/// @@ -19840,12 +24841,15 @@ 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, +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, } -/// Persisted summary metadata and refreshed workspace metadata. +/// Identifies the target session. /// ///
/// @@ -19855,56 +24859,58 @@ pub struct SessionWorkspacesReadCheckpointResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionWorkspacesAddSummaryResult { - #[serde(skip_serializing_if = "Option::is_none")] - pub summary: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub workspace: Option, +pub struct SessionPluginsListParams { + /// Target session identifier + pub session_id: SessionId, } +/// Plugins installed for the session, with their enabled state and version 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 SessionWorkspacesTruncateSummariesResultWorkspace { +pub struct SessionPluginsListResult { + /// Installed plugins + pub plugins: Vec, +} + +/// A snapshot of the provider endpoint the session is currently configured to talk to. +/// +///
+/// +/// **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 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 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, + 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 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, + pub session_token: Option, + /// Transport to be used for provider requests. #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, - #[serde(rename = "remote_steerable", skip_serializing_if = "Option::is_none")] - pub remote_steerable: Option, + 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 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 wire_api: Option, } -/// Current workspace metadata for the session, including its absolute filesystem path when available. +/// The selectable model entries synthesized for the models added by this call. /// ///
/// @@ -19914,12 +24920,27 @@ pub struct SessionWorkspacesTruncateSummariesResultWorkspace { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionWorkspacesTruncateSummariesResult { - /// Absolute filesystem path to the workspace directory. Omitted when the session has no workspace (e.g. remote sessions). +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, +} + +/// Indicates whether the session options patch was applied successfully. +/// +///
+/// +/// **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 SessionOptionsUpdateResult { + /// Number of hooks loaded from installed plugins, returned when installedPlugins is updated #[serde(skip_serializing_if = "Option::is_none")] - pub path: Option, - /// Current workspace metadata, or null if not available - pub workspace: Option, + pub plugin_hook_count: Option, + /// Whether the operation succeeded + pub success: bool, } /// Identifies the target session. @@ -19932,12 +24953,12 @@ pub struct SessionWorkspacesTruncateSummariesResult { /// #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionWorkspacesReadAutopilotObjectiveParams { +pub struct SessionExtensionsListParams { /// Target session identifier pub session_id: SessionId, } -/// Autopilot objective file content, or null when missing. +/// Extensions discovered for the session, with their current status. /// ///
/// @@ -19947,12 +24968,27 @@ pub struct SessionWorkspacesReadAutopilotObjectiveParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionWorkspacesReadAutopilotObjectiveResult { - /// Autopilot objective file content, or null when missing. - pub content: Option, +pub struct SessionExtensionsListResult { + /// Discovered extensions and their current status + pub extensions: 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 SessionExtensionsReloadParams { + /// Target session identifier + pub session_id: SessionId, } -/// Result of writing the autopilot objective file. +/// Rust-owned built-in tool descriptors for the session. /// ///
/// @@ -19962,12 +24998,12 @@ pub struct SessionWorkspacesReadAutopilotObjectiveResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionWorkspacesWriteAutopilotObjectiveResult { - /// Filesystem operation performed. - pub operation: String, +pub struct SessionToolsGetBuiltinDescriptorsResult { + /// Built-in tool descriptors materialized for the session. + pub tools: Vec, } -/// Identifies the target session. +/// Task completion notification with summary from the agent /// ///
/// @@ -19977,12 +25013,25 @@ pub struct SessionWorkspacesWriteAutopilotObjectiveResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionWorkspacesDeleteAutopilotObjectiveParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionToolsTaskCompleteEventDataResult { + /// 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 + #[serde(skip_serializing_if = "Option::is_none")] + pub summary: Option, } -/// Result of deleting the autopilot objective file. +/// Indicates whether the external tool call result was handled successfully. /// ///
/// @@ -19992,9 +25041,9 @@ pub struct SessionWorkspacesDeleteAutopilotObjectiveParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionWorkspacesDeleteAutopilotObjectiveResult { - /// True when a file was deleted. - pub deleted: bool, +pub struct SessionToolsHandlePendingToolCallResult { + /// Whether the tool call result was handled successfully + pub success: bool, } /// Identifies the target session. @@ -20007,12 +25056,12 @@ pub struct SessionWorkspacesDeleteAutopilotObjectiveResult { /// #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionWorkspacesAutopilotObjectiveExistsParams { +pub struct SessionToolsInitializeAndValidateParams { /// Target session identifier pub session_id: SessionId, } -/// Whether the autopilot objective file exists. +/// 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. /// ///
/// @@ -20022,23 +25071,24 @@ pub struct SessionWorkspacesAutopilotObjectiveExistsParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionWorkspacesAutopilotObjectiveExistsResult { - /// True when the objective file exists. - pub exists: bool, -} +pub struct SessionToolsInitializeAndValidateResult {} +/// 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 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 SessionToolsGetCurrentMetadataParams { + /// Target session identifier + pub session_id: SessionId, } -/// Descriptor for the saved paste file, or null when the workspace is unavailable. +/// Current lightweight tool metadata snapshot for the session. /// ///
/// @@ -20048,12 +25098,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 SessionToolsGetCurrentMetadataResult { + /// Current tool metadata, or null when tools have not been initialized yet + pub tools: Option>, } -/// Workspace diff result for the requested mode. +/// Empty result after replacing the calling connection's externally implemented tools. /// ///
/// @@ -20063,24 +25113,9 @@ 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. - #[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, -} +pub struct SessionToolsSetResult {} -/// Identifies the target session. +/// Empty result after applying subagent settings /// ///
/// @@ -20090,12 +25125,9 @@ 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 SessionToolsUpdateSubagentSettingsResult {} -/// 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`). +/// Slash commands available in the session, after applying any include/exclude filters. /// ///
/// @@ -20105,12 +25137,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 SessionCommandsListResult { + /// Commands available in this session + pub commands: Vec, } -/// Host-driven completion items for the current composer input. Empty when the host returns no items or does not support completions. +/// Whether finalizing the invocation effect succeeded, and the failure reason when it did not. /// ///
/// @@ -20120,12 +25152,15 @@ 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 SessionCommandsFinalizeInvocationEffectResult { + /// Failure reason when the invocation effect could not be finalized. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// Whether the pending invocation effect was finalized successfully. + pub success: bool, } -/// Identifies the target session. +/// Indicates whether the pending client-handled command was completed successfully. /// ///
/// @@ -20135,12 +25170,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 SessionCommandsHandlePendingCommandResult { + /// Whether the command was handled successfully + pub success: bool, } -/// Instruction sources loaded for the session, in merge order. +/// Error message produced while executing the command, if any. /// ///
/// @@ -20150,12 +25185,13 @@ 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 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, } -/// Indicates whether fleet mode was successfully activated. +/// Indicates whether the command was accepted into the local execution queue. /// ///
/// @@ -20165,12 +25201,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 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, } -/// Agents available to the session. +/// Indicates whether the queued-command response was matched to a pending request. /// ///
/// @@ -20180,9 +25216,9 @@ pub struct SessionFleetStartResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionAgentListResult { - /// Available agents - pub agents: 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, } /// Identifies the target session. @@ -20195,12 +25231,12 @@ pub struct SessionAgentListResult { /// #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionAgentGetCurrentParams { +pub struct SessionTelemetryGetEngagementIdParams { /// Target session identifier pub session_id: SessionId, } -/// The currently selected custom agent, or null when using the default agent. +/// Telemetry engagement ID for the session, when available. /// ///
/// @@ -20210,12 +25246,13 @@ 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 SessionTelemetryGetEngagementIdResult { + /// Current telemetry engagement ID, when available. + #[serde(skip_serializing_if = "Option::is_none")] + pub engagement_id: Option, } -/// The newly selected custom agent. +/// Completed transient query. Ordered chunks and the terminal outcome are also delivered through `ui.ephemeral_query` session events while it runs. /// ///
/// @@ -20225,12 +25262,12 @@ 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 SessionUiEphemeralQueryResult { + /// Answer returned by the model + pub answer: String, } -/// Identifies the target session. +/// The elicitation response (accept with form values, decline, or cancel) /// ///
/// @@ -20240,12 +25277,18 @@ pub struct SessionAgentSelectResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionAgentDeselectParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionUiElicitationResult { + /// MCP response metadata. + #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] + pub meta: Option>, + /// 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>, } -/// Identifies the target session. +/// Indicates whether the elicitation response was accepted; false if it was already resolved by another client. /// ///
/// @@ -20255,12 +25298,12 @@ pub struct SessionAgentDeselectParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionAgentReloadParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionUiHandlePendingElicitationResult { + /// Whether the response was accepted. False if the request was already resolved by another client. + pub success: bool, } -/// Custom agents available to the session after reloading definitions from disk. +/// Indicates whether the pending UI request was resolved by this call. /// ///
/// @@ -20270,12 +25313,12 @@ pub struct SessionAgentReloadParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionAgentReloadResult { - /// Reloaded custom agents - pub agents: 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, } -/// Identifier assigned to the newly started background agent task. +/// Indicates whether the pending UI request was resolved by this call. /// ///
/// @@ -20285,12 +25328,12 @@ 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 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. /// ///
/// @@ -20300,12 +25343,12 @@ pub struct SessionTasksStartAgentResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionTasksListParams { - /// 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, } -/// Background tasks currently tracked by the session. +/// Indicates whether the pending UI request was resolved by this call. /// ///
/// @@ -20315,12 +25358,12 @@ pub struct SessionTasksListParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionTasksListResult { - /// Currently tracked tasks - pub tasks: 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, } -/// Identifies the target session. +/// Indicates whether the pending UI request was resolved by this call. /// ///
/// @@ -20330,12 +25373,12 @@ 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 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, } -/// 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. /// ///
/// @@ -20345,9 +25388,12 @@ pub struct SessionTasksRefreshParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionTasksRefreshResult {} +pub struct SessionUiRegisterDirectAutoModeSwitchHandlerParams { + /// Target session identifier + pub session_id: SessionId, +} -/// Identifies the target session. +/// 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). /// ///
/// @@ -20357,12 +25403,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 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, } -/// 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). +/// Indicates whether the handle was active and the registration count was decremented. /// ///
/// @@ -20372,9 +25418,12 @@ pub struct SessionTasksWaitForPendingParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionTasksWaitForPendingResult {} +pub struct SessionUiUnregisterDirectAutoModeSwitchHandlerResult { + /// True if the handle was active and decremented the counter; false if the handle was unknown. + pub unregistered: bool, +} -/// Progress information for the task, or null when no task with that ID is tracked. +/// Indicates whether the operation succeeded. /// ///
/// @@ -20384,12 +25433,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 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. /// ///
/// @@ -20399,12 +25448,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 SessionPermissionsHandlePendingPermissionRequestResult { + /// Whether the permission request was handled successfully + pub success: bool, } -/// The first sync-waiting task that can currently be promoted to background mode. +/// List of pending permission requests reconstructed from event history. /// ///
/// @@ -20414,13 +25463,12 @@ 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. - #[serde(skip_serializing_if = "Option::is_none")] - pub task: Option, +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, } -/// Indicates whether the task was successfully promoted to background mode. +/// Indicates whether the operation succeeded. /// ///
/// @@ -20430,12 +25478,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 SessionPermissionsSetApproveAllResult { + /// Whether the operation succeeded + pub success: bool, } -/// Identifies the target session. +/// Indicates whether the requested permission mode was applied and reports the authoritative post-mutation mode. /// ///
/// @@ -20445,12 +25493,14 @@ 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 SessionPermissionsSetModeResult { + /// Authoritative permission mode after the mutation + pub mode: PermissionMode, + /// Whether the operation succeeded + pub success: bool, } -/// The promoted task as it now exists in background mode, omitted if no promotable task was waiting. +/// Current permission mode. /// ///
/// @@ -20460,13 +25510,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 SessionPermissionsGetModeResult { + /// Current permission mode + pub mode: PermissionMode, } -/// Indicates whether the background task was successfully cancelled. +/// Indicates whether the operation succeeded. /// ///
/// @@ -20476,12 +25525,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 SessionPermissionsModifyRulesResult { + /// Whether the operation succeeded + pub success: bool, } -/// Indicates whether the task was removed. False when the task does not exist or is still running/idle. +/// Indicates whether the operation succeeded. /// ///
/// @@ -20491,12 +25540,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 SessionPermissionsSetRequiredResult { + /// Whether the operation succeeded + pub success: bool, } -/// Indicates whether the message was delivered, with an error message when delivery failed. +/// Indicates whether the operation succeeded. /// ///
/// @@ -20506,15 +25555,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 SessionPermissionsResetSessionApprovalsResult { + /// Whether the operation succeeded + pub success: bool, } -/// Identifies the target session. +/// Indicates whether the operation succeeded. /// ///
/// @@ -20524,12 +25570,12 @@ 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 SessionPermissionsNotifyPromptShownResult { + /// Whether the operation succeeded + pub success: bool, } -/// Skills available to the session, with their enabled state. +/// Snapshot of the session's allow-listed directories and primary working directory. /// ///
/// @@ -20539,12 +25585,14 @@ pub struct SessionSkillsListParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionSkillsListResult { - /// Available skills - pub skills: Vec, +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, } -/// Identifies the target session. +/// Indicates whether the operation succeeded. /// ///
/// @@ -20554,12 +25602,12 @@ 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 SessionPermissionsPathsAddResult { + /// Whether the operation succeeded + pub success: bool, } -/// Skills invoked during this session, ordered by invocation time (most recent last). +/// Indicates whether the operation succeeded. /// ///
/// @@ -20569,12 +25617,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 SessionPermissionsPathsUpdatePrimaryResult { + /// Whether the operation succeeded + pub success: bool, } -/// Identifies the target session. +/// Indicates whether the supplied path is within the session's allowed directories. /// ///
/// @@ -20584,12 +25632,12 @@ pub struct SessionSkillsGetInvokedResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionSkillsReloadParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionPermissionsPathsIsPathWithinAllowedDirectoriesResult { + /// Whether the path is within the session's allowed directories + pub allowed: bool, } -/// Diagnostics from reloading skill definitions, with warnings and errors as separate lists. +/// Indicates whether the supplied path is within the session's workspace directory. /// ///
/// @@ -20599,14 +25647,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 SessionPermissionsPathsIsPathWithinWorkspaceResult { + /// Whether the path is within the session workspace directory + pub allowed: bool, } -/// Identifies the target session. +/// Resolved location-permissions key and type. /// ///
/// @@ -20616,12 +25662,14 @@ 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 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, } -/// Identifies the target session. +/// Summary of persisted location permissions applied to the session. /// ///
/// @@ -20631,12 +25679,22 @@ 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 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, } -/// MCP servers configured for the session, with their connection status and host-level state. +/// Indicates whether the operation succeeded. /// ///
/// @@ -20646,15 +25704,12 @@ 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. - #[serde(skip_serializing_if = "Option::is_none")] - pub host: Option, - /// Configured MCP servers - pub servers: Vec, +pub struct SessionPermissionsLocationsAddToolApprovalResult { + /// Whether the operation succeeded + pub success: bool, } -/// Tools exposed by the connected MCP server. Throws when the server is not connected. +/// Folder trust check result. /// ///
/// @@ -20664,12 +25719,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 SessionPermissionsFolderTrustIsTrustedResult { + /// Whether the folder is trusted + pub trusted: bool, } -/// Identifies the target session. +/// Indicates whether the operation succeeded. /// ///
/// @@ -20679,12 +25734,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 SessionPermissionsFolderTrustAddTrustedResult { + /// Whether the operation succeeded + pub success: bool, } -/// MCP server startup filtering result. +/// Indicates whether the operation succeeded. /// ///
/// @@ -20694,15 +25749,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 SessionPermissionsUrlsSetUnrestrictedModeResult { + /// Whether the operation succeeded + pub success: bool, } -/// Outcome of an MCP sampling execution: success result, failure error, or cancellation. +/// Identifier of the session event that was emitted for the log message. /// ///
/// @@ -20712,18 +25764,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 SessionLogResult { + /// The unique identifier of the emitted session event + pub event_id: String, } -/// Indicates whether an in-flight sampling execution with the given requestId was found and cancelled. +/// Identifies the target session. /// ///
/// @@ -20733,12 +25779,47 @@ 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 SessionMetadataSnapshotParams { + /// Target session identifier + pub session_id: SessionId, } -/// Env-value mode recorded on the session after the update. +/// 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 /// ///
/// @@ -20748,9 +25829,42 @@ 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 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, } /// Identifies the target session. @@ -20763,12 +25877,12 @@ pub struct SessionMcpSetEnvValueModeResult { /// #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMcpRemoveGitHubParams { +pub struct SessionMetadataIsProcessingParams { /// Target session identifier pub session_id: SessionId, } -/// Indicates whether the auto-managed `github` MCP server was removed (false when nothing to remove). +/// Indicates whether the local session is currently processing a turn or background continuation. /// ///
/// @@ -20778,12 +25892,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 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, } -/// Result of configuring GitHub MCP. +/// Identifies the target session. /// ///
/// @@ -20793,12 +25907,12 @@ 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 SessionMetadataActivityParams { + /// Target session identifier + pub session_id: SessionId, } -/// Whether the named MCP server is running. +/// Current activity flags for the session. /// ///
/// @@ -20808,27 +25922,40 @@ 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 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, } -/// Indicates whether the pending MCP OAuth response was accepted. -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
+/// Token-usage breakdown for the session's current context window #[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 success: bool, +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, } -/// OAuth authorization URL the caller should open, or empty when cached tokens already authenticated the server. +/// Token breakdown for the session's current context window, or null if uninitialized. /// ///
/// @@ -20838,13 +25965,12 @@ 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. - #[serde(skip_serializing_if = "Option::is_none")] - pub authorization_url: Option, +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 pending MCP OAuth response was accepted. +/// Identifies the target session. /// ///
/// @@ -20854,72 +25980,85 @@ pub struct SessionMcpOauthLoginResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMcpOauthRespondResult { - /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. - pub success: bool, +pub struct SessionMetadataGetContextAttributionParams { + /// Target session identifier + pub session_id: SessionId, } -/// Indicates whether the pending MCP headers refresh response was accepted. -/// -///
-/// -/// **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 SessionMcpHeadersHandlePendingHeadersRefreshRequestResult { - /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. - 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, } -/// Resource contents returned by the 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. -/// -///
+/// Successful compaction history for the session. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMcpAppsReadResourceResult { - /// Resource contents returned by the server - pub contents: Vec, +pub struct SessionMetadataGetContextAttributionResultContextAttributionCompactions { + /// Number of successful compactions in this session. + pub count: i64, } -/// App-callable tools from 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. -/// -///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMcpAppsListToolsResult { - /// App-callable tools from the server - pub tools: Vec>, +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, } -/// 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. -/// -///
+/// 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 SessionMcpAppsGetHostContextParams { - /// Target session identifier - pub session_id: SessionId, +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, } -/// Current host context advertised to MCP App guests. +/// Per-source attribution breakdown for the session's current context window, or null if uninitialized. /// ///
/// @@ -20929,12 +26068,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 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, } -/// Diagnostic snapshot of MCP Apps wiring for the named server. +/// The heaviest individual messages in the session's context window, most-expensive first. /// ///
/// @@ -20944,14 +26083,14 @@ 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 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, } -/// Resource contents returned by the MCP server. +/// 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. /// ///
/// @@ -20961,12 +26100,9 @@ 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 SessionMetadataRecordContextChangeResult {} -/// One page of resources advertised by the named MCP server. +/// 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. /// ///
/// @@ -20976,15 +26112,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 SessionMetadataSetWorkingDirectoryResult { + /// Working directory after the update + pub working_directory: String, } -/// One page of resource templates advertised by the named MCP server. +/// 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. /// ///
/// @@ -20994,12 +26127,13 @@ 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 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, } /// Identifies the target session. @@ -21012,12 +26146,12 @@ pub struct SessionMcpResourcesListTemplatesResult { /// #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPluginsListParams { +pub struct SessionSettingsSnapshotParams { /// Target session identifier pub session_id: SessionId, } -/// Plugins installed for the session, with their enabled state and version metadata. +/// Redacted, serializable view of session runtime settings for SDK boundary consumers. Secrets and raw feature flags are intentionally excluded. /// ///
/// @@ -21027,12 +26161,32 @@ pub struct SessionPluginsListParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPluginsListResult { - /// Installed plugins - pub plugins: Vec, +pub struct SessionSettingsSnapshotResult { + /// Name of the SDK client that created the session. + #[serde(skip_serializing_if = "Option::is_none")] + pub client_name: Option, + /// Redacted job settings. + pub job: SessionSettingsJobSnapshot, + /// Redacted model routing settings. + pub model: SessionSettingsModelSnapshot, + /// Online-evaluation settings safe for SDK consumers. + pub online_evaluation: SessionSettingsOnlineEvaluationSnapshot, + /// Redacted repository and host settings. + pub repo: SessionSettingsRepoSnapshot, + /// Session start time as Unix epoch milliseconds. + #[serde(skip_serializing_if = "Option::is_none")] + pub start_time_ms: Option, + /// Session timeout in milliseconds. + #[serde(skip_serializing_if = "Option::is_none")] + pub timeout_ms: Option, + /// Redacted validation and memory-tool settings. + pub validation: SessionSettingsValidationSnapshot, + /// Agent runtime version selector copied from the session settings, such as `latest` or a runtime release identifier. + #[serde(skip_serializing_if = "Option::is_none")] + pub version: Option, } -/// A snapshot of the provider endpoint the session is currently configured to talk to. +/// Batch content-exclusion result. Callers must fail closed when policy evaluation is unavailable. /// ///
/// @@ -21042,28 +26196,14 @@ 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 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, } -/// The selectable model entries synthesized for the models added by this call. +/// Identifier of the spawned process, used to correlate streamed output and exit notifications. /// ///
/// @@ -21073,12 +26213,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 SessionShellExecResult { + /// Unique identifier for tracking streamed output + pub process_id: String, } -/// Indicates whether the session options patch was applied successfully. +/// Indicates whether the signal was delivered; false if the process was unknown or already exited. /// ///
/// @@ -21088,15 +26228,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 SessionShellKillResult { + /// Whether the signal was sent successfully + pub killed: bool, } -/// Identifies the target session. +/// Result of a user-requested shell command. /// ///
/// @@ -21106,12 +26243,22 @@ 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 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, } -/// Extensions discovered for the session, with their current status. +/// Cancellation result for a user-requested shell command. /// ///
/// @@ -21121,12 +26268,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 SessionShellCancelUserRequestedResult { + /// Whether an in-flight execution was found and signalled to cancel + pub cancelled: bool, } -/// Identifies the target session. +/// Compaction outcome with the number of tokens and messages removed, summary text, and the resulting context window breakdown. /// ///
/// @@ -21136,12 +26283,22 @@ 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 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, } -/// Indicates whether the external tool call result was handled successfully. +/// Number of events that were removed by the truncation. /// ///
/// @@ -21151,9 +26308,15 @@ 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 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. @@ -21166,12 +26329,12 @@ pub struct SessionToolsHandlePendingToolCallResult { /// #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionToolsInitializeAndValidateParams { +pub struct SessionHistoryListRewindPointsParams { /// Target session identifier pub session_id: SessionId, } -/// 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. +/// Rewind points and file-change-tracking availability for the session. /// ///
/// @@ -21181,9 +26344,17 @@ pub struct SessionToolsInitializeAndValidateParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionToolsInitializeAndValidateResult {} +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 unavailable_reason: Option, +} -/// Identifies the target session. +/// Files and aggregate changes for a prospective rewind. /// ///
/// @@ -21193,12 +26364,19 @@ 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 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, } -/// Current lightweight tool metadata snapshot for the session. +/// Structured outcome of a rewind request. /// ///
/// @@ -21208,12 +26386,22 @@ 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 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, } -/// Empty result after applying subagent settings +/// Identifies the target session. /// ///
/// @@ -21223,9 +26411,12 @@ pub struct SessionToolsGetCurrentMetadataResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionToolsUpdateSubagentSettingsResult {} +pub struct SessionHistoryCancelBackgroundCompactionParams { + /// Target session identifier + pub session_id: SessionId, +} -/// Slash commands available in the session, after applying any include/exclude filters. +/// Indicates whether an in-progress background compaction was cancelled. /// ///
/// @@ -21235,12 +26426,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 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, } -/// Indicates whether the pending client-handled command was completed successfully. +/// Identifies the target session. /// ///
/// @@ -21250,12 +26441,12 @@ pub struct SessionCommandsListResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionCommandsHandlePendingCommandResult { - /// Whether the command was handled successfully - pub success: bool, +pub struct SessionHistoryAbortManualCompactionParams { + /// Target session identifier + pub session_id: SessionId, } -/// Error message produced while executing the command, if any. +/// Indicates whether an in-progress manual compaction was aborted. /// ///
/// @@ -21265,13 +26456,12 @@ 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 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, } -/// Indicates whether the command was accepted into the local execution queue. +/// Identifies the target session. /// ///
/// @@ -21281,12 +26471,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 SessionHistorySummarizeForHandoffParams { + /// Target session identifier + pub session_id: SessionId, } -/// Indicates whether the queued-command response was matched to a pending request. +/// Markdown summary of the conversation context (empty when not available). /// ///
/// @@ -21296,12 +26486,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 success: bool, +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, } -/// Identifies the target session. +/// What a successful clear removed. A clear that could not be applied rejects instead of reporting a count. /// ///
/// @@ -21311,12 +26501,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 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, } -/// Telemetry engagement ID for the session, when available. +/// Identifies the target session. /// ///
/// @@ -21326,13 +26516,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 SessionQueuePendingItemsParams { + /// Target session identifier + pub session_id: SessionId, } -/// Transient answer generated from current conversation context. +/// Snapshot of the session's pending queued items and immediate-steering messages. /// ///
/// @@ -21342,12 +26531,17 @@ 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 SessionQueuePendingItemsResult { + /// How many leading entries of `steeringMessages` have already been folded into the running turn (and so have an emitted `user.message`), as opposed to still waiting for one. Absent for hosts that do not distinguish the two. + #[serde(skip_serializing_if = "Option::is_none")] + pub in_flight_steering_count: Option, + /// 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 elicitation response (accept with form values, decline, or cancel) +/// Identifies the target session. /// ///
/// @@ -21357,15 +26551,12 @@ 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 SessionQueueSnapshotParams { + /// Target session identifier + pub session_id: SessionId, } -/// Indicates whether the elicitation response was accepted; false if it was already resolved by another client. +/// Internal snapshot of native queue state for local session orchestration. /// ///
/// @@ -21375,12 +26566,20 @@ 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 success: bool, +pub struct SessionQueueSnapshotResult { + /// 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, } -/// Indicates whether the pending UI request was resolved by this call. +/// Result of moving a queued item. /// ///
/// @@ -21390,12 +26589,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 SessionQueueMoveItemResult { + /// True when the item changed position; false when it was already at the requested position. + pub changed: bool, } -/// Indicates whether the pending UI request was resolved by this call. +/// Result of inserting a queued message. /// ///
/// @@ -21405,12 +26604,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 success: bool, +pub struct SessionQueueInsertAtResult { + /// Fresh stable opaque id assigned to the inserted item. + pub id: String, } -/// Indicates whether the pending UI request was resolved by this call. +/// Result of removing a queued item. /// ///
/// @@ -21420,12 +26619,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 success: bool, +pub struct SessionQueueRemoveAtResult { + /// True when the addressed item was removed. + pub removed: bool, } -/// Indicates whether the pending UI request was resolved by this call. +/// Result of editing a queued message. /// ///
/// @@ -21435,12 +26634,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 SessionQueueUpdateTextResult { + /// True when the stored text changed. + pub updated: bool, } -/// Indicates whether the pending UI request was resolved by this call. +/// Result of duplicating a queued item. /// ///
/// @@ -21450,12 +26649,12 @@ 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 SessionQueueDuplicateAtResult { + /// Fresh stable opaque id assigned to the duplicate. + pub id: String, } -/// Identifies the target session. +/// Result of trying to steer a queued message into a live turn. /// ///
/// @@ -21465,12 +26664,12 @@ pub struct SessionUiHandlePendingExitPlanModeResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionUiRegisterDirectAutoModeSwitchHandlerParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionQueueSendNowResult { + /// True when the item was accepted into the steering lane; false when no main turn was live. + pub steered: bool, } -/// 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. /// ///
/// @@ -21480,12 +26679,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 SessionQueueHasPendingParams { + /// Target session identifier + pub session_id: SessionId, } -/// Indicates whether the handle was active and the registration count was decremented. +/// Whether the native queue has pending work. /// ///
/// @@ -21495,12 +26694,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 SessionQueueHasPendingResult { + /// True when queued or immediate native work is pending. + pub has_pending: bool, } -/// Indicates whether the operation succeeded. +/// Whether a deferred-idle drain should run. /// ///
/// @@ -21510,12 +26709,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 SessionQueueBeginDeferredIdleDrainResult { + /// True when the host should run finishDeferredIdleDrain asynchronously. + pub should_drain: bool, } -/// Indicates whether the permission decision was applied; false when the request was already resolved. +/// Action selected by the native deferred-idle drain. /// ///
/// @@ -21525,12 +26724,14 @@ 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 SessionQueueFinishDeferredIdleDrainResult { + /// Whether the deferred idle was caused by an aborted foreground turn. + pub aborted: bool, + /// One of none, processQueue, or emitSessionIdle. + pub action: String, } -/// List of pending permission requests reconstructed from event history. +/// Identifies the target session. /// ///
/// @@ -21540,12 +26741,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 SessionQueueRemoveMostRecentParams { + /// Target session identifier + pub session_id: SessionId, } -/// Indicates whether the operation succeeded. +/// Indicates whether a user-facing pending item was removed. /// ///
/// @@ -21555,12 +26756,12 @@ 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 SessionQueueRemoveMostRecentResult { + /// True if a user-facing pending item was removed (LIFO across both queues); false when no removable items remained. + pub removed: bool, } -/// Indicates whether the operation succeeded and reports the post-mutation state. +/// Identifies the target session. /// ///
/// @@ -21570,17 +26771,12 @@ pub struct SessionPermissionsSetApproveAllResult { ///
#[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 SessionQueueClearParams { + /// Target session identifier + pub session_id: SessionId, } -/// Current allow-all permission mode. +/// Indicates whether a user-facing pending item was removed. /// ///
/// @@ -21590,15 +26786,12 @@ pub struct SessionPermissionsSetAllowAllResult { ///
#[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 SessionQueueConsumeSystemNotificationsResult { + /// True if a user-facing pending item was removed (LIFO across both queues); false when no removable items remained. + pub removed: bool, } -/// Indicates whether the operation succeeded. +/// Identifies the target session. /// ///
/// @@ -21608,12 +26801,12 @@ pub struct SessionPermissionsGetAllowAllResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPermissionsModifyRulesResult { - /// Whether the operation succeeded - pub success: bool, +pub struct SessionQueueEnqueueResumePendingParams { + /// Target session identifier + pub session_id: SessionId, } -/// Indicates whether the operation succeeded. +/// Result of enqueueing the resume-pending wake item. /// ///
/// @@ -21623,12 +26816,12 @@ pub struct SessionPermissionsModifyRulesResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPermissionsSetRequiredResult { - /// Whether the operation succeeded - pub success: bool, +pub struct SessionQueueEnqueueResumePendingResult { + /// True when a wake item was newly queued. + pub queued: bool, } -/// Indicates whether the operation succeeded. +/// Identifies the target session. /// ///
/// @@ -21638,12 +26831,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 SessionQueueProcessParams { + /// Target session identifier + pub session_id: SessionId, } -/// Indicates whether the operation succeeded. +/// Batch of session events returned by a read, with cursor and continuation metadata. /// ///
/// @@ -21653,12 +26846,18 @@ 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 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. 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 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, + /// 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 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, } -/// Snapshot of the session's allow-listed directories and primary working directory. +/// Identifies the target session. /// ///
/// @@ -21668,14 +26867,12 @@ 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 SessionEventLogTailParams { + /// Target session identifier + pub session_id: SessionId, } -/// Indicates whether the operation succeeded. +/// 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). /// ///
/// @@ -21685,12 +26882,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 SessionEventLogTailResult { + /// 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). + pub cursor: String, } -/// Indicates whether the operation succeeded. +/// Opaque handle representing an event-type interest registration. /// ///
/// @@ -21700,12 +26897,12 @@ 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 SessionEventLogRegisterInterestResult { + /// 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, } -/// Indicates whether the supplied path is within the session's allowed directories. +/// Indicates whether the operation succeeded. /// ///
/// @@ -21715,12 +26912,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 SessionEventLogReleaseInterestResult { + /// Whether the operation succeeded + pub success: bool, } -/// Indicates whether the supplied path is within the session's workspace directory. +/// Identifies the target session. /// ///
/// @@ -21730,12 +26927,12 @@ 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 SessionUsageGetMetricsParams { + /// Target session identifier + pub session_id: SessionId, } -/// Resolved location-permissions key and type. +/// Accumulated session usage metrics, including premium request cost, token counts, model breakdown, and code-change totals. /// ///
/// @@ -21745,14 +26942,38 @@ 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 SessionUsageGetMetricsResult { + /// Per-agent usage metrics, keyed by agent instance identifier. The main conversation uses the stable key `main`. + #[serde(skip_serializing_if = "Option::is_none")] + pub agent_metrics: Option>, + /// 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, } -/// Summary of persisted location permissions applied to the session. +/// GitHub URL for the session and a flag indicating whether remote steering is enabled. /// ///
/// @@ -21762,22 +26983,15 @@ 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 SessionRemoteEnableResult { + /// Whether remote steering is enabled + pub remote_steerable: bool, + /// GitHub frontend URL for this session + #[serde(skip_serializing_if = "Option::is_none")] + pub url: Option, } -/// Indicates whether the operation succeeded. +/// Identifies the target session. /// ///
/// @@ -21787,12 +27001,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 SessionRemoteDisableParams { + /// Target session identifier + pub session_id: SessionId, } -/// Folder trust check result. +/// 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. /// ///
/// @@ -21802,12 +27016,9 @@ 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 SessionRemoteNotifySteerableChangedResult {} -/// Indicates whether the operation succeeded. +/// Identifies the target session. /// ///
/// @@ -21817,12 +27028,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 SessionVisibilityGetParams { + /// Target session identifier + pub session_id: SessionId, } -/// Indicates whether the operation succeeded. +/// Current sharing status and shareable GitHub URL for a session. /// ///
/// @@ -21832,12 +27043,18 @@ pub struct SessionPermissionsFolderTrustAddTrustedResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPermissionsUrlsSetUnrestrictedModeResult { - /// Whether the operation succeeded - pub success: bool, +pub struct SessionVisibilityGetResult { + /// 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, } -/// Identifier of the session event that was emitted for the log message. +/// Effective sharing status and shareable GitHub URL after updating session visibility. /// ///
/// @@ -21847,9 +27064,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 SessionVisibilitySetResult { + /// 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, } /// Identifies the target session. @@ -21862,47 +27085,27 @@ pub struct SessionLogResult { /// #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMetadataSnapshotParams { +pub struct SessionScheduleListParams { /// Target session identifier pub session_id: SessionId, } -/// Public-facing projection of workspace metadata for SDK / TUI consumers +/// 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 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, +pub struct SessionScheduleListResult { + /// Active scheduled prompts, ordered by id. + pub entries: Vec, } -/// Point-in-time snapshot of slow-changing session identifier and state fields +/// Identifies the target session. /// ///
/// @@ -21912,42 +27115,9 @@ 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. - #[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 struct SessionScheduleHydrateParams { + /// Target session identifier 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, } /// Identifies the target session. @@ -21960,12 +27130,12 @@ pub struct SessionMetadataSnapshotResult { /// #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMetadataIsProcessingParams { +pub struct SessionScheduleHasSelfPacedParams { /// Target session identifier pub session_id: SessionId, } -/// Indicates whether the local session is currently processing a turn or background continuation. +/// Whether the session currently has an active self-paced schedule. /// ///
/// @@ -21975,12 +27145,12 @@ 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 SessionScheduleHasSelfPacedResult { + /// True when at least one active schedule is self-paced. + pub has_self_paced: bool, } -/// Identifies the target session. +/// Result of registering or re-arming a scheduled prompt. /// ///
/// @@ -21990,12 +27160,16 @@ pub struct SessionMetadataIsProcessingResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMetadataActivityParams { - /// Target session identifier - pub session_id: SessionId, +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, } -/// Current activity flags for the session. +/// Result of registering or re-arming a scheduled prompt. /// ///
/// @@ -22005,40 +27179,16 @@ 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 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, } -/// Token breakdown for the session's current context window, or null if uninitialized. +/// Result of registering or re-arming a scheduled prompt. /// ///
/// @@ -22048,12 +27198,16 @@ 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 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, } -/// Identifies the target session. +/// Result of registering or re-arming a scheduled prompt. /// ///
/// @@ -22063,51 +27217,16 @@ 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. +pub struct SessionScheduleAddSelfPacedResult { + /// The registered or updated schedule entry. #[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. + pub entry: Option, + /// User-facing validation error, when registration failed. #[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 error: Option, } -/// Per-source attribution breakdown for the session's current context window, or null if uninitialized. +/// Result of registering or re-arming a scheduled prompt. /// ///
/// @@ -22117,12 +27236,16 @@ 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 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, } -/// The heaviest individual messages in the session's context window, most-expensive first. +/// Remove a scheduled prompt by id. The result entry is omitted if the id was unknown. /// ///
/// @@ -22132,14 +27255,13 @@ 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 SessionScheduleStopResult { + /// The removed entry, or omitted if no entry matched. + #[serde(skip_serializing_if = "Option::is_none")] + pub entry: Option, } -/// 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. +/// Identifies the target session. /// ///
/// @@ -22149,9 +27271,12 @@ pub struct SessionMetadataGetContextHeaviestMessagesResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMetadataRecordContextChangeResult {} +pub struct SkillProviderListParams { + /// Target session identifier + pub session_id: SessionId, +} -/// 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. +/// 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. /// ///
/// @@ -22161,12 +27286,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 ProviderTokenGetTokenResult { + /// The bearer token value (without the `Bearer ` prefix). + pub token: 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. +/// Acknowledgement that a factory request was accepted. /// ///
/// @@ -22176,14 +27301,7 @@ 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 FactoryAbortResult {} /// Identifies the target session. /// @@ -22195,12 +27313,12 @@ pub struct SessionMetadataRecomputeContextTokensResult { /// #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionSettingsSnapshotParams { +pub struct SessionFsSqliteExistsParams { /// 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. +/// Canvas open result returned by the provider. /// ///
/// @@ -22210,23 +27328,19 @@ 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 CanvasOpenResult { + /// Provider-supplied status text #[serde(skip_serializing_if = "Option::is_none")] - pub start_time_ms: Option, + pub status: Option, + /// Provider-supplied title #[serde(skip_serializing_if = "Option::is_none")] - pub timeout_ms: Option, - pub validation: SessionSettingsValidationSnapshot, + pub title: Option, + /// URL for web-rendered canvases #[serde(skip_serializing_if = "Option::is_none")] - pub version: Option, + pub url: Option, } -/// Batch content-exclusion result. Callers must fail closed when policy evaluation is unavailable. +/// Validation errors from the most recent authentication attempt. /// ///
/// @@ -22234,16 +27348,9 @@ pub struct SessionSettingsSnapshotResult { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -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, -} +pub type AuthValidationErrors = Vec; -/// Identifier of the spawned process, used to correlate streamed output and exit notifications. +/// SHA-256 digest encoded as exactly 64 lowercase hexadecimal characters. /// ///
/// @@ -22251,14 +27358,9 @@ pub struct SessionContentExclusionCheckPathsResult { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionShellExecResult { - /// Unique identifier for tracking streamed output - pub process_id: String, -} +pub type CardDigestValue = String; -/// Indicates whether the signal was delivered; false if the process was unknown or already exited. +/// Bounded extensible wire-feature identifier. Known values are described by `CatalogCapability`; newer callers may send future identifiers so an older runtime can return a typed negotiation refusal instead of failing schema validation. Capability negotiation establishes contract understanding, while each operation's result separately reports runtime availability. /// ///
/// @@ -22266,14 +27368,9 @@ pub struct SessionShellExecResult { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionShellKillResult { - /// Whether the signal was sent successfully - pub killed: bool, -} +pub type CatalogCapabilityId = String; -/// Result of a user-requested shell command. +/// HTTP headers as a map from lowercased header name to a list of values. Multi-valued headers (e.g. Set-Cookie) preserve all values. /// ///
/// @@ -22281,24 +27378,9 @@ pub struct SessionShellKillResult { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[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 type LlmInferenceHeaders = HashMap>; -/// Cancellation result for a user-requested shell command. +/// 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. /// ///
/// @@ -22306,14 +27388,9 @@ pub struct SessionShellExecuteUserRequestedResult { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[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 type McpExecuteSamplingResult = HashMap; -/// Compaction outcome with the number of tokens and messages removed, summary text, and the resulting context window breakdown. +/// A runtime-assigned secret placeholder. The identifier is carried once, inside the placeholder, so it cannot contradict a separate secret-id field. /// ///
/// @@ -22321,24 +27398,9 @@ pub struct SessionShellCancelUserRequestedResult { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[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 type McpPlanSecretReference = String; -/// Number of events that were removed by the truncation. +/// The form values submitted by the user (present when action is 'accept') /// ///
/// @@ -22346,20 +27408,9 @@ pub struct SessionHistoryCompactResult { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -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, -} +pub type UIElicitationResponseContent = HashMap; -/// Identifies the target session. +/// List of all authenticated users /// ///
/// @@ -22367,14 +27418,9 @@ pub struct SessionHistoryTruncateResult { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionHistoryListRewindPointsParams { - /// Target session identifier - pub session_id: SessionId, -} +pub type AccountGetAllUsersResult = Vec; -/// Rewind points and file-change-tracking availability for the session. +/// The number of running background agents (task-registry agents) that were cancelled. /// ///
/// @@ -22382,19 +27428,9 @@ pub struct SessionHistoryListRewindPointsParams { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -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 unavailable_reason: Option, -} +pub type SessionCancelAllBackgroundAgentsResult = i64; -/// Files and aggregate changes for a prospective rewind. +/// Authentication accounts available to the internal session host. /// ///
/// @@ -22402,21 +27438,9 @@ pub struct SessionHistoryListRewindPointsResult { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -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, -} +pub type SessionGitHubAuthGetAllAuthAvailableResult = Vec; -/// Structured outcome of a rewind request. +/// Whether the current authentication was logged out. /// ///
/// @@ -22424,24 +27448,9 @@ pub struct SessionHistoryPreviewRewindResult { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -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, -} +pub type SessionGitHubAuthLogoutResult = bool; -/// Identifies the target session. +/// Whether the requested authentication was logged out. /// ///
/// @@ -22449,14 +27458,9 @@ pub struct SessionHistoryRewindResult { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionHistoryCancelBackgroundCompactionParams { - /// Target session identifier - pub session_id: SessionId, -} +pub type SessionGitHubAuthLogoutUserResult = bool; -/// Indicates whether an in-progress background compaction was cancelled. +/// Validation errors from the most recent authentication attempt. /// ///
/// @@ -22464,14 +27468,9 @@ pub struct SessionHistoryCancelBackgroundCompactionParams { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[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 type SessionGitHubAuthLastAuthErrorsResult = Vec; -/// Identifies the target session. +/// Standard MCP CallToolResult /// ///
/// @@ -22479,59 +27478,89 @@ pub struct SessionHistoryCancelBackgroundCompactionResult { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionHistoryAbortManualCompactionParams { - /// Target session identifier - pub session_id: SessionId, +pub type SessionMcpAppsCallToolResult = HashMap; + +/// Authentication host. HMAC auth always targets the public GitHub host. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum HMACAuthInfoHost { + #[serde(rename = "https://github.com")] + #[default] + HttpsGitHubCom, } -/// Indicates whether an in-progress manual compaction was aborted. -/// -///
-/// -/// **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 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, +/// HMAC-based authentication used by GitHub-internal services. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum HMACAuthInfoType { + #[serde(rename = "hmac")] + #[default] + Hmac, +} + +/// Personal access token (PAT) or server-to-server token sourced from an environment variable. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum EnvAuthInfoType { + #[serde(rename = "env")] + #[default] + Env, +} + +/// SDK-side token authentication; the host configured the token directly via the SDK. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum TokenAuthInfoType { + #[serde(rename = "token")] + #[default] + Token, +} + +/// SDK callback-backed GitHub token authentication. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum TokenProviderAuthInfoType { + #[serde(rename = "token-provider")] + #[default] + TokenProvider, +} + +/// Authentication host (always the public GitHub host). +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CopilotApiTokenAuthInfoHost { + #[serde(rename = "https://github.com")] + #[default] + HttpsGitHubCom, +} + +/// Direct Copilot API authentication via the `GITHUB_COPILOT_API_TOKEN` + `COPILOT_API_URL` environment-variable pair. The token itself is read from the environment by the runtime, not carried in this struct. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CopilotApiTokenAuthInfoType { + #[serde(rename = "copilot-api-token")] + #[default] + CopilotApiToken, +} + +/// 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. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum UserAuthInfoType { + #[serde(rename = "user")] + #[default] + User, } -/// 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 SessionHistorySummarizeForHandoffParams { - /// Target session identifier - pub session_id: SessionId, +/// 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, } -/// Markdown summary of the conversation context (empty when not 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 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, +/// API-key authentication for non-GitHub LLM providers (e.g. when running BYOM-style). +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum ApiKeyAuthInfoType { + #[serde(rename = "api-key")] + #[default] + ApiKey, } -/// What a successful clear removed. A clear that could not be applied rejects instead of reporting a count. +/// Authentication credentials accepted only at native protocol ingress. Runtime outputs use credential-free `AuthIdentity` metadata. /// ///
/// @@ -22539,14 +27568,20 @@ pub struct SessionHistorySummarizeForHandoffResult { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -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, +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum AuthInfo { + Hmac(HMACAuthInfo), + Env(EnvAuthInfo), + Token(TokenAuthInfo), + TokenProvider(TokenProviderAuthInfo), + CopilotApiToken(CopilotApiTokenAuthInfo), + User(UserAuthInfo), + GhCli(GhCliAuthInfo), + ApiKey(ApiKeyAuthInfo), } -/// Identifies the target session. +/// Resolved Anthropic adaptive-thinking capability for a model. /// ///
/// @@ -22554,14 +27589,24 @@ pub struct SessionHistoryClearContextResult { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionQueuePendingItemsParams { - /// Target session identifier - pub session_id: SessionId, +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AdaptiveThinkingSupport { + /// The model does not accept thinking.type='adaptive' + #[serde(rename = "unsupported")] + Unsupported, + /// The model accepts adaptive thinking but also accepts thinking.type='enabled' + #[serde(rename = "optional")] + Optional, + /// The model only accepts adaptive thinking and rejects thinking.type='enabled' with HTTP 400 (e.g. opus-4.7/4.8) + #[serde(rename = "required")] + Required, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// Snapshot of the session's pending queued items and immediate-steering messages. +/// Which tier this directory belongs to /// ///
/// @@ -22569,16 +27614,21 @@ pub struct SessionQueuePendingItemsParams { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[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, +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AgentDiscoveryPathScope { + /// The user's personal agent configuration directory. + #[serde(rename = "user")] + User, + /// A project's repository agent directory. + #[serde(rename = "project")] + Project, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// Identifies the target session. +/// Where the agent definition was loaded from /// ///
/// @@ -22586,14 +27636,33 @@ pub struct SessionQueuePendingItemsResult { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionQueueSnapshotParams { - /// Target session identifier - pub session_id: SessionId, +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AgentInfoSource { + /// Agent loaded from the user's personal agent configuration. + #[serde(rename = "user")] + User, + /// Agent loaded from the current project's repository configuration. + #[serde(rename = "project")] + Project, + /// Agent inherited from a parent project or workspace. + #[serde(rename = "inherited")] + Inherited, + /// Agent provided by a remote runtime or service. + #[serde(rename = "remote")] + Remote, + /// Agent contributed by an installed plugin. + #[serde(rename = "plugin")] + Plugin, + /// Agent built into the Copilot runtime. + #[serde(rename = "builtin")] + Builtin, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// Internal snapshot of native queue state for local session orchestration. +/// Kind of attention required when status === "attention". Meaningful only when status === "attention". /// ///
/// @@ -22601,22 +27670,30 @@ pub struct SessionQueueSnapshotParams { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionQueueSnapshotResult { - /// 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, +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AgentRegistryLiveTargetEntryAttentionKind { + /// Session is blocked on an unrecoverable error + #[serde(rename = "error")] + Error, + /// Session is waiting for a tool-permission decision + #[serde(rename = "permission")] + Permission, + /// Session is waiting for the user to approve or reject a plan + #[serde(rename = "exit_plan")] + ExitPlan, + /// Session is waiting on an elicitation prompt + #[serde(rename = "elicitation")] + Elicitation, + /// Session is waiting for free-form user input + #[serde(rename = "user_input")] + UserInput, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// Result of moving a queued item. +/// Process kind tag for the registry entry /// ///
/// @@ -22624,14 +27701,21 @@ pub struct SessionQueueSnapshotResult { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionQueueMoveItemResult { - /// True when the item changed position; false when it was already at the requested position. - pub changed: bool, +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AgentRegistryLiveTargetEntryKind { + /// Interactive Copilot CLI exposing a UI server (legacy/normal CLI process) + #[serde(rename = "ui-server")] + UiServer, + /// Headless `--server --managed-server` child spawned by a controller + #[serde(rename = "managed-server")] + ManagedServer, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// Result of inserting a queued message. +/// How the most recent turn ended (clean vs aborted). Lets the renderer distinguish done from done_cancelled. /// ///
/// @@ -22639,14 +27723,21 @@ pub struct SessionQueueMoveItemResult { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionQueueInsertAtResult { - /// Fresh stable opaque id assigned to the inserted item. - pub id: String, +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AgentRegistryLiveTargetEntryLastTerminalEvent { + /// Last turn ended cleanly (model returned a final assistant message) + #[serde(rename = "turn_end")] + TurnEnd, + /// Last turn was aborted (e.g. user interrupted) + #[serde(rename = "abort")] + Abort, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// Result of removing a queued item. +/// Coarse lifecycle status of the foreground session /// ///
/// @@ -22654,14 +27745,27 @@ pub struct SessionQueueInsertAtResult { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionQueueRemoveAtResult { - /// True when the addressed item was removed. - pub removed: bool, +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AgentRegistryLiveTargetEntryStatus { + /// Session is actively processing a turn + #[serde(rename = "working")] + Working, + /// Session is idle, waiting for input + #[serde(rename = "waiting")] + Waiting, + /// Last turn completed successfully + #[serde(rename = "done")] + Done, + /// Session needs user attention (see attentionKind for the specific reason) + #[serde(rename = "attention")] + Attention, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// Result of editing a queued message. +/// Categorized reason for log-open failure /// ///
/// @@ -22669,14 +27773,32 @@ pub struct SessionQueueRemoveAtResult { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionQueueUpdateTextResult { - /// True when the stored text changed. - pub updated: bool, +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AgentRegistryLogCaptureOpenErrorReason { + /// Filesystem permission denied opening the log file + #[serde(rename = "permission")] + Permission, + /// No space left on device + #[serde(rename = "disk_full")] + DiskFull, + /// Other / uncategorized open failure + #[serde(rename = "other")] + Other, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Discriminator: child_process.spawn itself failed +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AgentRegistrySpawnErrorKind { + #[serde(rename = "spawn-error")] + #[default] + SpawnError, } -/// Result of duplicating a queued item. +/// Permission posture for the new session. 'yolo' requires the controller-local session to currently be in allow-all mode. /// ///
/// @@ -22684,44 +27806,37 @@ pub struct SessionQueueUpdateTextResult { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionQueueDuplicateAtResult { - /// Fresh stable opaque id assigned to the duplicate. - pub id: String, +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AgentRegistrySpawnPermissionMode { + /// Standard permission posture (prompts for each request) + #[serde(rename = "default")] + Default, + /// Full allow-all (requires the controller-local session to currently be in allow-all mode) + #[serde(rename = "yolo")] + Yolo, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// Result of trying to steer a queued message into a live 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 SessionQueueSendNowResult { - /// True when the item was accepted into the steering lane; false when no main turn was live. - pub steered: bool, +/// Discriminator: spawn succeeded but child never registered +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AgentRegistrySpawnRegistryTimeoutKind { + #[serde(rename = "registry-timeout")] + #[default] + RegistryTimeout, } -/// 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 SessionQueueHasPendingParams { - /// Target session identifier - pub session_id: SessionId, +/// Discriminator: managed-server child spawned successfully +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AgentRegistrySpawnSpawnedKind { + #[serde(rename = "spawned")] + #[default] + Spawned, } -/// Whether the native queue has pending work. +/// Which parameter field was invalid. Omitted when the rejection is not field-specific. /// ///
/// @@ -22729,14 +27844,38 @@ pub struct SessionQueueHasPendingParams { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionQueueHasPendingResult { - /// True when queued or immediate native work is pending. - pub has_pending: bool, +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AgentRegistrySpawnValidationErrorField { + /// The cwd parameter + #[serde(rename = "cwd")] + Cwd, + /// The session name parameter + #[serde(rename = "name")] + Name, + /// The agentName parameter + #[serde(rename = "agentName")] + AgentName, + /// The model parameter + #[serde(rename = "model")] + Model, + /// The permissionMode parameter + #[serde(rename = "permissionMode")] + PermissionMode, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// Whether a deferred-idle drain should run. +/// Discriminator: synchronous pre-validation rejected the request +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AgentRegistrySpawnValidationErrorKind { + #[serde(rename = "validation-error")] + #[default] + ValidationError, +} + +/// Categorized reason for the rejection. Low-cardinality enum so telemetry can aggregate by reason without leaking raw paths or agent/model names. /// ///
/// @@ -22744,14 +27883,33 @@ pub struct SessionQueueHasPendingResult { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionQueueBeginDeferredIdleDrainResult { - /// True when the host should run finishDeferredIdleDrain asynchronously. - pub should_drain: bool, +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AgentRegistrySpawnValidationErrorReason { + /// Provided cwd does not exist on disk + #[serde(rename = "cwd-not-found")] + CwdNotFound, + /// Provided cwd exists but is not a directory + #[serde(rename = "cwd-not-directory")] + CwdNotDirectory, + /// Session name failed validateSessionName + #[serde(rename = "invalid-name")] + InvalidName, + /// Requested agent name was not found in builtin or custom agents + #[serde(rename = "unknown-agent")] + UnknownAgent, + /// Requested model is not available to this session + #[serde(rename = "unknown-model")] + UnknownModel, + /// Caller asked for permissionMode='yolo' but the controller is not currently in allow-all mode + #[serde(rename = "yolo-not-allowed")] + YoloNotAllowed, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// Action selected by the native deferred-idle drain. +/// Outcome of an agentRegistry.spawn call. /// ///
/// @@ -22759,16 +27917,80 @@ pub struct SessionQueueBeginDeferredIdleDrainResult { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -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, +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum AgentRegistrySpawnResult { + Spawned(AgentRegistrySpawnSpawned), + SpawnError(AgentRegistrySpawnError), + RegistryTimeout(AgentRegistrySpawnRegistryTimeout), + ValidationError(AgentRegistrySpawnValidationError), } -/// Identifies the target session. +/// Attachment type discriminator +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AttachmentBlobType { + #[serde(rename = "blob")] + #[default] + Blob, +} + +/// Attachment type discriminator +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AttachmentDirectoryType { + #[serde(rename = "directory")] + #[default] + Directory, +} + +/// Attachment type discriminator +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AttachmentExtensionContextType { + #[serde(rename = "extension_context")] + #[default] + ExtensionContext, +} + +/// Attachment type discriminator +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AttachmentFileType { + #[serde(rename = "file")] + #[default] + File, +} + +/// Attachment type discriminator +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AttachmentGitHubActionsJobType { + #[serde(rename = "github_actions_job")] + #[default] + GitHubActionsJob, +} + +/// Attachment type discriminator +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AttachmentGitHubCommitType { + #[serde(rename = "github_commit")] + #[default] + GitHubCommit, +} + +/// Attachment type discriminator +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AttachmentGitHubFileType { + #[serde(rename = "github_file")] + #[default] + GitHubFile, +} + +/// Attachment type discriminator +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AttachmentGitHubFileDiffType { + #[serde(rename = "github_file_diff")] + #[default] + GitHubFileDiff, +} + +/// Type of GitHub reference /// ///
/// @@ -22776,44 +27998,72 @@ pub struct SessionQueueFinishDeferredIdleDrainResult { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionQueueRemoveMostRecentParams { - /// Target session identifier - pub session_id: SessionId, +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AttachmentGitHubReferenceType { + /// GitHub issue reference. + #[serde(rename = "issue")] + Issue, + /// GitHub pull request reference. + #[serde(rename = "pr")] + Pr, + /// GitHub discussion reference. + #[serde(rename = "discussion")] + Discussion, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Attachment type discriminator +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AttachmentGitHubReleaseType { + #[serde(rename = "github_release")] + #[default] + GitHubRelease, +} + +/// Attachment type discriminator +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AttachmentGitHubRepositoryType { + #[serde(rename = "github_repository")] + #[default] + GitHubRepository, +} + +/// Attachment type discriminator +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AttachmentGitHubSnippetType { + #[serde(rename = "github_snippet")] + #[default] + GitHubSnippet, +} + +/// Attachment type discriminator +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AttachmentGitHubTreeComparisonType { + #[serde(rename = "github_tree_comparison")] + #[default] + GitHubTreeComparison, } -/// Indicates whether a user-facing pending item was removed. -/// -///
-/// -/// **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 SessionQueueRemoveMostRecentResult { - /// True if a user-facing pending item was removed (LIFO across both queues); false when no removable items remained. - pub removed: bool, +/// Attachment type discriminator +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AttachmentGitHubUrlType { + #[serde(rename = "github_url")] + #[default] + GitHubUrl, } -/// 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 SessionQueueClearParams { - /// Target session identifier - pub session_id: SessionId, +/// Attachment type discriminator +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AttachmentSelectionType { + #[serde(rename = "selection")] + #[default] + Selection, } -/// Indicates whether a user-facing pending item was removed. +/// Authentication type /// ///
/// @@ -22821,14 +28071,39 @@ pub struct SessionQueueClearParams { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -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, +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AuthInfoType { + /// Authentication provided by a GitHub App HMAC credential. + #[serde(rename = "hmac")] + Hmac, + /// Authentication resolved from environment-provided credentials. + #[serde(rename = "env")] + Env, + /// Authentication from an interactive user sign-in. + #[serde(rename = "user")] + User, + /// Authentication delegated to the GitHub CLI. + #[serde(rename = "gh-cli")] + GhCli, + /// Authentication from an API key credential. + #[serde(rename = "api-key")] + ApiKey, + /// Authentication from a GitHub token. + #[serde(rename = "token")] + Token, + /// Authentication from an SDK GitHub token callback. + #[serde(rename = "token-provider")] + TokenProvider, + /// Authentication from a Copilot API token. + #[serde(rename = "copilot-api-token")] + CopilotApiToken, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// Identifies the target session. +/// Custom input-format kind. /// ///
/// @@ -22836,14 +28111,18 @@ pub struct SessionQueueConsumeSystemNotificationsResult { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionQueueEnqueueResumePendingParams { - /// Target session identifier - pub session_id: SessionId, +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum BuiltinToolFormatType { + /// The tool input is parsed with the supplied grammar. + #[serde(rename = "grammar")] + Grammar, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// Result of enqueueing the resume-pending wake item. +/// Root JSON Schema type for a built-in tool input. /// ///
/// @@ -22851,14 +28130,18 @@ pub struct SessionQueueEnqueueResumePendingParams { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionQueueEnqueueResumePendingResult { - /// True when a wake item was newly queued. - pub queued: bool, +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum BuiltinToolInputSchemaType { + /// The tool accepts a JSON object. + #[serde(rename = "object")] + Object, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// Identifies the target session. +/// Canonical digest algorithm for a validated MCP card /// ///
/// @@ -22866,110 +28149,66 @@ pub struct SessionQueueEnqueueResumePendingResult { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionQueueProcessParams { - /// Target session identifier - pub session_id: SessionId, +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CardDigestAlgorithm { + /// SHA-256 over RFC 8785 canonical JSON encoded as UTF-8. + #[serde(rename = "sha256-rfc8785")] + Sha256Rfc8785, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// Batch of session events returned by a read, with cursor and continuation 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 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. 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 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, - /// 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 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, +/// AI skills are discovery-only and cannot be installed through this surface +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogAiSkillCandidateInstallability { + #[serde(rename = "not-installable-kind")] + #[default] + NotInstallableKind, } -/// 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 SessionEventLogTailParams { - /// Target session identifier - pub session_id: SessionId, +/// Discriminator: this candidate describes an AI skill +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogAiSkillCandidateKind { + #[serde(rename = "ai-skill")] + #[default] + AiSkill, } -/// 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.** 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 SessionEventLogTailResult { - /// 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). - pub cursor: String, +/// Media type of the underlying AI skill card +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogAiSkillCandidateMediaType { + #[serde(rename = "application/ai-skill")] + #[default] + ApplicationAiSkill, } -/// Opaque handle representing an event-type interest registration. -/// -///
-/// -/// **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 SessionEventLogRegisterInterestResult { - /// 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, +/// Media type advertised for the referenced AI skill card +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogAiSkillCandidateProvenanceMediaType { + #[serde(rename = "application/ai-skill")] + #[default] + ApplicationAiSkill, } -/// 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 SessionEventLogReleaseInterestResult { - /// Whether the operation succeeded - pub success: bool, +/// Discriminator: the card is URL-backed, and carries no embedded data +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogCandidateSourceUrlKind { + #[serde(rename = "url")] + #[default] + Url, } -/// 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 SessionUsageGetMetricsParams { - /// Target session identifier - pub session_id: SessionId, +/// Discriminator: the card is embedded, and carries no URL +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogCandidateSourceEmbeddedKind { + #[serde(rename = "embedded")] + #[default] + Embedded, } -/// Accumulated session usage metrics, including premium request cost, token counts, model breakdown, and code-change totals. +/// Where a candidate's card came from. Exactly one of a URL or embedded data: the union has no variant carrying both, and no variant carrying neither, so the rule holds structurally rather than by validation. /// ///
/// @@ -22977,55 +28216,22 @@ pub struct SessionUsageGetMetricsParams { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionUsageGetMetricsResult { - /// 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, +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum CatalogCandidateSource { + Url(CatalogCandidateSourceUrl), + Embedded(CatalogCandidateSourceEmbedded), } -/// GitHub URL for the session and a flag indicating whether remote steering 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 SessionRemoteEnableResult { - /// Whether remote steering is enabled - pub remote_steerable: bool, - /// GitHub frontend URL for this session - #[serde(skip_serializing_if = "Option::is_none")] - pub url: Option, +/// Discriminator: the caller is not authenticated +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogAuthenticationRequiredErrorKind { + #[serde(rename = "authentication-required")] + #[default] + AuthenticationRequired, } -/// Identifies the target session. +/// Why the catalog authority did not accept the caller's identity /// ///
/// @@ -23033,14 +28239,24 @@ pub struct SessionRemoteEnableResult { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionRemoteDisableParams { - /// Target session identifier - pub session_id: SessionId, +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogAuthenticationRequiredReason { + /// No credential was presented, so there is nothing to refresh and the caller must sign in. + #[serde(rename = "no-credential")] + NoCredential, + /// A credential was presented and its lifetime has elapsed. A silent refresh is worth attempting before prompting anyone. + #[serde(rename = "credential-expired")] + CredentialExpired, + /// A credential was presented and the authority refused it, for example because it was revoked, malformed, or issued for another audience. Refreshing the same rejected credential is not useful; the caller must sign in again. + #[serde(rename = "credential-rejected")] + CredentialRejected, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// 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. +/// Whether an MCP server candidate can be planned for installation /// ///
/// @@ -23048,11 +28264,29 @@ pub struct SessionRemoteDisableParams { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionRemoteNotifySteerableChangedResult {} +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogMcpServerInstallability { + /// An install plan can be computed for this MCP server candidate. + #[serde(rename = "installable")] + Installable, + /// Policy forbids installing this MCP server candidate. + #[serde(rename = "not-installable-policy")] + NotInstallablePolicy, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} -/// Identifies the target session. +/// Discriminator: this candidate describes an MCP server +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogMcpServerCandidateKind { + #[serde(rename = "mcp-server")] + #[default] + McpServer, +} + +/// JSON MCP card media type accepted for install planning /// ///
/// @@ -23060,14 +28294,21 @@ pub struct SessionRemoteNotifySteerableChangedResult {} /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionVisibilityGetParams { - /// Target session identifier - pub session_id: SessionId, +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum McpServerCardMediaType { + /// The current MCP server card media type. + #[serde(rename = "application/mcp-server-card+json")] + ApplicationMcpServerCardJson, + /// The legacy MCP server card media type, accepted for compatibility. + #[serde(rename = "application/mcp-server+json")] + ApplicationMcpServerJson, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// Current sharing status and shareable GitHub URL for a session. +/// One inert catalog result, represented as an MCP server or discovery-only AI skill variant so kind, media type, provenance, and installability cannot contradict each other. /// ///
/// @@ -23075,20 +28316,14 @@ pub struct SessionVisibilityGetParams { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionVisibilityGetResult { - /// 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, +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum CatalogCandidate { + McpServer(CatalogMcpServerCandidate), + AiSkill(CatalogAiSkillCandidate), } -/// Effective sharing status and shareable GitHub URL after updating session visibility. +/// What kind of resource a catalog candidate describes /// ///
/// @@ -23096,20 +28331,21 @@ pub struct SessionVisibilityGetResult { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionVisibilitySetResult { - /// 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, +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogCandidateKind { + /// An MCP server, which can be planned for installation. + #[serde(rename = "mcp-server")] + McpServer, + /// An AI skill, which is discoverable but not installable through this surface. + #[serde(rename = "ai-skill")] + AiSkill, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// Identifies the target session. +/// A wire feature a caller can require of the catalog surface, negotiated per request. A grant means the runtime understands the feature's contract, not that the deployment has enabled the operation; typed unavailable results report availability separately. /// ///
/// @@ -23117,14 +28353,38 @@ pub struct SessionVisibilitySetResult { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionScheduleListParams { - /// Target session identifier - pub session_id: SessionId, +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogCapability { + /// Understands the current `application/mcp-server-card+json` media type. + #[serde(rename = "mcp-server-card")] + McpServerCard, + /// Understands the legacy `application/mcp-server+json` media type. + #[serde(rename = "legacy-mcp-server-card")] + LegacyMcpServerCard, + /// Understands `application/ai-skill` candidates as discovery-only and typed non-installable. + #[serde(rename = "ai-skill-discovery")] + AiSkillDiscovery, + /// Understands side-effect-free MCP install-plan requests, results, and plan handles; `planning-unavailable` separately reports that planning is not enabled. + #[serde(rename = "mcp-install-planning")] + McpInstallPlanning, + /// Understands plans that enumerate every eligible transport rather than a single preferred one. + #[serde(rename = "multiple-transport-choice")] + MultipleTransportChoice, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// Snapshot of the currently active recurring prompts for this session. +/// Discriminator: the upstream response broke the contract +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogContractViolationErrorKind { + #[serde(rename = "contract-violation")] + #[default] + ContractViolation, +} + +/// Which wire-contract rule an upstream response broke /// ///
/// @@ -23132,14 +28392,27 @@ pub struct SessionScheduleListParams { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionScheduleListResult { - /// Active scheduled prompts, ordered by id. - pub entries: Vec, +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogContractViolationReason { + /// A result carried both a URL and embedded data, when exactly one is permitted. + #[serde(rename = "both-url-and-data")] + BothUrlAndData, + /// A result carried neither a URL nor embedded data, when exactly one is required. + #[serde(rename = "neither-url-nor-data")] + NeitherUrlNorData, + /// Two results claimed the same normalised identity. + #[serde(rename = "duplicate-identity")] + DuplicateIdentity, + /// A result declared no media type, or one this contract does not model. + #[serde(rename = "unknown-media-type")] + UnknownMediaType, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// Identifies the target session. +/// Which kind of opaque handle was presented /// ///
/// @@ -23147,14 +28420,29 @@ pub struct SessionScheduleListResult { /// 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, +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogHandleType { + /// A search candidate handle. + #[serde(rename = "candidate")] + Candidate, + /// An install plan handle. + #[serde(rename = "plan")] + Plan, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// Identifies the target session. +/// Discriminator: a handle was rejected +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogHandleRejectedErrorKind { + #[serde(rename = "handle-rejected")] + #[default] + HandleRejected, +} + +/// Why a presented handle was rejected /// ///
/// @@ -23162,14 +28450,27 @@ pub struct SessionScheduleHydrateParams { /// 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, +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogHandleRejectionReason { + /// The handle is unparseable, unknown, or was issued for a different operation. + #[serde(rename = "invalid")] + Invalid, + /// The handle's time to live has elapsed. + #[serde(rename = "stale")] + Stale, + /// The handle has already been used, and handles are single-use. + #[serde(rename = "replayed")] + Replayed, + /// The handle was issued by a different runtime instance. + #[serde(rename = "foreign")] + Foreign, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// Whether the session currently has an active self-paced schedule. +/// Which request field was rejected before any work was done /// ///
/// @@ -23177,14 +28478,52 @@ pub struct SessionScheduleHasSelfPacedParams { /// 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, +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogInvalidRequestField { + /// The search query was empty or longer than permitted. + #[serde(rename = "query")] + Query, + /// The requested result count fell outside its permitted range. + #[serde(rename = "limit")] + Limit, + /// The requested candidate kinds were empty or contained a duplicate. + #[serde(rename = "kinds")] + Kinds, + /// The negotiation block was missing or malformed. + #[serde(rename = "contract")] + Contract, + /// The plan source was missing or malformed. + #[serde(rename = "source")] + Source, + /// The supplied card was missing its media type, URL, or data. + #[serde(rename = "card")] + Card, + /// The requested configuration scope is not one this runtime writes. + #[serde(rename = "scope")] + Scope, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// Result of registering or re-arming a scheduled prompt. +/// Discriminator: the request itself was invalid +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogInvalidRequestErrorKind { + #[serde(rename = "invalid-request")] + #[default] + InvalidRequest, +} + +/// Discriminator: the card was malformed +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogMalformedCardErrorKind { + #[serde(rename = "malformed-card")] + #[default] + MalformedCard, +} + +/// Media type a catalog card is interpreted as /// ///
/// @@ -23192,18 +28531,24 @@ pub struct SessionScheduleHasSelfPacedResult { /// 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, +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogMediaType { + /// The current MCP server card media type. + #[serde(rename = "application/mcp-server-card+json")] + ApplicationMcpServerCardJson, + /// The legacy MCP server card media type, accepted for compatibility. + #[serde(rename = "application/mcp-server+json")] + ApplicationMcpServerJson, + /// An AI skill card. Representable and searchable, but typed non-installable. + #[serde(rename = "application/ai-skill")] + ApplicationAiSkill, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// Result of registering or re-arming a scheduled prompt. +/// How a card failed validation /// ///
/// @@ -23211,18 +28556,38 @@ pub struct SessionScheduleAddResult { /// 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, +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogMalformedCardReason { + /// The document is not well-formed JSON. + #[serde(rename = "invalid-json")] + InvalidJson, + /// The document does not satisfy its media type's schema. + #[serde(rename = "schema-violation")] + SchemaViolation, + /// The declared media type is not one this runtime understands. + #[serde(rename = "unsupported-media-type")] + UnsupportedMediaType, + /// A field the media type requires is absent. + #[serde(rename = "missing-required-field")] + MissingRequiredField, + /// The document exceeded the permitted size. + #[serde(rename = "size-limit-exceeded")] + SizeLimitExceeded, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// Result of registering or re-arming a scheduled prompt. +/// Discriminator: capability or protocol-version negotiation failed +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogNegotiationRefusedErrorKind { + #[serde(rename = "negotiation-refused")] + #[default] + NegotiationRefused, +} + +/// Why capability and protocol-version negotiation refused a caller /// ///
/// @@ -23230,18 +28595,29 @@ pub struct SessionScheduleAddCronResult { /// 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, +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogNegotiationRefusedReason { + /// The caller's protocol version is below the lowest this runtime serves. + #[serde(rename = "unsupported-protocol-version")] + UnsupportedProtocolVersion, + /// The caller requires at least one capability this runtime cannot honour. + #[serde(rename = "unsupported-capability")] + UnsupportedCapability, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// Result of registering or re-arming a scheduled prompt. +/// Discriminator: the network operation failed +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogNetworkFailureErrorKind { + #[serde(rename = "network-failure")] + #[default] + NetworkFailure, +} + +/// Categorised network failure, low cardinality so it can be aggregated without carrying a URL /// ///
/// @@ -23249,18 +28625,56 @@ pub struct SessionScheduleAddAtResult { /// 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, +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogNetworkFailureReason { + /// No network is available, so nothing was attempted. + #[serde(rename = "offline")] + Offline, + /// The authority's name could not be resolved. + #[serde(rename = "dns")] + Dns, + /// The request exceeded its time budget. + #[serde(rename = "timeout")] + Timeout, + /// The TLS handshake or certificate validation failed. + #[serde(rename = "tls")] + Tls, + /// The connection was refused or reset. + #[serde(rename = "connection-refused")] + ConnectionRefused, + /// The configured proxy returned 407 and requires authentication. + #[serde(rename = "proxy-authentication-required")] + ProxyAuthenticationRequired, + /// The authority rate-limited requests and supplied or implied a bounded cooldown. + #[serde(rename = "rate-limited")] + RateLimited, + /// The authority returned a transient 5xx response. + #[serde(rename = "service-unavailable")] + ServiceUnavailable, + /// The authority returned another status the runtime treats as a failure. + #[serde(rename = "http-status")] + HttpStatus, + /// The response exceeded the permitted size. + #[serde(rename = "response-too-large")] + ResponseTooLarge, + /// A redirect was refused by the runtime's redirect policy. + #[serde(rename = "redirect-rejected")] + RedirectRejected, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// Result of registering or re-arming a scheduled prompt. +/// Discriminator: the candidate cannot be installed +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogNotInstallableErrorKind { + #[serde(rename = "not-installable")] + #[default] + NotInstallable, +} + +/// Why a discoverable candidate cannot be installed /// ///
/// @@ -23268,18 +28682,32 @@ pub struct SessionScheduleAddSelfPacedResult { /// 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, +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogNotInstallableReason { + /// This kind of resource is not installable through this surface. + #[serde(rename = "kind-not-installable")] + KindNotInstallable, + /// AI skills are discoverable but have no typed importer in this phase. + #[serde(rename = "ai-skill-not-installable")] + AiSkillNotInstallable, + /// Policy forbids installing this candidate. + #[serde(rename = "policy-forbids")] + PolicyForbids, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// Remove a scheduled prompt by id. The result entry is omitted if the id was unknown. +/// Discriminator: policy refused the operation +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogPolicyRejectedErrorKind { + #[serde(rename = "policy-rejected")] + #[default] + PolicyRejected, +} + +/// Which authority produced a policy decision /// ///
/// @@ -23287,15 +28715,51 @@ pub struct SessionScheduleRearmSelfPacedResult { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionScheduleStopResult { - /// The removed entry, or omitted if no entry matched. - #[serde(skip_serializing_if = "Option::is_none")] - pub entry: Option, +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum McpPlanPolicySource { + /// No policy applied, so the server is permitted by default. + #[serde(rename = "none")] + None, + /// An enterprise allowlist evaluated the server. + #[serde(rename = "enterprise-allowlist")] + EnterpriseAllowlist, + /// The registry the card came from evaluated the server. + #[serde(rename = "registry-policy")] + RegistryPolicy, + /// Local trust settings evaluated the server. + #[serde(rename = "local-trust")] + LocalTrust, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Discriminator: the search completed +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogSearchSucceededKind { + #[serde(rename = "succeeded")] + #[default] + Succeeded, +} + +/// Discriminator: an unsupported candidate kind was requested +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogUnsupportedKindErrorKind { + #[serde(rename = "unsupported-kind")] + #[default] + UnsupportedKind, +} + +/// Discriminator: retrieval was refused as unsafe +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogUnsafeRetrievalErrorKind { + #[serde(rename = "unsafe-retrieval")] + #[default] + UnsafeRetrieval, } -/// 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. +/// Which hardened-fetch control refused a retrieval /// ///
/// @@ -23303,14 +28767,41 @@ pub struct SessionScheduleStopResult { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ProviderTokenGetTokenResult { - /// The bearer token value (without the `Bearer ` prefix). - pub token: String, +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogUnsafeRetrievalReason { + /// The URL used a scheme the runtime refuses to fetch. + #[serde(rename = "blocked-scheme")] + BlockedScheme, + /// The URL embedded credentials. + #[serde(rename = "credentials-in-url")] + CredentialsInUrl, + /// The URL resolved to a loopback, private, link-local, or cloud metadata address. + #[serde(rename = "blocked-address")] + BlockedAddress, + /// A redirect target resolved to a blocked address. + #[serde(rename = "redirect-to-blocked-address")] + RedirectToBlockedAddress, + /// The configured proxy policy refused the request. + #[serde(rename = "proxy-rejected")] + ProxyRejected, + /// The authority is not permitted for card retrieval. + #[serde(rename = "host-not-permitted")] + HostNotPermitted, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// Acknowledgement that a factory request was accepted. +/// Discriminator: the operation is not available +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogUnavailableErrorKind { + #[serde(rename = "unavailable")] + #[default] + Unavailable, +} + +/// Why a catalog operation is not available on this runtime /// ///
/// @@ -23318,11 +28809,27 @@ pub struct ProviderTokenGetTokenResult { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct FactoryAbortResult {} +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogUnavailableReason { + /// Bounded search is not wired up on this runtime build. + #[serde(rename = "search-unavailable")] + SearchUnavailable, + /// Install planning is not wired up on this runtime build. + #[serde(rename = "planning-unavailable")] + PlanningUnavailable, + /// No catalog authority is configured for this runtime. + #[serde(rename = "authority-not-configured")] + AuthorityNotConfigured, + /// The surface is disabled by policy on this runtime. + #[serde(rename = "disabled-by-policy")] + DisabledByPolicy, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} -/// Identifies the target session. +/// Outcome of a catalog.search call: either bounded inert candidates, or one typed refusal. Never a partial success. /// ///
/// @@ -23330,14 +28837,31 @@ pub struct FactoryAbortResult {} /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionFsSqliteExistsParams { - /// Target session identifier - pub session_id: SessionId, +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum CatalogSearchResult { + Succeeded(CatalogSearchSucceeded), + NegotiationRefused(CatalogNegotiationRefusedError), + UnsupportedKind(CatalogUnsupportedKindError), + InvalidRequest(CatalogInvalidRequestError), + AuthenticationRequired(CatalogAuthenticationRequiredError), + PolicyRejected(CatalogPolicyRejectedError), + NetworkFailure(CatalogNetworkFailureError), + UnsafeRetrieval(CatalogUnsafeRetrievalError), + MalformedCard(CatalogMalformedCardError), + ContractViolation(CatalogContractViolationError), + Unavailable(CatalogUnavailableError), } -/// Canvas open result returned by the provider. +/// Discriminator: no usable transport is available +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogUnavailableTransportErrorKind { + #[serde(rename = "unavailable-transport")] + #[default] + UnavailableTransport, +} + +/// Why no usable transport could be offered /// ///
/// @@ -23345,21 +28869,24 @@ pub struct SessionFsSqliteExistsParams { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct CanvasOpenResult { - /// Provider-supplied status text - #[serde(skip_serializing_if = "Option::is_none")] - pub status: Option, - /// Provider-supplied 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, +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CatalogUnavailableTransportReason { + /// The card advertises no transport this runtime can use. + #[serde(rename = "no-eligible-transport")] + NoEligibleTransport, + /// Every advertised transport is of a kind this runtime does not implement. + #[serde(rename = "transport-not-supported")] + TransportNotSupported, + /// Eligible remotes could not be enumerated, so no explicit choice can be offered. + #[serde(rename = "remote-enumeration-unavailable")] + RemoteEnumerationUnavailable, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// HTTP headers as a map from lowercased header name to a list of values. Multi-valued headers (e.g. Set-Cookie) preserve all values. +/// Optional completion hint for the input (e.g. 'directory' for filesystem path completion) /// ///
/// @@ -23367,9 +28894,18 @@ pub struct CanvasOpenResult { /// and may change or be removed in future SDK or CLI releases. /// ///
-pub type LlmInferenceHeaders = HashMap>; +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SlashCommandInputCompletion { + /// Input should complete filesystem directories. + #[serde(rename = "directory")] + Directory, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} -/// 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. +/// Coarse command category for grouping and behavior: runtime built-in, skill-backed command, or SDK/client-owned command /// ///
/// @@ -23377,9 +28913,24 @@ pub type LlmInferenceHeaders = HashMap>; /// and may change or be removed in future SDK or CLI releases. /// ///
-pub type McpExecuteSamplingResult = HashMap; +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SlashCommandKind { + /// Command implemented by the runtime. + #[serde(rename = "builtin")] + Builtin, + /// Command backed by a skill. + #[serde(rename = "skill")] + Skill, + /// Command registered by an SDK client or extension. + #[serde(rename = "client")] + Client, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} -/// The form values submitted by the user (present when action is 'accept') +/// Whether a pending slash-command invocation effect was applied or cancelled by the host. /// ///
/// @@ -23387,9 +28938,20 @@ pub type McpExecuteSamplingResult = HashMap; /// and may change or be removed in future SDK or CLI releases. /// ///
-pub type UIElicitationResponseContent = HashMap; +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CommandsInvocationEffectOutcome { + /// The host applied the pending invocation effect. + #[serde(rename = "applied")] + Applied, + /// The host cancelled the pending invocation effect, so any provisional state must be reverted. + #[serde(rename = "cancelled")] + Cancelled, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} -/// List of all authenticated users /// ///
/// @@ -23397,9 +28959,17 @@ pub type UIElicitationResponseContent = HashMap; /// and may change or be removed in future SDK or CLI releases. /// ///
-pub type AccountGetAllUsersResult = Vec; +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CommandsInvocationOrigin { + #[serde(rename = "settings")] + Settings, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} -/// The number of running background agents (task-registry agents) that were cancelled. +/// Neutral SDK discriminator for the connected remote session kind. /// ///
/// @@ -23407,9 +28977,21 @@ pub type AccountGetAllUsersResult = Vec; /// and may change or be removed in future SDK or CLI releases. /// ///
-pub type SessionCancelAllBackgroundAgentsResult = i64; +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum ConnectedRemoteSessionMetadataKind { + /// Remote CLI session. + #[serde(rename = "remote-session")] + RemoteSession, + /// GitHub Copilot coding agent session. + #[serde(rename = "coding-agent")] + CodingAgent, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} -/// Standard MCP CallToolResult +/// Controls how MCP tool result content is filtered: none leaves content unchanged, markdown sanitizes HTML while preserving Markdown-friendly output, and hidden_characters removes characters that can hide directives. /// ///
/// @@ -23417,9 +28999,24 @@ pub type SessionCancelAllBackgroundAgentsResult = i64; /// and may change or be removed in future SDK or CLI releases. /// ///
-pub type SessionMcpAppsCallToolResult = HashMap; +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum ContentFilterMode { + /// Leave MCP tool result content unchanged. + #[serde(rename = "none")] + None, + /// Sanitize HTML while preserving Markdown-friendly output. + #[serde(rename = "markdown")] + Markdown, + /// Remove characters that can hide directives. + #[serde(rename = "hidden_characters")] + HiddenCharacters, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} -/// Resolved Anthropic adaptive-thinking capability for a model. +/// Source category for a collected debug bundle entry. /// ///
/// @@ -23428,23 +29025,42 @@ pub type SessionMcpAppsCallToolResult = HashMap; /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum AdaptiveThinkingSupport { - /// The model does not accept thinking.type='adaptive' - #[serde(rename = "unsupported")] - Unsupported, - /// The model accepts adaptive thinking but also accepts thinking.type='enabled' - #[serde(rename = "optional")] - Optional, - /// The model only accepts adaptive thinking and rejects thinking.type='enabled' with HTTP 400 (e.g. opus-4.7/4.8) - #[serde(rename = "required")] - Required, +pub enum DebugCollectLogsSource { + /// Session event log. + #[serde(rename = "events")] + Events, + /// Process log for the session. + #[serde(rename = "process-log")] + ProcessLog, + /// Interactive shell log for the session. + #[serde(rename = "shell-log")] + ShellLog, + /// Caller-provided diagnostic entry. + #[serde(rename = "additional")] + Additional, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Which tier this directory belongs to +/// Destination variant discriminator. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum DebugCollectLogsDestinationArchiveKind { + #[serde(rename = "archive")] + #[default] + Archive, +} + +/// Destination variant discriminator. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum DebugCollectLogsDestinationDirectoryKind { + #[serde(rename = "directory")] + #[default] + Directory, +} + +/// Destination for the redacted debug bundle. /// ///
/// @@ -23452,21 +29068,14 @@ pub enum AdaptiveThinkingSupport { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum AgentDiscoveryPathScope { - /// The user's personal agent configuration directory. - #[serde(rename = "user")] - User, - /// A project's repository agent directory. - #[serde(rename = "project")] - Project, - /// Unknown variant for forward compatibility. - #[default] - #[serde(other)] - Unknown, +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum DebugCollectLogsDestination { + Archive(DebugCollectLogsDestinationArchive), + Directory(DebugCollectLogsDestinationDirectory), } -/// Where the agent definition was loaded from +/// Kind of caller-provided debug log entry. /// ///
/// @@ -23475,32 +29084,20 @@ pub enum AgentDiscoveryPathScope { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum AgentInfoSource { - /// Agent loaded from the user's personal agent configuration. - #[serde(rename = "user")] - User, - /// Agent loaded from the current project's repository configuration. - #[serde(rename = "project")] - Project, - /// Agent inherited from a parent project or workspace. - #[serde(rename = "inherited")] - Inherited, - /// Agent provided by a remote runtime or service. - #[serde(rename = "remote")] - Remote, - /// Agent contributed by an installed plugin. - #[serde(rename = "plugin")] - Plugin, - /// Agent built into the Copilot runtime. - #[serde(rename = "builtin")] - Builtin, +pub enum DebugCollectLogsEntryKind { + /// Include a single server-local file. + #[serde(rename = "file")] + File, + /// Include files from a server-local directory recursively. + #[serde(rename = "directory")] + Directory, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Kind of attention required when status === "attention". Meaningful only when status === "attention". +/// How a collected debug entry should be redacted before being staged. /// ///
/// @@ -23509,29 +29106,20 @@ pub enum AgentInfoSource { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum AgentRegistryLiveTargetEntryAttentionKind { - /// Session is blocked on an unrecoverable error - #[serde(rename = "error")] - Error, - /// Session is waiting for a tool-permission decision - #[serde(rename = "permission")] - Permission, - /// Session is waiting for the user to approve or reject a plan - #[serde(rename = "exit_plan")] - ExitPlan, - /// Session is waiting on an elicitation prompt - #[serde(rename = "elicitation")] - Elicitation, - /// Session is waiting for free-form user input - #[serde(rename = "user_input")] - UserInput, +pub enum DebugCollectLogsRedaction { + /// Redact the file as plain UTF-8 log text. + #[serde(rename = "plain-text")] + PlainText, + /// Redact each non-empty line as a session event JSON object, falling back to plain-text redaction for malformed lines. + #[serde(rename = "events-jsonl")] + EventsJsonl, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Process kind tag for the registry entry +/// Destination kind that was written. /// ///
/// @@ -23540,20 +29128,20 @@ pub enum AgentRegistryLiveTargetEntryAttentionKind { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum AgentRegistryLiveTargetEntryKind { - /// Interactive Copilot CLI exposing a UI server (legacy/normal CLI process) - #[serde(rename = "ui-server")] - UiServer, - /// Headless `--server --managed-server` child spawned by a controller - #[serde(rename = "managed-server")] - ManagedServer, +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, } -/// How the most recent turn ended (clean vs aborted). Lets the renderer distinguish done from done_cancelled. +/// Persisted extension discovery source /// ///
/// @@ -23562,20 +29150,20 @@ pub enum AgentRegistryLiveTargetEntryKind { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum AgentRegistryLiveTargetEntryLastTerminalEvent { - /// Last turn ended cleanly (model returned a final assistant message) - #[serde(rename = "turn_end")] - TurnEnd, - /// Last turn was aborted (e.g. user interrupted) - #[serde(rename = "abort")] - Abort, +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, } -/// Coarse lifecycle status of the foreground session +/// Effective extension loading and agent-management mode /// ///
/// @@ -23584,26 +29172,23 @@ pub enum AgentRegistryLiveTargetEntryLastTerminalEvent { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum AgentRegistryLiveTargetEntryStatus { - /// Session is actively processing a turn - #[serde(rename = "working")] - Working, - /// Session is idle, waiting for input - #[serde(rename = "waiting")] - Waiting, - /// Last turn completed successfully - #[serde(rename = "done")] - Done, - /// Session needs user attention (see attentionKind for the specific reason) - #[serde(rename = "attention")] - Attention, +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)] Unknown, } -/// Categorized reason for log-open failure +/// Hook event name. Discovery emits the file-configurable subset; SDK callbacks additionally support callback-only events. /// ///
/// @@ -23612,31 +29197,65 @@ pub enum AgentRegistryLiveTargetEntryStatus { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum AgentRegistryLogCaptureOpenErrorReason { - /// Filesystem permission denied opening the log file - #[serde(rename = "permission")] - Permission, - /// No space left on device - #[serde(rename = "disk_full")] - DiskFull, - /// Other / uncategorized open failure - #[serde(rename = "other")] - Other, +pub enum HookType { + /// Runs before a tool is invoked. + #[serde(rename = "preToolUse")] + PreToolUse, + /// Runs before an MCP tool is invoked. + #[serde(rename = "preMcpToolCall")] + PreMcpToolCall, + /// Runs after a tool completes successfully. + #[serde(rename = "postToolUse")] + PostToolUse, + /// Runs after a tool fails. + #[serde(rename = "postToolUseFailure")] + PostToolUseFailure, + /// Runs after the user submits a prompt. + #[serde(rename = "userPromptSubmitted")] + UserPromptSubmitted, + /// Runs after the runtime transforms the submitted prompt for the model, before it is added to session history. + #[serde(rename = "userPromptTransformed")] + UserPromptTransformed, + /// Runs when a session starts. + #[serde(rename = "sessionStart")] + SessionStart, + /// Runs when a session ends. + #[serde(rename = "sessionEnd")] + SessionEnd, + /// Runs after an agent result is produced. + #[serde(rename = "postResult")] + PostResult, + /// Runs before a pull request description is generated. + #[serde(rename = "prePRDescription")] + PrePRDescription, + /// Runs when the agent encounters an error. + #[serde(rename = "errorOccurred")] + ErrorOccurred, + /// Runs when the agent stops. + #[serde(rename = "agentStop")] + AgentStop, + /// Runs when a subagent starts. + #[serde(rename = "subagentStart")] + SubagentStart, + /// Runs when a subagent stops. + #[serde(rename = "subagentStop")] + SubagentStop, + /// Runs before conversation context is compacted. + #[serde(rename = "preCompact")] + PreCompact, + /// Runs when the agent requests permission. + #[serde(rename = "permissionRequest")] + PermissionRequest, + /// Runs when the agent emits a notification. + #[serde(rename = "notification")] + Notification, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Discriminator: child_process.spawn itself failed -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum AgentRegistrySpawnErrorKind { - #[serde(rename = "spawn-error")] - #[default] - SpawnError, -} - -/// Permission posture for the new session. 'yolo' requires the controller-local session to currently be in allow-all mode. +/// Configuration tier that contributed a discovered hook action. /// ///
/// @@ -23645,36 +29264,26 @@ pub enum AgentRegistrySpawnErrorKind { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum AgentRegistrySpawnPermissionMode { - /// Standard permission posture (prompts for each request) - #[serde(rename = "default")] - Default, - /// Full allow-all (requires the controller-local session to currently be in allow-all mode) - #[serde(rename = "yolo")] - Yolo, +pub enum HookOrigin { + /// Hook loaded from user settings or the user's hook directory. + #[serde(rename = "user")] + User, + /// Hook loaded from repository settings or the repository hook directory. + #[serde(rename = "repository")] + Repository, + /// Hook provided by an enabled installed or explicit plugin. Projectless rows omit projectPath and do not expand a project directory. + #[serde(rename = "plugin")] + Plugin, + /// Hook enforced by centrally managed policy. + #[serde(rename = "policy")] + Policy, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Discriminator: spawn succeeded but child never registered -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum AgentRegistrySpawnRegistryTimeoutKind { - #[serde(rename = "registry-timeout")] - #[default] - RegistryTimeout, -} - -/// Discriminator: managed-server child spawned successfully -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum AgentRegistrySpawnSpawnedKind { - #[serde(rename = "spawned")] - #[default] - Spawned, -} - -/// Which parameter field was invalid. Omitted when the rejection is not field-specific. +/// Server transport type: stdio, http, sse (deprecated), or memory /// ///
/// @@ -23683,37 +29292,48 @@ pub enum AgentRegistrySpawnSpawnedKind { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum AgentRegistrySpawnValidationErrorField { - /// The cwd parameter - #[serde(rename = "cwd")] - Cwd, - /// The session name parameter - #[serde(rename = "name")] - Name, - /// The agentName parameter - #[serde(rename = "agentName")] - AgentName, - /// The model parameter - #[serde(rename = "model")] - Model, - /// The permissionMode parameter - #[serde(rename = "permissionMode")] - PermissionMode, +pub enum DiscoveredMcpServerType { + /// Server communicates over stdio with a local child process. + #[serde(rename = "stdio")] + Stdio, + /// Server communicates over streamable HTTP. + #[serde(rename = "http")] + Http, + /// Server communicates over Server-Sent Events (deprecated). + #[serde(rename = "sse")] + Sse, + /// Server is backed by an in-memory runtime implementation. + #[serde(rename = "memory")] + Memory, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Discriminator: synchronous pre-validation rejected the request +/// 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.** 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 AgentRegistrySpawnValidationErrorKind { - #[serde(rename = "validation-error")] +pub enum EventsAgentScope { + /// Return main-agent events and typed subagent lifecycle events. + #[serde(rename = "primary")] + Primary, + /// Return events from all agents. + #[serde(rename = "all")] + All, + /// Unknown variant for forward compatibility. #[default] - ValidationError, + #[serde(other)] + Unknown, } -/// Categorized reason for the rejection. Low-cardinality enum so telemetry can aggregate by reason without leaking raw paths or agent/model names. +/// 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. /// ///
/// @@ -23722,32 +29342,20 @@ pub enum AgentRegistrySpawnValidationErrorKind { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum AgentRegistrySpawnValidationErrorReason { - /// Provided cwd does not exist on disk - #[serde(rename = "cwd-not-found")] - CwdNotFound, - /// Provided cwd exists but is not a directory - #[serde(rename = "cwd-not-directory")] - CwdNotDirectory, - /// Session name failed validateSessionName - #[serde(rename = "invalid-name")] - InvalidName, - /// Requested agent name was not found in builtin or custom agents - #[serde(rename = "unknown-agent")] - UnknownAgent, - /// Requested model is not available to this session - #[serde(rename = "unknown-model")] - UnknownModel, - /// Caller asked for permissionMode='yolo' but the controller is not currently in allow-all mode - #[serde(rename = "yolo-not-allowed")] - YoloNotAllowed, +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, } -/// Outcome of an agentRegistry.spawn call. +/// 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. /// ///
/// @@ -23755,16 +29363,21 @@ pub enum AgentRegistrySpawnValidationErrorReason { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum AgentRegistrySpawnResult { - Spawned(AgentRegistrySpawnSpawned), - SpawnError(AgentRegistrySpawnError), - RegistryTimeout(AgentRegistrySpawnRegistryTimeout), - ValidationError(AgentRegistrySpawnValidationError), +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum EventsCursorStatus { + /// The cursor was applied successfully. + #[serde(rename = "ok")] + Ok, + /// The cursor referred to history that is no longer available. + #[serde(rename = "expired")] + Expired, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// Current or requested allow-all mode. +/// Discovery source: project (.github/extensions/), user (~/.copilot/extensions/), plugin (installed plugin), or session (session-state//extensions/) /// ///
/// @@ -23773,31 +29386,26 @@ pub enum AgentRegistrySpawnResult { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum PermissionsAllowAllMode { - /// Permission requests follow the normal approval flow. - #[serde(rename = "off")] - Off, - /// Tool, path, and URL permission requests are automatically approved. - #[serde(rename = "on")] - On, - /// 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. - #[serde(rename = "auto")] - Auto, +pub enum ExtensionSource { + /// Extension discovered from the current project's .github/extensions directory. + #[serde(rename = "project")] + Project, + /// Extension discovered from the user's ~/.copilot/extensions directory. + #[serde(rename = "user")] + User, + /// Extension contributed by an installed plugin. + #[serde(rename = "plugin")] + Plugin, + /// Extension discovered from the current session's state directory (loaded only for this session). + #[serde(rename = "session")] + Session, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// API-key authentication for non-GitHub LLM providers (e.g. when running BYOM-style). -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum ApiKeyAuthInfoType { - #[serde(rename = "api-key")] - #[default] - ApiKey, -} - -/// Why the binary data is absent: it exceeded the inline size limit, or its asset was unavailable +/// Current status: running, disabled, failed, or starting /// ///
/// @@ -23806,13 +29414,19 @@ pub enum ApiKeyAuthInfoType { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum OmittedBinaryOmittedReason { - /// Bytes exceeded the session's inline size limit. - #[serde(rename = "too_large")] - TooLarge, - /// The referenced binary asset could not be found (e.g. a truncated log). - #[serde(rename = "asset_unavailable")] - AssetUnavailable, +pub enum ExtensionStatus { + /// The extension process is running. + #[serde(rename = "running")] + Running, + /// The extension is installed but disabled. + #[serde(rename = "disabled")] + Disabled, + /// The extension failed to start or crashed. + #[serde(rename = "failed")] + Failed, + /// The extension process is starting. + #[serde(rename = "starting")] + Starting, /// Unknown variant for forward compatibility. #[default] #[serde(other)] @@ -23821,69 +29435,59 @@ pub enum OmittedBinaryOmittedReason { /// Attachment type discriminator #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum AttachmentBlobType { - #[serde(rename = "blob")] - #[default] - Blob, -} - -/// Attachment type discriminator -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum AttachmentDirectoryType { - #[serde(rename = "directory")] - #[default] - Directory, -} - -/// Attachment type discriminator -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum AttachmentExtensionContextType { +pub enum ExtensionContextPushInputType { #[serde(rename = "extension_context")] #[default] ExtensionContext, } -/// Attachment type discriminator -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum AttachmentFileType { - #[serde(rename = "file")] - #[default] - File, -} - -/// Attachment type discriminator +/// Binary result type discriminator. Use "image" for images and "resource" for other binary data. +/// +///
+/// +/// **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 AttachmentGitHubActionsJobType { - #[serde(rename = "github_actions_job")] +pub enum ExternalToolTextResultForLlmBinaryResultsForLlmType { + /// Binary image data. + #[serde(rename = "image")] + Image, + /// Other binary resource data. + #[serde(rename = "resource")] + Resource, + /// Unknown variant for forward compatibility. #[default] - GitHubActionsJob, + #[serde(other)] + Unknown, } -/// Attachment type discriminator +/// Content block type discriminator #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum AttachmentGitHubCommitType { - #[serde(rename = "github_commit")] +pub enum ExternalToolTextResultForLlmContentAudioType { + #[serde(rename = "audio")] #[default] - GitHubCommit, + Audio, } -/// Attachment type discriminator +/// Content block type discriminator #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum AttachmentGitHubFileType { - #[serde(rename = "github_file")] +pub enum ExternalToolTextResultForLlmContentImageType { + #[serde(rename = "image")] #[default] - GitHubFile, + Image, } -/// Attachment type discriminator +/// Content block type discriminator #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum AttachmentGitHubFileDiffType { - #[serde(rename = "github_file_diff")] +pub enum ExternalToolTextResultForLlmContentResourceType { + #[serde(rename = "resource")] #[default] - GitHubFileDiff, + Resource, } -/// Type of GitHub reference +/// Theme variant this icon is intended for /// ///
/// @@ -23892,71 +29496,52 @@ pub enum AttachmentGitHubFileDiffType { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum AttachmentGitHubReferenceType { - /// GitHub issue reference. - #[serde(rename = "issue")] - Issue, - /// GitHub pull request reference. - #[serde(rename = "pr")] - Pr, - /// GitHub discussion reference. - #[serde(rename = "discussion")] - Discussion, +pub enum ExternalToolTextResultForLlmContentResourceLinkIconTheme { + /// Icon intended for light themes. + #[serde(rename = "light")] + Light, + /// Icon intended for dark themes. + #[serde(rename = "dark")] + Dark, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Attachment type discriminator -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum AttachmentGitHubReleaseType { - #[serde(rename = "github_release")] - #[default] - GitHubRelease, -} - -/// Attachment type discriminator -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum AttachmentGitHubRepositoryType { - #[serde(rename = "github_repository")] - #[default] - GitHubRepository, -} - -/// Attachment type discriminator +/// Content block type discriminator #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum AttachmentGitHubSnippetType { - #[serde(rename = "github_snippet")] +pub enum ExternalToolTextResultForLlmContentResourceLinkType { + #[serde(rename = "resource_link")] #[default] - GitHubSnippet, + ResourceLink, } -/// Attachment type discriminator +/// Content block type discriminator #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum AttachmentGitHubTreeComparisonType { - #[serde(rename = "github_tree_comparison")] +pub enum ExternalToolTextResultForLlmContentShellExitType { + #[serde(rename = "shell_exit")] #[default] - GitHubTreeComparison, + ShellExit, } -/// Attachment type discriminator +/// Content block type discriminator #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum AttachmentGitHubUrlType { - #[serde(rename = "github_url")] +pub enum ExternalToolTextResultForLlmContentTerminalType { + #[serde(rename = "terminal")] #[default] - GitHubUrl, + Terminal, } -/// Attachment type discriminator +/// Content block type discriminator #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum AttachmentSelectionType { - #[serde(rename = "selection")] +pub enum ExternalToolTextResultForLlmContentTextType { + #[serde(rename = "text")] #[default] - Selection, + Text, } -/// Authentication type +/// Execution-critical factory storage operation. /// ///
/// @@ -23965,35 +29550,47 @@ pub enum AttachmentSelectionType { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum AuthInfoType { - /// Authentication provided by a GitHub App HMAC credential. - #[serde(rename = "hmac")] - Hmac, - /// Authentication resolved from environment-provided credentials. - #[serde(rename = "env")] - Env, - /// Authentication from an interactive user sign-in. - #[serde(rename = "user")] - User, - /// Authentication delegated to the GitHub CLI. - #[serde(rename = "gh-cli")] - GhCli, - /// Authentication from an API key credential. - #[serde(rename = "api-key")] - ApiKey, - /// Authentication from a GitHub token. - #[serde(rename = "token")] - Token, - /// Authentication from a Copilot API token. - #[serde(rename = "copilot-api-token")] - CopilotApiToken, +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, + /// Renewing the durable owner lease that proves this process still owns the run. + #[serde(rename = "refreshLease")] + RefreshLease, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Optional completion hint for the input (e.g. 'directory' for filesystem path completion) +/// Current or terminal state of a factory run. /// ///
/// @@ -24002,17 +29599,32 @@ pub enum AuthInfoType { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum SlashCommandInputCompletion { - /// Input should complete filesystem directories. - #[serde(rename = "directory")] - Directory, +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, } -/// Coarse command category for grouping and behavior: runtime built-in, skill-backed command, or SDK/client-owned command +/// Kind of factory progress line. /// ///
/// @@ -24021,23 +29633,20 @@ pub enum SlashCommandInputCompletion { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum SlashCommandKind { - /// Command implemented by the runtime. - #[serde(rename = "builtin")] - Builtin, - /// Command backed by a skill. - #[serde(rename = "skill")] - Skill, - /// Command registered by an SDK client or extension. - #[serde(rename = "client")] - Client, +pub enum FactoryLogLineKind { + /// A narrator log line. + #[serde(rename = "log")] + Log, + /// A named factory phase marker. + #[serde(rename = "phase")] + Phase, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Neutral SDK discriminator for the connected remote session kind. +/// Derived lifecycle state of a factory phase. /// ///
/// @@ -24046,20 +29655,26 @@ pub enum SlashCommandKind { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum ConnectedRemoteSessionMetadataKind { - /// Remote CLI session. - #[serde(rename = "remote-session")] - RemoteSession, - /// GitHub Copilot coding agent session. - #[serde(rename = "coding-agent")] - CodingAgent, +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, } -/// Controls how MCP tool result content is filtered: none leaves content unchanged, markdown sanitizes HTML while preserving Markdown-friendly output, and hidden_characters removes characters that can hide directives. +/// Cumulative resource ceiling that stopped a factory run. /// ///
/// @@ -24068,81 +29683,54 @@ pub enum ConnectedRemoteSessionMetadataKind { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum ContentFilterMode { - /// Leave MCP tool result content unchanged. - #[serde(rename = "none")] - None, - /// Sanitize HTML while preserving Markdown-friendly output. - #[serde(rename = "markdown")] - Markdown, - /// Remove characters that can hide directives. - #[serde(rename = "hidden_characters")] - HiddenCharacters, +pub enum FactoryRunFailureKind { + /// The run admitted the approved maximum total number of subagents. + #[serde(rename = "maxTotalSubagents")] + MaxTotalSubagents, + /// 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, } -/// Authentication host (always the public GitHub host). -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum CopilotApiTokenAuthInfoHost { - #[serde(rename = "https://github.com")] - #[default] - HttpsGitHubCom, -} - -/// Direct Copilot API authentication via the `GITHUB_COPILOT_API_TOKEN` + `COPILOT_API_URL` environment-variable pair. The token itself is read from the environment by the runtime, not carried in this struct. -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum CopilotApiTokenAuthInfoType { - #[serde(rename = "copilot-api-token")] - #[default] - CopilotApiToken, -} - -/// Source category for a collected debug bundle entry. -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
+/// Why the runtime is requesting a GitHub credential. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum DebugCollectLogsSource { - /// Session event log. - #[serde(rename = "events")] - Events, - /// Process log for the session. - #[serde(rename = "process-log")] - ProcessLog, - /// Interactive shell log for the session. - #[serde(rename = "shell-log")] - ShellLog, - /// Caller-provided diagnostic entry. - #[serde(rename = "additional")] - Additional, +pub enum GitHubTokenAcquireReason { + /// The runtime is acquiring the registration's first credential. + #[serde(rename = "initial")] + Initial, + /// The runtime is replacing a credential that is approaching expiry. + #[serde(rename = "refresh")] + Refresh, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } +/// GitHub credential response variant discriminator. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum DebugCollectLogsDestinationArchiveKind { - #[serde(rename = "archive")] +pub enum GitHubTokenAcquireResultTokenKind { + #[serde(rename = "token")] #[default] - Archive, + Token, } +/// GitHub credential response variant discriminator. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum DebugCollectLogsDestinationDirectoryKind { - #[serde(rename = "directory")] +pub enum GitHubTokenAcquireResultCancelledKind { + #[serde(rename = "cancelled")] #[default] - Directory, + Cancelled, } -/// Destination for the redacted debug bundle. +/// SDK host response to a GitHub credential request. /// ///
/// @@ -24152,34 +29740,27 @@ pub enum DebugCollectLogsDestinationDirectoryKind { ///
#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] -pub enum DebugCollectLogsDestination { - Archive(DebugCollectLogsDestinationArchive), - Directory(DebugCollectLogsDestinationDirectory), +pub enum GitHubTokenAcquireResult { + Token(GitHubTokenAcquireResultToken), + Cancelled(GitHubTokenAcquireResultCancelled), } -/// Kind of caller-provided debug log entry. -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
+/// 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 DebugCollectLogsEntryKind { - /// Include a single server-local file. - #[serde(rename = "file")] - File, - /// Include files from a server-local directory recursively. - #[serde(rename = "directory")] - Directory, +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, } -/// How a collected debug entry should be redacted before being staged. +/// Reason a captured file was not restored. /// ///
/// @@ -24188,20 +29769,20 @@ pub enum DebugCollectLogsEntryKind { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum DebugCollectLogsRedaction { - /// Redact the file as plain UTF-8 log text. - #[serde(rename = "plain-text")] - PlainText, - /// Redact each non-empty line as a session event JSON object, falling back to plain-text redaction for malformed lines. - #[serde(rename = "events-jsonl")] - EventsJsonl, +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, } -/// Destination kind that was written. +/// Reason a rewind read (rewind points, file-restore preview, or session diff) could not be answered from the session's file-change captures. /// ///
/// @@ -24210,20 +29791,23 @@ 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 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] #[serde(other)] Unknown, } -/// Persisted extension discovery source +/// Aggregate file change represented by a rewind preview. /// ///
/// @@ -24232,20 +29816,23 @@ pub enum DebugCollectLogsResultKind { /// ///
#[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, +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, } -/// Effective extension loading and agent-management mode +/// Scope of a rewind operation. /// ///
/// @@ -24254,23 +29841,20 @@ pub enum DiscoveredExtensionSource { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -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, +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, } -/// Server transport type: stdio, http, sse (deprecated), or memory +/// Outcome of a rewind request. /// ///
/// @@ -24279,56 +29863,65 @@ pub enum DiscoveredExtensionMode { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum DiscoveredMcpServerType { - /// Server communicates over stdio with a local child process. - #[serde(rename = "stdio")] - Stdio, - /// Server communicates over streamable HTTP. - #[serde(rename = "http")] - Http, - /// Server communicates over Server-Sent Events (deprecated). - #[serde(rename = "sse")] - Sse, - /// Server is backed by an in-memory runtime implementation. - #[serde(rename = "memory")] - Memory, +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, } -/// Personal access token (PAT) or server-to-server token sourced from an environment variable. +/// Constant value. Always "github". #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum EnvAuthInfoType { - #[serde(rename = "env")] +pub enum InstalledPluginSourceGitHubSource { + #[serde(rename = "github")] #[default] - Env, + GitHub, } -/// 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.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
+/// Constant value. Always "local". #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum EventsAgentScope { - /// Return main-agent events and typed subagent lifecycle events. - #[serde(rename = "primary")] - Primary, - /// Return events from all agents. - #[serde(rename = "all")] - All, - /// Unknown variant for forward compatibility. +pub enum InstalledPluginSourceLocalSource { + #[serde(rename = "local")] #[default] - #[serde(other)] - Unknown, + Local, } -/// 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. +/// Constant value. Always "url". +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum InstalledPluginSourceUrlSource { + #[serde(rename = "url")] + #[default] + Url, +} + +/// Whether the target is a single file or a directory of instruction files /// ///
/// @@ -24337,20 +29930,20 @@ pub enum EventsAgentScope { /// ///
#[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, +pub enum InstructionDiscoveryPathKind { + /// The target is a single instruction file. + #[serde(rename = "file")] + File, + /// The target is a directory that holds instruction files. + #[serde(rename = "directory")] + Directory, /// 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. +/// Which tier this target belongs to /// ///
/// @@ -24359,20 +29952,26 @@ pub enum EventsReadDirection { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum EventsCursorStatus { - /// The cursor was applied successfully. - #[serde(rename = "ok")] - Ok, - /// The cursor referred to history that is no longer available. - #[serde(rename = "expired")] - Expired, +pub enum InstructionDiscoveryPathLocation { + /// Instructions live in user-level configuration. + #[serde(rename = "user")] + User, + /// Instructions live in repository-level configuration. + #[serde(rename = "repository")] + Repository, + /// Instructions live under the current working directory. + #[serde(rename = "working-directory")] + WorkingDirectory, + /// Instructions live in plugin-provided configuration. + #[serde(rename = "plugin")] + Plugin, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Discovery source: project (.github/extensions/), user (~/.copilot/extensions/), plugin (installed plugin), or session (session-state//extensions/) +/// Where this source lives — used for UI grouping /// ///
/// @@ -24381,26 +29980,26 @@ pub enum EventsCursorStatus { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum ExtensionSource { - /// Extension discovered from the current project's .github/extensions directory. - #[serde(rename = "project")] - Project, - /// Extension discovered from the user's ~/.copilot/extensions directory. +pub enum InstructionSourceLocation { + /// Instructions live in user-level configuration. #[serde(rename = "user")] User, - /// Extension contributed by an installed plugin. + /// Instructions live in repository-level configuration. + #[serde(rename = "repository")] + Repository, + /// Instructions live under the current working directory. + #[serde(rename = "working-directory")] + WorkingDirectory, + /// Instructions live in plugin-provided configuration. #[serde(rename = "plugin")] Plugin, - /// Extension discovered from the current session's state directory (loaded only for this session). - #[serde(rename = "session")] - Session, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Current status: running, disabled, failed, or starting +/// Category of instruction source — used for merge logic /// ///
/// @@ -24409,34 +30008,50 @@ pub enum ExtensionSource { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum ExtensionStatus { - /// The extension process is running. - #[serde(rename = "running")] - Running, - /// The extension is installed but disabled. - #[serde(rename = "disabled")] - Disabled, - /// The extension failed to start or crashed. - #[serde(rename = "failed")] - Failed, - /// The extension process is starting. - #[serde(rename = "starting")] - Starting, +pub enum InstructionSourceType { + /// Instructions loaded from the user's home configuration. + #[serde(rename = "home")] + Home, + /// Instructions loaded from repository-scoped files. + #[serde(rename = "repo")] + Repo, + /// Instructions loaded from model-specific files. + #[serde(rename = "model")] + Model, + /// Instructions loaded from VS Code instruction files. + #[serde(rename = "vscode")] + Vscode, + /// Instructions discovered from nested agent files. + #[serde(rename = "nested-agents")] + NestedAgents, + /// Instructions inherited from child instruction files. + #[serde(rename = "child-instructions")] + ChildInstructions, + /// Instructions supplied by an installed plugin. + #[serde(rename = "plugin")] + Plugin, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Attachment type discriminator +/// 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. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum ExtensionContextPushInputType { - #[serde(rename = "extension_context")] +pub enum LlmInferenceHttpRequestStartTransport { + /// Plain HTTP or SSE response. Each body chunk is an opaque byte range; the response is a status line, headers, and a (possibly streamed) body. + #[serde(rename = "http")] + Http, + /// Full-duplex WebSocket channel. Each body chunk maps to exactly one WebSocket message and the `binary` flag distinguishes text from binary frames; request and response chunks flow concurrently. + #[serde(rename = "websocket")] + Websocket, + /// Unknown variant for forward compatibility. #[default] - ExtensionContext, + #[serde(other)] + Unknown, } -/// Binary result type discriminator. Use "image" for images and "resource" for other binary data. +/// Repository host type /// ///
/// @@ -24445,44 +30060,20 @@ pub enum ExtensionContextPushInputType { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum ExternalToolTextResultForLlmBinaryResultsForLlmType { - /// Binary image data. - #[serde(rename = "image")] - Image, - /// Other binary resource data. - #[serde(rename = "resource")] - Resource, +pub enum SessionContextHostType { + /// Session repository is hosted on GitHub. + #[serde(rename = "github")] + GitHub, + /// Session repository is hosted on Azure DevOps. + #[serde(rename = "ado")] + Ado, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Content block type discriminator -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum ExternalToolTextResultForLlmContentAudioType { - #[serde(rename = "audio")] - #[default] - Audio, -} - -/// Content block type discriminator -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum ExternalToolTextResultForLlmContentImageType { - #[serde(rename = "image")] - #[default] - Image, -} - -/// Content block type discriminator -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum ExternalToolTextResultForLlmContentResourceType { - #[serde(rename = "resource")] - #[default] - Resource, -} - -/// Theme variant this icon is intended for +/// Log severity level. Determines how the message is displayed in the timeline. Defaults to "info". /// ///
/// @@ -24491,52 +30082,48 @@ pub enum ExternalToolTextResultForLlmContentResourceType { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum ExternalToolTextResultForLlmContentResourceLinkIconTheme { - /// Icon intended for light themes. - #[serde(rename = "light")] - Light, - /// Icon intended for dark themes. - #[serde(rename = "dark")] - Dark, +pub enum SessionLogLevel { + /// Informational message. + #[serde(rename = "info")] + Info, + /// Warning message that may require attention. + #[serde(rename = "warning")] + Warning, + /// Error message describing a failure. + #[serde(rename = "error")] + Error, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Content block type discriminator -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum ExternalToolTextResultForLlmContentResourceLinkType { - #[serde(rename = "resource_link")] - #[default] - ResourceLink, -} - -/// Content block type discriminator -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum ExternalToolTextResultForLlmContentShellExitType { - #[serde(rename = "shell_exit")] - #[default] - ShellExit, -} - -/// Content block type discriminator -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum ExternalToolTextResultForLlmContentTerminalType { - #[serde(rename = "terminal")] - #[default] - Terminal, -} - -/// Content block type discriminator +/// Allowed values for the `McpAppsHostContextDetailsAvailableDisplayMode` enumeration. +/// +///
+/// +/// **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 ExternalToolTextResultForLlmContentTextType { - #[serde(rename = "text")] +pub enum McpAppsHostContextDetailsAvailableDisplayMode { + /// Rendered inline within the host conversation surface + #[serde(rename = "inline")] + Inline, + /// Rendered as a fullscreen overlay + #[serde(rename = "fullscreen")] + Fullscreen, + /// Rendered as a picture-in-picture floating panel + #[serde(rename = "pip")] + Pip, + /// Unknown variant for forward compatibility. #[default] - Text, + #[serde(other)] + Unknown, } -/// Execution-critical factory storage operation. +/// Current display mode (SEP-1865) /// ///
/// @@ -24545,44 +30132,23 @@ pub enum ExternalToolTextResultForLlmContentTextType { /// ///
#[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, +pub enum McpAppsHostContextDetailsDisplayMode { + /// Rendered inline within the host conversation surface + #[serde(rename = "inline")] + Inline, + /// Rendered as a fullscreen overlay + #[serde(rename = "fullscreen")] + Fullscreen, + /// Rendered as a picture-in-picture floating panel + #[serde(rename = "pip")] + Pip, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Current or terminal state of a factory run. +/// Platform type for responsive design /// ///
/// @@ -24591,32 +30157,23 @@ pub enum FactoryDurableOperation { /// ///
#[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 McpAppsHostContextDetailsPlatform { + /// Host runs in a web browser + #[serde(rename = "web")] + Web, + /// Host runs as a desktop application + #[serde(rename = "desktop")] + Desktop, + /// Host runs on a mobile device + #[serde(rename = "mobile")] + Mobile, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Kind of factory progress line. +/// UI theme preference per SEP-1865 /// ///
/// @@ -24625,20 +30182,20 @@ pub enum FactoryRunStatus { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum FactoryLogLineKind { - /// A narrator log line. - #[serde(rename = "log")] - Log, - /// A named factory phase marker. - #[serde(rename = "phase")] - Phase, +pub enum McpAppsHostContextDetailsTheme { + /// Light UI theme + #[serde(rename = "light")] + Light, + /// Dark UI theme + #[serde(rename = "dark")] + Dark, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Derived lifecycle state of a factory phase. +/// Allowed values for the `McpAppsSetHostContextDetailsAvailableDisplayMode` enumeration. /// ///
/// @@ -24647,26 +30204,23 @@ pub enum FactoryLogLineKind { /// ///
#[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, +pub enum McpAppsSetHostContextDetailsAvailableDisplayMode { + /// Rendered inline within the host conversation surface + #[serde(rename = "inline")] + Inline, + /// Rendered as a fullscreen overlay + #[serde(rename = "fullscreen")] + Fullscreen, + /// Rendered as a picture-in-picture floating panel + #[serde(rename = "pip")] + Pip, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Cumulative resource ceiling that stopped a factory run. +/// Current display mode (SEP-1865) /// ///
/// @@ -24675,46 +30229,48 @@ pub enum FactoryPhaseStatus { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum FactoryRunFailureKind { - /// The run admitted the approved maximum total number of subagents. - #[serde(rename = "maxTotalSubagents")] - MaxTotalSubagents, - /// 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, +pub enum McpAppsSetHostContextDetailsDisplayMode { + /// Rendered inline within the host conversation surface + #[serde(rename = "inline")] + Inline, + /// Rendered as a fullscreen overlay + #[serde(rename = "fullscreen")] + Fullscreen, + /// Rendered as a picture-in-picture floating panel + #[serde(rename = "pip")] + Pip, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// 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). +/// Platform type for responsive design +/// +///
+/// +/// **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 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, +pub enum McpAppsSetHostContextDetailsPlatform { + /// Host runs in a web browser + #[serde(rename = "web")] + Web, + /// Host runs as a desktop application + #[serde(rename = "desktop")] + Desktop, + /// Host runs on a mobile device + #[serde(rename = "mobile")] + Mobile, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Reason a captured file was not restored. +/// UI theme preference per SEP-1865 /// ///
/// @@ -24723,20 +30279,20 @@ pub enum HistoryCompactRequestTrigger { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -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, +pub enum McpAppsSetHostContextDetailsTheme { + /// Light UI theme + #[serde(rename = "light")] + Light, + /// Dark UI theme + #[serde(rename = "dark")] + Dark, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Reason a rewind read (rewind points, file-restore preview, or session diff) could not be answered from the session's file-change captures. +/// Structured MCP elicitation mode. /// ///
/// @@ -24745,23 +30301,47 @@ pub enum HistoryFileRestoreSkipReason { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -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, +pub enum McpElicitationFormMode { + #[serde(rename = "form")] + Form, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Aggregate file change represented by a rewind preview. +/// Headers-refresh response variant discriminator. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum McpHeadersHandlePendingHeadersRefreshRequestHeadersKind { + #[serde(rename = "headers")] + #[default] + Headers, +} + +/// Headers-refresh response variant discriminator. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum McpHeadersHandlePendingHeadersRefreshRequestNoneKind { + #[serde(rename = "none")] + #[default] + None, +} + +/// Host response: supply dynamic headers or decline this refresh. +/// +///
+/// +/// **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 McpHeadersHandlePendingHeadersRefreshRequest { + Headers(McpHeadersHandlePendingHeadersRefreshRequestHeaders), + None(McpHeadersHandlePendingHeadersRefreshRequestNone), +} + +/// Whether a planned configuration change would create or modify an entry /// ///
/// @@ -24770,45 +30350,39 @@ pub enum HistoryRewindUnavailableReason { /// ///
#[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, +pub enum McpPlanConfigurationOperation { + /// Creates a configuration entry that does not exist yet. + #[serde(rename = "add")] + Add, + /// Modifies a configuration entry that already exists. + #[serde(rename = "update")] + Update, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Scope of a rewind operation. +/// Configuration scope an MCP install plan targets /// ///
/// /// **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, +/// +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum McpPlanScope { + /// The user's own MCP configuration. + #[serde(rename = "user")] + User, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Outcome of a rewind request. +/// What policy decided for a planned server /// ///
/// @@ -24817,141 +30391,61 @@ pub enum HistoryRewindMode { /// ///
#[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, +pub enum McpPlanPolicyDecision { + /// Policy permits the server. + #[serde(rename = "allowed")] + Allowed, + /// Policy forbids the server, so the plan cannot be applied. + #[serde(rename = "blocked")] + Blocked, + /// Policy permits the server only after an explicit approval. + #[serde(rename = "requires-approval")] + RequiresApproval, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Authentication host. HMAC auth always targets the public GitHub host. -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum HMACAuthInfoHost { - #[serde(rename = "https://github.com")] - #[default] - HttpsGitHubCom, -} - -/// HMAC-based authentication used by GitHub-internal services. -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum HMACAuthInfoType { - #[serde(rename = "hmac")] - #[default] - Hmac, -} - -/// Hook event name dispatched through the SDK callback transport. +/// Consumer allowed to call an MCP tool. +/// +///
+/// +/// **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 HookType { - /// Runs before a tool is invoked. - #[serde(rename = "preToolUse")] - PreToolUse, - /// Runs before an MCP tool is invoked. - #[serde(rename = "preMcpToolCall")] - PreMcpToolCall, - /// Runs after a tool completes successfully. - #[serde(rename = "postToolUse")] - PostToolUse, - /// Runs after a tool fails. - #[serde(rename = "postToolUseFailure")] - PostToolUseFailure, - /// Runs after the user submits a prompt. - #[serde(rename = "userPromptSubmitted")] - UserPromptSubmitted, - /// Runs after the runtime transforms the submitted prompt for the model, before it is added to session history. - #[serde(rename = "userPromptTransformed")] - UserPromptTransformed, - /// Runs when a session starts. - #[serde(rename = "sessionStart")] - SessionStart, - /// Runs when a session ends. - #[serde(rename = "sessionEnd")] - SessionEnd, - /// Runs after an agent result is produced. - #[serde(rename = "postResult")] - PostResult, - /// Runs before a pull request description is generated. - #[serde(rename = "prePRDescription")] - PrePRDescription, - /// Runs when the agent encounters an error. - #[serde(rename = "errorOccurred")] - ErrorOccurred, - /// Runs when the agent stops. - #[serde(rename = "agentStop")] - AgentStop, - /// Runs when a subagent starts. - #[serde(rename = "subagentStart")] - SubagentStart, - /// Runs when a subagent stops. - #[serde(rename = "subagentStop")] - SubagentStop, - /// Runs before conversation context is compacted. - #[serde(rename = "preCompact")] - PreCompact, - /// Runs when the agent requests permission. - #[serde(rename = "permissionRequest")] - PermissionRequest, - /// Runs when the agent emits a notification. - #[serde(rename = "notification")] - Notification, +pub enum McpToolUiVisibility { + /// The model may call the tool. + #[serde(rename = "model")] + Model, + /// An MCP App view may call the tool. + #[serde(rename = "app")] + App, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Constant value. Always "github". -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum InstalledPluginSourceGitHubSource { - #[serde(rename = "github")] - #[default] - GitHub, -} - -/// Constant value. Always "local". +/// OAuth response variant discriminator. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum InstalledPluginSourceLocalSource { - #[serde(rename = "local")] +pub enum McpOauthPendingRequestResponseTokenKind { + #[serde(rename = "token")] #[default] - Local, + Token, } -/// Constant value. Always "url". +/// OAuth response variant discriminator. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum InstalledPluginSourceUrlSource { - #[serde(rename = "url")] +pub enum McpOauthPendingRequestResponseCancelledKind { + #[serde(rename = "cancelled")] #[default] - Url, + Cancelled, } -/// Whether the target is a single file or a directory of instruction files +/// Host response to the pending OAuth request. /// ///
/// @@ -24959,21 +30453,14 @@ pub enum InstalledPluginSourceUrlSource { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum InstructionDiscoveryPathKind { - /// The target is a single instruction file. - #[serde(rename = "file")] - File, - /// The target is a directory that holds instruction files. - #[serde(rename = "directory")] - Directory, - /// Unknown variant for forward compatibility. - #[default] - #[serde(other)] - Unknown, +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum McpOauthPendingRequestResponse { + Token(McpOauthPendingRequestResponseToken), + Cancelled(McpOauthPendingRequestResponseCancelled), } -/// Which tier this target belongs to +/// OAuth grant type override for this login. /// ///
/// @@ -24982,26 +30469,20 @@ pub enum InstructionDiscoveryPathKind { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum InstructionDiscoveryPathLocation { - /// Instructions live in user-level configuration. - #[serde(rename = "user")] - User, - /// Instructions live in repository-level configuration. - #[serde(rename = "repository")] - Repository, - /// Instructions live under the current working directory. - #[serde(rename = "working-directory")] - WorkingDirectory, - /// Instructions live in plugin-provided configuration. - #[serde(rename = "plugin")] - Plugin, +pub enum McpOauthLoginGrantType { + /// Interactive browser-based OAuth flow using an authorization code, typically with PKCE. + #[serde(rename = "authorization_code")] + AuthorizationCode, + /// Headless OAuth flow where a confidential client authenticates directly with a client secret. + #[serde(rename = "client_credentials")] + ClientCredentials, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Where this source lives — used for UI grouping +/// Why a passive MCP OAuth probe determined authentication is needed. /// ///
/// @@ -25010,26 +30491,55 @@ pub enum InstructionDiscoveryPathLocation { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum InstructionSourceLocation { - /// Instructions live in user-level configuration. - #[serde(rename = "user")] - User, - /// Instructions live in repository-level configuration. - #[serde(rename = "repository")] - Repository, - /// Instructions live under the current working directory. - #[serde(rename = "working-directory")] - WorkingDirectory, - /// Instructions live in plugin-provided configuration. - #[serde(rename = "plugin")] - Plugin, +pub enum McpOauthProbeNeedsAuthReason { + /// No token was sent and the server requires authentication. + #[serde(rename = "initial")] + Initial, + /// A cached token was sent and rejected. + #[serde(rename = "refresh")] + Refresh, + /// The server returned a 403 insufficient_scope challenge, indicating additional scopes or audience are needed. + #[serde(rename = "upscope")] + Upscope, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Category of instruction source — used for merge logic +/// Probe outcome variant discriminator. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum McpOauthProbeResultNoAuthRequiredStatus { + #[serde(rename = "no-auth-required")] + #[default] + NoAuthRequired, +} + +/// Probe outcome variant discriminator. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum McpOauthProbeResultAuthenticatedStatus { + #[serde(rename = "authenticated")] + #[default] + Authenticated, +} + +/// Probe outcome variant discriminator. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum McpOauthProbeResultNeedsAuthStatus { + #[serde(rename = "needs-auth")] + #[default] + NeedsAuth, +} + +/// Probe outcome variant discriminator. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum McpOauthProbeResultFailedStatus { + #[serde(rename = "failed")] + #[default] + Failed, +} + +/// Passive MCP OAuth probe result. `authenticated` means the server accepted the probe request while an OAuth-origin access token was attached; it does not prove the server required or independently validated that token. The probe does not make a second unauthenticated request. Failed is an expected probe-domain outcome; JSON-RPC errors are reserved for API-call failures. /// ///
/// @@ -25037,51 +30547,43 @@ pub enum InstructionSourceLocation { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum InstructionSourceType { - /// Instructions loaded from the user's home configuration. - #[serde(rename = "home")] - Home, - /// Instructions loaded from repository-scoped files. - #[serde(rename = "repo")] - Repo, - /// Instructions loaded from model-specific files. - #[serde(rename = "model")] - Model, - /// Instructions loaded from VS Code instruction files. - #[serde(rename = "vscode")] - Vscode, - /// Instructions discovered from nested agent files. - #[serde(rename = "nested-agents")] - NestedAgents, - /// Instructions inherited from child instruction files. - #[serde(rename = "child-instructions")] - ChildInstructions, - /// Instructions supplied by an installed plugin. - #[serde(rename = "plugin")] - Plugin, - /// Unknown variant for forward compatibility. - #[default] - #[serde(other)] - Unknown, +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum McpOauthProbeResult { + NoAuthRequired(McpOauthProbeResultNoAuthRequired), + Authenticated(McpOauthProbeResultAuthenticated), + NeedsAuth(McpOauthProbeResultNeedsAuth), + Failed(McpOauthProbeResultFailed), } -/// 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. +/// Discriminator for an enumerated required 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, PartialEq, Eq, Serialize, Deserialize)] -pub enum LlmInferenceHttpRequestStartTransport { - /// Plain HTTP or SSE response. Each body chunk is an opaque byte range; the response is a status line, headers, and a (possibly streamed) body. - #[serde(rename = "http")] - Http, - /// Full-duplex WebSocket channel. Each body chunk maps to exactly one WebSocket message and the `binary` flag distinguishes text from binary frames; request and response chunks flow concurrently. - #[serde(rename = "websocket")] - Websocket, +pub enum McpPlanEnumValueType { + /// One of a fixed, non-empty set of permitted values. + #[serde(rename = "enum")] + Enum, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Repository host type +/// Discriminator: a plan was computed and nothing was changed +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum McpPlanInstallPlannedKind { + #[serde(rename = "planned")] + #[default] + Planned, +} + +/// Discriminator for a candidate-backed install-plan source /// ///
/// @@ -25090,20 +30592,17 @@ pub enum LlmInferenceHttpRequestStartTransport { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum SessionContextHostType { - /// Session repository is hosted on GitHub. - #[serde(rename = "github")] - GitHub, - /// Session repository is hosted on Azure DevOps. - #[serde(rename = "ado")] - Ado, +pub enum McpPlanInstallSourceCandidateKind { + /// Plan from a candidate returned by catalog search. + #[serde(rename = "candidate")] + Candidate, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Log severity level. Determines how the message is displayed in the timeline. Defaults to "info". +/// Discriminator for a URL-backed MCP server card /// ///
/// @@ -25112,23 +30611,17 @@ pub enum SessionContextHostType { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum SessionLogLevel { - /// Informational message. - #[serde(rename = "info")] - Info, - /// Warning message that may require attention. - #[serde(rename = "warning")] - Warning, - /// Error message describing a failure. - #[serde(rename = "error")] - Error, +pub enum McpServerCardUrlKind { + /// Retrieve the card from its URL. + #[serde(rename = "url")] + Url, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Allowed values for the `McpAppsHostContextDetailsAvailableDisplayMode` enumeration. +/// Discriminator for an embedded MCP server card /// ///
/// @@ -25137,23 +30630,17 @@ pub enum SessionLogLevel { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum McpAppsHostContextDetailsAvailableDisplayMode { - /// Rendered inline within the host conversation surface - #[serde(rename = "inline")] - Inline, - /// Rendered as a fullscreen overlay - #[serde(rename = "fullscreen")] - Fullscreen, - /// Rendered as a picture-in-picture floating panel - #[serde(rename = "pip")] - Pip, +pub enum McpServerCardEmbeddedKind { + /// Use the embedded card document. + #[serde(rename = "embedded")] + Embedded, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Current display mode (SEP-1865) +/// A card supplied directly by the caller. Exactly one of a URL or embedded data, encoded structurally so neither both nor neither can be expressed. /// ///
/// @@ -25161,24 +30648,14 @@ pub enum McpAppsHostContextDetailsAvailableDisplayMode { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum McpAppsHostContextDetailsDisplayMode { - /// Rendered inline within the host conversation surface - #[serde(rename = "inline")] - Inline, - /// Rendered as a fullscreen overlay - #[serde(rename = "fullscreen")] - Fullscreen, - /// Rendered as a picture-in-picture floating panel - #[serde(rename = "pip")] - Pip, - /// Unknown variant for forward compatibility. - #[default] - #[serde(other)] - Unknown, +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum McpServerCardReference { + Url(McpServerCardUrl), + Embedded(McpServerCardEmbedded), } -/// Platform type for responsive design +/// Discriminator for a caller-supplied-card install-plan source /// ///
/// @@ -25187,23 +30664,17 @@ pub enum McpAppsHostContextDetailsDisplayMode { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum McpAppsHostContextDetailsPlatform { - /// Host runs in a web browser - #[serde(rename = "web")] - Web, - /// Host runs as a desktop application - #[serde(rename = "desktop")] - Desktop, - /// Host runs on a mobile device - #[serde(rename = "mobile")] - Mobile, +pub enum McpPlanInstallSourceCardKind { + /// Plan directly from a caller-supplied card. + #[serde(rename = "card")] + Card, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// UI theme preference per SEP-1865 +/// What an install plan is computed from: a candidate handle from a previous search, or a card supplied directly. /// ///
/// @@ -25211,21 +30682,40 @@ pub enum McpAppsHostContextDetailsPlatform { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum McpAppsHostContextDetailsTheme { - /// Light UI theme - #[serde(rename = "light")] - Light, - /// Dark UI theme - #[serde(rename = "dark")] - Dark, - /// Unknown variant for forward compatibility. - #[default] - #[serde(other)] - Unknown, +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum McpPlanInstallSource { + Candidate(McpPlanInstallSourceCandidate), + Card(McpPlanInstallSourceCard), } -/// Allowed values for the `McpAppsSetHostContextDetailsAvailableDisplayMode` enumeration. +/// Outcome of an mcp.planInstall call: either a normalised plan, or one typed refusal. Nothing is written in either case. +/// +///
+/// +/// **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 McpPlanInstallResult { + Planned(McpPlanInstallPlanned), + NegotiationRefused(CatalogNegotiationRefusedError), + HandleRejected(CatalogHandleRejectedError), + InvalidRequest(CatalogInvalidRequestError), + AuthenticationRequired(CatalogAuthenticationRequiredError), + PolicyRejected(CatalogPolicyRejectedError), + NetworkFailure(CatalogNetworkFailureError), + UnsafeRetrieval(CatalogUnsafeRetrievalError), + MalformedCard(CatalogMalformedCardError), + ContractViolation(CatalogContractViolationError), + UnavailableTransport(CatalogUnavailableTransportError), + NotInstallable(CatalogNotInstallableError), + Unavailable(CatalogUnavailableError), +} + +/// Discriminator for a package-backed transport choice /// ///
/// @@ -25234,23 +30724,17 @@ pub enum McpAppsHostContextDetailsTheme { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum McpAppsSetHostContextDetailsAvailableDisplayMode { - /// Rendered inline within the host conversation surface - #[serde(rename = "inline")] - Inline, - /// Rendered as a fullscreen overlay - #[serde(rename = "fullscreen")] - Fullscreen, - /// Rendered as a picture-in-picture floating panel - #[serde(rename = "pip")] - Pip, +pub enum McpPlanPackageInstallMethod { + /// Install and run a local package. + #[serde(rename = "package")] + Package, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Current display mode (SEP-1865) +/// Transport exposed by a locally launched package /// ///
/// @@ -25259,23 +30743,17 @@ pub enum McpAppsSetHostContextDetailsAvailableDisplayMode { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum McpAppsSetHostContextDetailsDisplayMode { - /// Rendered inline within the host conversation surface - #[serde(rename = "inline")] - Inline, - /// Rendered as a fullscreen overlay - #[serde(rename = "fullscreen")] - Fullscreen, - /// Rendered as a picture-in-picture floating panel - #[serde(rename = "pip")] - Pip, +pub enum McpPlanPackageTransport { + /// A locally launched process spoken to over standard input and output. + #[serde(rename = "stdio")] + Stdio, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Platform type for responsive design +/// Discriminator for a remote-endpoint transport choice /// ///
/// @@ -25284,23 +30762,17 @@ pub enum McpAppsSetHostContextDetailsDisplayMode { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum McpAppsSetHostContextDetailsPlatform { - /// Host runs in a web browser - #[serde(rename = "web")] - Web, - /// Host runs as a desktop application - #[serde(rename = "desktop")] - Desktop, - /// Host runs on a mobile device - #[serde(rename = "mobile")] - Mobile, +pub enum McpPlanRemoteInstallMethod { + /// Connect to a remote endpoint. + #[serde(rename = "remote")] + Remote, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// UI theme preference per SEP-1865 +/// Transport exposed by a remote endpoint /// ///
/// @@ -25309,34 +30781,23 @@ pub enum McpAppsSetHostContextDetailsPlatform { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum McpAppsSetHostContextDetailsTheme { - /// Light UI theme - #[serde(rename = "light")] - Light, - /// Dark UI theme - #[serde(rename = "dark")] - Dark, +pub enum McpPlanRemoteTransport { + /// An HTTP endpoint. + #[serde(rename = "http")] + Http, + /// A streamable HTTP endpoint. + #[serde(rename = "streamable-http")] + StreamableHttp, + /// A server-sent events endpoint. + #[serde(rename = "sse")] + Sse, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum McpHeadersHandlePendingHeadersRefreshRequestHeadersKind { - #[serde(rename = "headers")] - #[default] - Headers, -} - -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum McpHeadersHandlePendingHeadersRefreshRequestNoneKind { - #[serde(rename = "none")] - #[default] - None, -} - -/// Host response: supply dynamic headers or decline this refresh. +/// Where a required value is applied when the planned server is launched /// ///
/// @@ -25344,14 +30805,30 @@ pub enum McpHeadersHandlePendingHeadersRefreshRequestNoneKind { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum McpHeadersHandlePendingHeadersRefreshRequest { - Headers(McpHeadersHandlePendingHeadersRefreshRequestHeaders), - None(McpHeadersHandlePendingHeadersRefreshRequestNone), +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum McpPlanValueCategory { + /// Set as an environment variable on the launched process. + #[serde(rename = "environment-variable")] + EnvironmentVariable, + /// Passed to the runtime that launches the package. + #[serde(rename = "runtime-argument")] + RuntimeArgument, + /// Passed to the packaged server itself. + #[serde(rename = "package-argument")] + PackageArgument, + /// Sent as a request header to a remote endpoint. + #[serde(rename = "header")] + Header, + /// Substituted into the remote endpoint URL. + #[serde(rename = "url-variable")] + UrlVariable, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// Consumer allowed to call an MCP tool. +/// Discriminator for a scalar required value /// ///
/// @@ -25360,34 +30837,17 @@ pub enum McpHeadersHandlePendingHeadersRefreshRequest { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum McpToolUiVisibility { - /// The model may call the tool. - #[serde(rename = "model")] - Model, - /// An MCP App view may call the tool. - #[serde(rename = "app")] - App, +pub enum McpPlanRequiredValueScalarKind { + /// The value uses one scalar type. + #[serde(rename = "scalar")] + Scalar, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum McpOauthPendingRequestResponseTokenKind { - #[serde(rename = "token")] - #[default] - Token, -} - -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum McpOauthPendingRequestResponseCancelledKind { - #[serde(rename = "cancelled")] - #[default] - Cancelled, -} - -/// Host response to the pending OAuth request. +/// Scalar type a required value must conform to /// ///
/// @@ -25395,14 +30855,27 @@ pub enum McpOauthPendingRequestResponseCancelledKind { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum McpOauthPendingRequestResponse { - Token(McpOauthPendingRequestResponseToken), - Cancelled(McpOauthPendingRequestResponseCancelled), +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum McpPlanScalarValueType { + /// Free text. + #[serde(rename = "string")] + String, + /// A number. + #[serde(rename = "number")] + Number, + /// A boolean. + #[serde(rename = "boolean")] + Boolean, + /// A filesystem path. + #[serde(rename = "path")] + Path, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// OAuth grant type override for this login. +/// Discriminator for an enumerated required value /// ///
/// @@ -25411,19 +30884,31 @@ pub enum McpOauthPendingRequestResponse { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum McpOauthLoginGrantType { - /// Interactive browser-based OAuth flow using an authorization code, typically with PKCE. - #[serde(rename = "authorization_code")] - AuthorizationCode, - /// Headless OAuth flow where a confidential client authenticates directly with a client secret. - #[serde(rename = "client_credentials")] - ClientCredentials, +pub enum McpPlanRequiredValueEnumKind { + /// The value uses a fixed non-empty enumeration. + #[serde(rename = "enum")] + Enum, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } +/// One non-secret value a transport choice needs, represented as a scalar or enumerated variant so enum values cannot be missing or attached to another 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, Serialize, Deserialize)] +#[serde(untagged)] +pub enum McpPlanRequiredValue { + Scalar(McpPlanRequiredValueScalar), + Enum(McpPlanRequiredValueEnum), +} + /// 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. /// ///
@@ -25471,7 +30956,51 @@ pub enum McpServerConfigDeferTools { Unknown, } -/// OAuth grant type to use when authenticating to the remote MCP server. +/// OAuth grant type to use when authenticating to the remote 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. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum McpServerConfigHttpOauthGrantType { + /// Interactive browser-based authorization code flow with PKCE. + #[serde(rename = "authorization_code")] + AuthorizationCode, + /// Headless client credentials flow using the configured OAuth client. + #[serde(rename = "client_credentials")] + ClientCredentials, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Remote transport type. Defaults to "http" when 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, PartialEq, Eq, Serialize, Deserialize)] +pub enum McpServerConfigHttpType { + /// Streamable HTTP transport. + #[serde(rename = "http")] + Http, + /// Server-Sent Events transport. + #[serde(rename = "sse")] + Sse, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// In-process MCP transport type. /// ///
/// @@ -25480,20 +31009,16 @@ pub enum McpServerConfigDeferTools { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum McpServerConfigHttpOauthGrantType { - /// Interactive browser-based authorization code flow with PKCE. - #[serde(rename = "authorization_code")] - AuthorizationCode, - /// Headless client credentials flow using the configured OAuth client. - #[serde(rename = "client_credentials")] - ClientCredentials, +pub enum McpServerConfigMemoryType { + #[serde(rename = "memory")] + Memory, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Remote transport type. Defaults to "http" when omitted. +/// Local MCP transport type. /// ///
/// @@ -25502,13 +31027,13 @@ pub enum McpServerConfigHttpOauthGrantType { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum McpServerConfigHttpType { - /// Streamable HTTP transport. - #[serde(rename = "http")] - Http, - /// Server-Sent Events transport. - #[serde(rename = "sse")] - Sse, +pub enum McpServerConfigStdioType { + /// Legacy alias for the local stdio transport. + #[serde(rename = "local")] + Local, + /// Server communicates over stdio with a local child process. + #[serde(rename = "stdio")] + Stdio, /// Unknown variant for forward compatibility. #[default] #[serde(other)] @@ -25954,6 +31479,14 @@ pub enum PermissionDecisionApproveForSessionApprovalExtensionPermissionAccessKin ExtensionPermissionAccess, } +/// Approval covering an extension's request to read sensitive environment variables. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum PermissionDecisionApproveForSessionApprovalExtensionEnvAccessKind { + #[serde(rename = "extension-env-access")] + #[default] + ExtensionEnvAccess, +} + /// Session-scoped approval to remember (tool prompts only; omitted for path/url prompts) /// ///
@@ -25975,6 +31508,7 @@ pub enum PermissionDecisionApproveForSessionApproval { ExtensionManagement(PermissionDecisionApproveForSessionApprovalExtensionManagement), Factory(PermissionDecisionApproveForSessionApprovalFactory), ExtensionPermissionAccess(PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess), + ExtensionEnvAccess(PermissionDecisionApproveForSessionApprovalExtensionEnvAccess), } /// Approve and remember for the rest of the session @@ -26065,6 +31599,14 @@ pub enum PermissionDecisionApproveForLocationApprovalExtensionPermissionAccessKi ExtensionPermissionAccess, } +/// Approval covering an extension's request to read sensitive environment variables. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum PermissionDecisionApproveForLocationApprovalExtensionEnvAccessKind { + #[serde(rename = "extension-env-access")] + #[default] + ExtensionEnvAccess, +} + /// Approval to persist for this location /// ///
@@ -26088,6 +31630,7 @@ pub enum PermissionDecisionApproveForLocationApproval { ExtensionPermissionAccess( PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess, ), + ExtensionEnvAccess(PermissionDecisionApproveForLocationApprovalExtensionEnvAccess), } /// Approve and persist for this project location @@ -26224,6 +31767,115 @@ pub enum PermissionDecision { 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, +} + +/// Response capability available to the client when it settled a permission 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 PermissionResponseCapability { + /// The client could ask a user for this decision. + #[serde(rename = "interactive")] + Interactive, + /// The client could return an automated response but could not ask a user. + #[serde(rename = "headless")] + Headless, + /// The client had no response path available. + #[serde(rename = "none")] + None, + /// 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 assisted-approval judge recommendation. + #[serde(rename = "assisted_approval")] + AssistedApproval, + /// 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, +} + +/// Client surface that submitted 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 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, + /// An Agent Client Protocol host. + #[serde(rename = "acp")] + Acp, + /// A generic Copilot SDK client. + #[serde(rename = "sdk")] + Sdk, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Approval scoped to specific command identifiers. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum PermissionsLocationsAddToolApprovalDetailsCommandsKind { @@ -26304,6 +31956,14 @@ pub enum PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccessKind ExtensionPermissionAccess, } +/// Approval covering an extension's request to read sensitive environment variables. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccessKind { + #[serde(rename = "extension-env-access")] + #[default] + ExtensionEnvAccess, +} + /// Tool approval to persist and apply /// ///
@@ -26325,6 +31985,7 @@ pub enum PermissionsLocationsAddToolApprovalDetails { ExtensionManagement(PermissionsLocationsAddToolApprovalDetailsExtensionManagement), Factory(PermissionsLocationsAddToolApprovalDetailsFactory), ExtensionPermissionAccess(PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess), + ExtensionEnvAccess(PermissionsLocationsAddToolApprovalDetailsExtensionEnvAccess), } /// Whether the location is a git repo or directory @@ -26349,6 +32010,37 @@ pub enum PermissionLocationType { Unknown, } +/// Optional source for permission-mode telemetry. Defaults to `rpc` when omitted for SDK callers. +/// +///
+/// +/// **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 PermissionModeSource { + /// The mode was set from a CLI command-line flag. + #[serde(rename = "cli_flag")] + CliFlag, + /// The mode was set by a slash command. + #[serde(rename = "slash_command")] + SlashCommand, + /// The mode was set by confirming autopilot behavior. + #[serde(rename = "autopilot_confirmation")] + AutopilotConfirmation, + /// The mode was set at startup by the `defaultPermissionMode` user setting. + #[serde(rename = "user_setting")] + UserSetting, + /// The mode was set through an RPC caller. + #[serde(rename = "rpc")] + Rpc, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Allowed values for the `PermissionsConfigureAdditionalContentExclusionPolicyScope` enumeration. /// ///
@@ -26402,7 +32094,7 @@ pub enum PermissionsModifyRulesScope { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum PermissionsSetAllowAllSource { +pub enum PermissionsSetApproveAllSource { /// Allow-all was enabled from a CLI command-line flag. #[serde(rename = "cli_flag")] CliFlag, @@ -26412,6 +32104,9 @@ pub enum PermissionsSetAllowAllSource { /// Allow-all was enabled by confirming autopilot behavior. #[serde(rename = "autopilot_confirmation")] AutopilotConfirmation, + /// Allow-all was enabled at startup by the `defaultPermissionMode` user setting. + #[serde(rename = "user_setting")] + UserSetting, /// Allow-all was enabled through an RPC caller. #[serde(rename = "rpc")] Rpc, @@ -26421,7 +32116,7 @@ pub enum PermissionsSetAllowAllSource { Unknown, } -/// Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers. +/// Controls whether the runtime may defer loading an external tool definition. /// ///
/// @@ -26430,19 +32125,13 @@ pub enum PermissionsSetAllowAllSource { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum PermissionsSetApproveAllSource { - /// Allow-all was enabled from a CLI command-line flag. - #[serde(rename = "cli_flag")] - CliFlag, - /// Allow-all was enabled by a slash command. - #[serde(rename = "slash_command")] - SlashCommand, - /// Allow-all was enabled by confirming autopilot behavior. - #[serde(rename = "autopilot_confirmation")] - AutopilotConfirmation, - /// Allow-all was enabled through an RPC caller. - #[serde(rename = "rpc")] - Rpc, +pub enum ProtocolExternalToolDefer { + /// The runtime may defer the tool according to its tool-loading policy. + #[serde(rename = "auto")] + Auto, + /// The runtime must include the tool without deferring it. + #[serde(rename = "never")] + Never, /// Unknown variant for forward compatibility. #[default] #[serde(other)] @@ -26776,6 +32465,34 @@ pub enum RemoteSessionMode { Unknown, } +/// What a remote host says one of its sessions is doing right now. Deliberately coarse: this is what a host can report for EVERY session in a catalogue listing, without a client subscribing to each one. AHP's `SessionSummary.status` is the source today; `input-needed` covers both a permission prompt and an `ask_user` question, since the summary does not say which. +/// +///
+/// +/// **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 RemoteSessionHostStatus { + /// No turn is running. + #[serde(rename = "idle")] + Idle, + /// A turn is running. + #[serde(rename = "working")] + Working, + /// The session is blocked on the user: a permission prompt or an `ask_user` question. + #[serde(rename = "input-needed")] + InputNeeded, + /// The session ended its last turn in an error. + #[serde(rename = "error")] + Error, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Whether the remote task originated from CCA or CLI `--remote`. /// ///
@@ -26798,6 +32515,43 @@ pub enum RemoteSessionMetadataTaskType { Unknown, } +/// Origin of the sandbox choice supplied by an internal 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 SandboxConfigSource { + /// The client applied the default because no sandbox preference was configured. + #[serde(rename = "never_configured")] + NeverConfigured, + /// The user's persisted settings enabled the sandbox. + #[serde(rename = "user_enabled")] + UserEnabled, + /// The user's persisted settings disabled the sandbox. + #[serde(rename = "user_disabled")] + UserDisabled, + /// A command-line flag selected the sandbox state for this session. + #[serde(rename = "session_flag")] + SessionFlag, + /// The user disabled the sandbox for the current session. + #[serde(rename = "session_disabled")] + SessionDisabled, + /// The client disabled the sandbox because the host cannot enforce it. + #[serde(rename = "unsupported_host")] + UnsupportedHost, + /// A repository policy selected the sandbox state. + #[serde(rename = "repository_policy")] + RepositoryPolicy, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Session capability enabled for this session /// ///
@@ -27060,6 +32814,7 @@ pub enum SessionLimitPredictionSource { Unknown, } +/// Prediction result variant discriminator. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum SessionLimitPredictionResultAvailableKind { #[serde(rename = "available")] @@ -27067,6 +32822,7 @@ pub enum SessionLimitPredictionResultAvailableKind { Available, } +/// Prediction result variant discriminator. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum SessionLimitPredictionResultUnavailableKind { #[serde(rename = "unavailable")] @@ -27431,6 +33187,34 @@ pub enum SessionsOpenStatus { Unknown, } +/// SDK-side token authentication; the host configured the token directly via the SDK. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SettableTokenAuthInfoType { + #[serde(rename = "token")] + #[default] + Token, +} + +/// Authentication credentials accepted by session.gitHubAuth.setCredentials. Session-owned token-provider identities cannot be installed through this method. +/// +///
+/// +/// **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 SettableAuthInfo { + Hmac(HMACAuthInfo), + Env(EnvAuthInfo), + Token(SettableTokenAuthInfo), + CopilotApiToken(CopilotApiTokenAuthInfo), + User(UserAuthInfo), + GhCli(GhCliAuthInfo), + ApiKey(ApiKeyAuthInfo), +} + /// Rust-owned settings predicates exposed across the SDK boundary. Raw feature-flag names are intentionally not part of the contract. /// ///
@@ -27601,6 +33385,14 @@ pub enum SkillDiscoveryScope { Unknown, } +/// Discriminator for an add-timeline-entry result. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SlashCommandAddTimelineEntryResultKind { + #[serde(rename = "add-timeline-entry")] + #[default] + AddTimelineEntry, +} + /// Agent prompt result discriminator #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum SlashCommandAgentPromptResultKind { @@ -27633,6 +33425,38 @@ pub enum SlashCommandSelectSubcommandResultKind { SelectSubcommand, } +/// Discriminator for a model-picker dialog. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SlashCommandModelPickerDialogKind { + #[serde(rename = "model-picker")] + #[default] + ModelPicker, +} + +/// Discriminator for a show-dialog result. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SlashCommandShowDialogResultKind { + #[serde(rename = "show-dialog")] + #[default] + ShowDialog, +} + +/// Discriminator for a set-model result. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SlashCommandSetModelResultKind { + #[serde(rename = "set-model")] + #[default] + SetModel, +} + +/// Discriminator for a set-plan-model result. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SlashCommandSetPlanModelResultKind { + #[serde(rename = "set-plan-model")] + #[default] + SetPlanModel, +} + /// Result of invoking the slash command (text output, prompt to send to the agent, completion, or subcommand selection). /// ///
@@ -27648,6 +33472,10 @@ pub enum SlashCommandInvocationResult { AgentPrompt(SlashCommandAgentPromptResult), Completed(SlashCommandCompletedResult), SelectSubcommand(SlashCommandSelectSubcommandResult), + AddTimelineEntry(SlashCommandAddTimelineEntryResult), + ShowDialog(SlashCommandShowDialogResult), + SetModel(SlashCommandSetModelResult), + SetPlanModel(SlashCommandSetPlanModelResult), } /// Context tier override for matching subagents @@ -27782,12 +33610,50 @@ pub enum TaskShellProgressType { Shell, } -/// SDK-side token authentication; the host configured the token directly via the SDK. +/// Execution outcome 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 TokenAuthInfoType { - #[serde(rename = "token")] +pub enum ToolResultType { + /// The tool completed successfully. + #[serde(rename = "success")] + Success, + /// The tool failed. + #[serde(rename = "failure")] + Failure, + /// The tool exceeded its execution timeout. + #[serde(rename = "timeout")] + Timeout, + /// The tool request was rejected before execution. + #[serde(rename = "rejected")] + Rejected, + /// Permission policy denied the tool request. + #[serde(rename = "denied")] + Denied, + /// Unknown variant for forward compatibility. #[default] - Token, + #[serde(other)] + Unknown, +} + +/// Canonical result returned by a session tool. +/// +///
+/// +/// **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 ToolResult { + String(String), + ToolResultExpanded(ToolResultExpanded), } /// User's choice for auto-mode switching: yes (allow this turn), yes_always (allow + persist as setting), or no (decline). @@ -28010,14 +33876,6 @@ pub enum UISessionLimitsExhaustedResponseAction { Unknown, } -/// 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. -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum UserAuthInfoType { - #[serde(rename = "user")] - #[default] - User, -} - /// Type of change represented by this file diff. /// ///
diff --git a/rust/src/generated/rpc.rs b/rust/src/generated/rpc.rs index 02404c1129..d31cea5ce9 100644 --- a/rust/src/generated/rpc.rs +++ b/rust/src/generated/rpc.rs @@ -43,6 +43,13 @@ impl<'a> ClientRpc<'a> { } } + /// `catalog.*` sub-namespace. + pub fn catalog(&self) -> ClientRpcCatalog<'a> { + ClientRpcCatalog { + client: self.client, + } + } + /// `commands.*` sub-namespace. pub fn commands(&self) -> ClientRpcCommands<'a> { ClientRpcCommands { @@ -57,6 +64,13 @@ impl<'a> ClientRpc<'a> { } } + /// `hooks.*` sub-namespace. + pub fn hooks(&self) -> ClientRpcHooks<'a> { + ClientRpcHooks { + client: self.client, + } + } + /// `instructions.*` sub-namespace. pub fn instructions(&self) -> ClientRpcInstructions<'a> { ClientRpcInstructions { @@ -71,6 +85,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 { @@ -175,7 +196,7 @@ impl<'a> ClientRpc<'a> { /// /// # Parameters /// - /// * `params` - Parameters for the `server.connect` handshake: an optional connection token and optional connection-level opt-ins (e.g. GitHub telemetry forwarding). + /// * `params` - Connection-level opt-ins for the `server.connect` handshake. Transport authentication is consumed by the native protocol boundary before dispatch. /// /// # Returns /// @@ -196,6 +217,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 uses its built-in extension launcher. + /// + /// 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. @@ -205,7 +249,7 @@ pub struct ClientRpcAccount<'a> { } impl<'a> ClientRpcAccount<'a> { - /// Gets Copilot quota usage for the authenticated user or supplied GitHub token. + /// Gets Copilot quota usage for the current or opaquely selected authenticated user. /// /// Wire method: `account.getQuota`. /// @@ -229,13 +273,13 @@ impl<'a> ClientRpcAccount<'a> { Ok(serde_json::from_value(_value)?) } - /// Gets Copilot quota usage for the authenticated user or supplied GitHub token. + /// Gets Copilot quota usage for the current or opaquely selected authenticated user. /// /// Wire method: `account.getQuota`. /// /// # Parameters /// - /// * `params` - Optional GitHub token used to look up quota for a specific user instead of the global auth context. + /// * `params` - Optional opaque account selection or compatibility GitHub token used to look up quota. /// /// # Returns /// @@ -308,13 +352,13 @@ impl<'a> ClientRpcAccount<'a> { Ok(serde_json::from_value(_value)?) } - /// Stores authentication credentials after successful login (e.g., device code flow). + /// Validates and stores authentication credentials. When login is omitted, resolves the authenticated user from the token before persistence. /// /// Wire method: `account.login`. /// /// # Parameters /// - /// * `params` - Credentials to store after successful authentication + /// * `params` - Credentials to validate and store. Omit login to resolve the authenticated user from the token. /// /// # Returns /// @@ -471,6 +515,42 @@ impl<'a> ClientRpcAgents<'a> { } } +/// `catalog.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcCatalog<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcCatalog<'a> { + /// Requests a bounded catalog search. This host-implemented server method is available through SDK/TUI hosts; standalone and C-ABI runtimes whose host does not implement server-method dispatch return JSON-RPC MethodNotFound. A runtime with search available returns inert candidate summaries, each with an opaque single-use handle scoped to this runtime instance; a runtime without it returns the typed search-unavailable result. Public authorities may be searched anonymously, while an authority that requires credentials yields the typed authentication-required result. All returned text, URLs, and package metadata are untrusted external data and can never trigger instructions, tools, or installation. Read-only: nothing is installed, configured, or persisted. + /// + /// Wire method: `catalog.search`. + /// + /// # Parameters + /// + /// * `params` - A bounded catalog search. Both the query length and the result count are capped by the schema so a caller cannot request an unbounded scan. + /// + /// # Returns + /// + /// Outcome of a catalog.search call: either bounded inert candidates, or one typed refusal. Never a partial success. + /// + ///
+ /// + /// **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 search(&self, params: CatalogSearchRequest) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::CATALOG_SEARCH, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } +} + /// `commands.*` RPCs. #[derive(Clone, Copy)] pub struct ClientRpcCommands<'a> { @@ -583,6 +663,45 @@ impl<'a> ClientRpcExtensions<'a> { } } +/// `hooks.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcHooks<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcHooks<'a> { + /// Discovers hook actions enabled under server-side discovery settings from user, repository, plugin, and managed-policy sources. + /// + /// Wire method: `hooks.discover`. + /// + /// # Parameters + /// + /// * `params` - Optional project paths and host-exclusion behavior for server-scoped hook discovery. + /// + /// # Returns + /// + /// Server-discovered hook actions and partial-load diagnostics from user, repository, plugin, and managed-policy sources. Concrete sessions may include additional session-specific hook sources. + /// + ///
+ /// + /// **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, + params: HooksDiscoverRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::HOOKS_DISCOVER, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } +} + /// `instructions.*` RPCs. #[derive(Clone, Copy)] pub struct ClientRpcInstructions<'a> { @@ -756,6 +875,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> { @@ -797,6 +948,37 @@ impl<'a> ClientRpcMcp<'a> { .await?; Ok(serde_json::from_value(_value)?) } + + /// Requests a side-effect-free MCP install plan from a catalog candidate handle or a caller-supplied card. This host-implemented server method is available through SDK/TUI hosts; standalone and C-ABI runtimes whose host does not implement server-method dispatch return JSON-RPC MethodNotFound. A runtime with planning available returns a normalised plan and opaque single-use plan handle; a runtime without it returns the typed planning-unavailable result. A completed plan reports resource identity, provenance, eligible transport choices, the user-scope target, required typed values and secret placeholders, the policy result, the configuration changes installing would make, and whether a reload would be needed. Planning never writes configuration, stores a secret, or reloads MCP servers, so abandoning a plan needs no call and leaves nothing behind. + /// + /// Wire method: `mcp.planInstall`. + /// + /// # Parameters + /// + /// * `params` - A side-effect-free request for an MCP install plan. Computing a plan never writes configuration, stores a secret, or reloads MCP servers. + /// + /// # Returns + /// + /// Outcome of an mcp.planInstall call: either a normalised plan, or one typed refusal. Nothing is written in either case. + /// + ///
+ /// + /// **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 plan_install( + &self, + params: McpPlanInstallRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::MCP_PLANINSTALL, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } } /// `mcp.config.*` RPCs. @@ -1008,7 +1190,7 @@ impl<'a> ClientRpcModels<'a> { /// /// # Parameters /// - /// * `params` - Optional GitHub token used to list models for a specific user instead of the global auth context. + /// * `params` - Optional opaque account selection or compatibility GitHub token used to list models. /// /// # Returns /// @@ -1062,6 +1244,13 @@ pub struct ClientRpcPlugins<'a> { } impl<'a> ClientRpcPlugins<'a> { + /// `plugins.builtin.*` sub-namespace. + pub fn builtin(&self) -> ClientRpcPluginsBuiltin<'a> { + ClientRpcPluginsBuiltin { + client: self.client, + } + } + /// `plugins.marketplaces.*` sub-namespace. pub fn marketplaces(&self) -> ClientRpcPluginsMarketplaces<'a> { ClientRpcPluginsMarketplaces { @@ -1249,6 +1438,38 @@ impl<'a> ClientRpcPlugins<'a> { } } +/// `plugins.builtin.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcPluginsBuiltin<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcPluginsBuiltin<'a> { + /// Replaces this server's trusted built-in plugin directories while no sessions are active. + /// + /// Wire method: `plugins.builtin.set`. + /// + /// # Parameters + /// + /// * `params` - Trusted built-in plugin directories to use for this runtime process. + /// + ///
+ /// + /// **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(&self, params: PluginsBuiltinSetRequest) -> Result<(), Error> { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::PLUGINS_BUILTIN_SET, Some(wire_params)) + .await?; + Ok(()) + } +} + /// `plugins.marketplaces.*` RPCs. #[derive(Clone, Copy)] pub struct ClientRpcPluginsMarketplaces<'a> { @@ -1711,6 +1932,37 @@ impl<'a> ClientRpcSessions<'a> { Ok(serde_json::from_value(_value)?) } + /// Reads a page of durable events directly from a local session's persisted journal without creating, resuming, or activating the session. The initial backward read uses a bounded tail scan for fast first paint; cursor continuations preserve the session event-log paging semantics. Persisted events may omit payloads that are reconstructed only for an active session. + /// + /// Wire method: `sessions.readPersistedEvents`. + /// + /// # Parameters + /// + /// * `params` - Pagination options for reading an inactive or active local session's persisted event journal. + /// + /// # Returns + /// + /// Batch of session events returned by a read, with cursor and continuation 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 read_persisted_events( + &self, + params: SessionsReadPersistedEventsRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::SESSIONS_READPERSISTEDEVENTS, 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`. @@ -2648,6 +2900,36 @@ impl<'a> ClientRpcSkillsConfig<'a> { .await?; Ok(()) } + + /// Atomically adds or removes one skill from the disabled list. + /// + /// Wire method: `skills.config.setSkillDisabled`. + /// + /// # Parameters + /// + /// * `params` - Adds or removes a single skill from the global disabled list, leaving every other entry untouched. + /// + ///
+ /// + /// **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_skill_disabled( + &self, + params: SkillsConfigSetSkillDisabledRequest, + ) -> Result<(), Error> { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call( + rpc_methods::SKILLS_CONFIG_SETSKILLDISABLED, + Some(wire_params), + ) + .await?; + Ok(()) + } } /// `tools.*` RPCs. @@ -2980,6 +3262,13 @@ impl<'a> SessionRpc<'a> { } } + /// `session.sandbox.*` sub-namespace. + pub fn sandbox(&self) -> SessionRpcSandbox<'a> { + SessionRpcSandbox { + session: self.session, + } + } + /// `session.schedule.*` sub-namespace. pub fn schedule(&self) -> SessionRpcSchedule<'a> { SessionRpcSchedule { @@ -3527,6 +3816,13 @@ impl<'a> SessionRpcCanvas<'a> { } } + /// `session.canvas.provider.*` sub-namespace. + pub fn provider(&self) -> SessionRpcCanvasProvider<'a> { + SessionRpcCanvasProvider { + session: self.session, + } + } + /// Lists canvases declared for the session. /// /// Wire method: `session.canvas.list`. @@ -3675,20 +3971,20 @@ impl<'a> SessionRpcCanvasAction<'a> { } } -/// `session.commands.*` RPCs. +/// `session.canvas.provider.*` RPCs. #[derive(Clone, Copy)] -pub struct SessionRpcCommands<'a> { +pub struct SessionRpcCanvasProvider<'a> { pub(crate) session: &'a Session, } -impl<'a> SessionRpcCommands<'a> { - /// Lists slash commands available in the session. +impl<'a> SessionRpcCanvasProvider<'a> { + /// Registers an internal canvas provider connection and its contributions. /// - /// Wire method: `session.commands.list`. + /// Wire method: `session.canvas.provider.register`. /// - /// # Returns + /// # Parameters /// - /// Slash commands available in the session, after applying any include/exclude filters. + /// * `params` - Internal canvas provider registration parameters. /// ///
/// @@ -3697,27 +3993,30 @@ impl<'a> SessionRpcCommands<'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 register( + &self, + params: CanvasProviderRegisterRequest, + ) -> 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_COMMANDS_LIST, Some(wire_params)) + .call( + rpc_methods::SESSION_CANVAS_PROVIDER_REGISTER, + Some(wire_params), + ) .await?; - Ok(serde_json::from_value(_value)?) + Ok(()) } - /// Lists slash commands available in the session. + /// Unregisters an internal canvas provider connection. /// - /// Wire method: `session.commands.list`. + /// Wire method: `session.canvas.provider.unregister`. /// /// # Parameters /// - /// * `params` - Optional filters controlling which command sources to include in the listing. - /// - /// # Returns - /// - /// Slash commands available in the session, after applying any include/exclude filters. + /// * `params` - Internal canvas provider unregistration parameters. /// ///
/// @@ -3726,18 +4025,87 @@ impl<'a> SessionRpcCommands<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn list_with_params( + pub(crate) async fn unregister( &self, - params: CommandsListRequest, - ) -> Result { + params: CanvasProviderUnregisterRequest, + ) -> 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_COMMANDS_LIST, Some(wire_params)) + .call( + rpc_methods::SESSION_CANVAS_PROVIDER_UNREGISTER, + Some(wire_params), + ) .await?; - Ok(serde_json::from_value(_value)?) + Ok(()) + } +} + +/// `session.commands.*` RPCs. +#[derive(Clone, Copy)] +pub struct SessionRpcCommands<'a> { + pub(crate) session: &'a Session, +} + +impl<'a> SessionRpcCommands<'a> { + /// Lists slash commands available in the session. + /// + /// Wire method: `session.commands.list`. + /// + /// # Returns + /// + /// Slash commands available in the session, after applying any include/exclude filters. + /// + ///
+ /// + /// **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_COMMANDS_LIST, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Lists slash commands available in the session. + /// + /// Wire method: `session.commands.list`. + /// + /// # Parameters + /// + /// * `params` - Optional filters controlling which command sources to include in the listing. + /// + /// # Returns + /// + /// Slash commands available in the session, after applying any include/exclude filters. + /// + ///
+ /// + /// **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: CommandsListRequest, + ) -> 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_COMMANDS_LIST, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) } /// Invokes a slash command in the session. @@ -3773,6 +4141,42 @@ impl<'a> SessionRpcCommands<'a> { Ok(serde_json::from_value(_value)?) } + /// Finalizes persistence associated with a client-applied slash-command effect. + /// + /// Wire method: `session.commands.finalizeInvocationEffect`. + /// + /// # Parameters + /// + /// * `params` - The pending slash-command invocation effect to finalize, plus whether the host applied or cancelled it. + /// + /// # Returns + /// + /// Whether finalizing the invocation effect succeeded, and the failure reason when it did not. + /// + ///
+ /// + /// **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 finalize_invocation_effect( + &self, + params: CommandsFinalizeInvocationEffectRequest, + ) -> 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_COMMANDS_FINALIZEINVOCATIONEFFECT, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + /// Reports completion of a pending client-handled slash command. /// /// Wire method: `session.commands.handlePendingCommand`. @@ -4415,6 +4819,75 @@ impl<'a> SessionRpcFactory<'a> { Ok(serde_json::from_value(_value)?) } + /// Internal tool-originated factory invocation. + /// + /// Wire method: `session.factory.runFromTool`. + /// + /// # Parameters + /// + /// * `params` - Internal parameters for invoking a registered factory from a tool. + /// + /// # Returns + /// + /// Complete current or terminal factory run envelope. + /// + ///
+ /// + /// **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 run_from_tool( + &self, + params: FactoryToolRunRequest, + ) -> 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_RUNFROMTOOL, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Internal tool-originated factory resume. + /// + /// Wire method: `session.factory.resumeFromTool`. + /// + /// # Parameters + /// + /// * `params` - Internal parameters for resuming a factory run from a tool. + /// + /// # Returns + /// + /// Resolved persisted factory identity and resumed run envelope. + /// + ///
+ /// + /// **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 resume_from_tool( + &self, + params: FactoryToolResumeRequest, + ) -> 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_RESUMEFROMTOOL, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + /// Gets the current or settled envelope for a factory run. /// /// Wire method: `session.factory.getRun`. @@ -4449,9 +4922,13 @@ impl<'a> SessionRpcFactory<'a> { /// /// Wire method: `session.factory.listRuns`. /// + /// # Parameters + /// + /// * `params` - Parameters for paging factory runs. + /// /// # Returns /// - /// Factory runs in durable creation order. + /// A page of factory runs in durable creation order. /// ///
/// @@ -4460,8 +4937,12 @@ impl<'a> SessionRpcFactory<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn list_runs(&self) -> Result { - let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + 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() @@ -4748,11 +5229,220 @@ pub struct SessionRpcGitHubAuth<'a> { impl<'a> SessionRpcGitHubAuth<'a> { /// Gets authentication status and account metadata for the session. /// - /// Wire method: `session.gitHubAuth.getStatus`. + /// Wire method: `session.gitHubAuth.getStatus`. + /// + /// # Returns + /// + /// Authentication status and account metadata 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 get_status(&self) -> Result { + let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_GITHUBAUTH_GETSTATUS, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Updates the session's auth credentials used for outbound model and API requests. + /// + /// Wire method: `session.gitHubAuth.setCredentials`. + /// + /// # Parameters + /// + /// * `params` - New auth credentials to install on the session. Omit to leave credentials unchanged. + /// + /// # Returns + /// + /// Indicates whether the credential update 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_credentials( + &self, + params: SessionSetCredentialsParams, + ) -> 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_GITHUBAUTH_SETCREDENTIALS, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Gets the current authentication information for internal session hosts. + /// + /// Wire method: `session.gitHubAuth.getCurrentAuthInfo`. + /// + /// # Returns + /// + /// Current authentication information, or null when no authentication is active. + /// + ///
+ /// + /// **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_current_auth_info(&self) -> Result { + let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + let _value = self + .session + .client() + .call( + rpc_methods::SESSION_GITHUBAUTH_GETCURRENTAUTHINFO, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Gets all authentication accounts available to the internal session host. + /// + /// Wire method: `session.gitHubAuth.getAllAuthAvailable`. + /// + /// # Returns + /// + /// Authentication accounts available to the internal session host. + /// + ///
+ /// + /// **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_all_auth_available( + &self, + ) -> Result { + let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + let _value = self + .session + .client() + .call( + rpc_methods::SESSION_GITHUBAUTH_GETALLAUTHAVAILABLE, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Refreshes Copilot account metadata for the current authentication. + /// + /// Wire method: `session.gitHubAuth.refreshCopilotUser`. + /// + /// # Returns + /// + /// Current authentication information, or null when no authentication is active. + /// + ///
+ /// + /// **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 refresh_copilot_user(&self) -> Result { + let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + let _value = self + .session + .client() + .call( + rpc_methods::SESSION_GITHUBAUTH_REFRESHCOPILOTUSER, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Logs in a GitHub user through the internal session host. + /// + /// Wire method: `session.gitHubAuth.login`. + /// + /// # Parameters + /// + /// * `params` - Internal GitHub login parameters. + /// + /// # Returns + /// + /// Authentication credentials accepted only at native protocol ingress. Runtime outputs use credential-free `AuthIdentity` 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(crate) async fn login(&self, params: SessionAuthLoginRequest) -> 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_GITHUBAUTH_LOGIN, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Switches the session to another available authentication. + /// + /// Wire method: `session.gitHubAuth.switchToAuth`. + /// + /// # Parameters + /// + /// * `params` - Parameters for switching the session's active authentication. + /// + ///
+ /// + /// **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 switch_to_auth( + &self, + params: SessionAuthSwitchRequest, + ) -> 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_GITHUBAUTH_SWITCHTOAUTH, + Some(wire_params), + ) + .await?; + Ok(()) + } + + /// Logs out the session's current GitHub authentication. + /// + /// Wire method: `session.gitHubAuth.logout`. /// /// # Returns /// - /// Authentication status and account metadata for the session. + /// Whether the current authentication was logged out. /// ///
/// @@ -4761,27 +5451,27 @@ impl<'a> SessionRpcGitHubAuth<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn get_status(&self) -> Result { + pub(crate) async fn logout(&self) -> Result { let wire_params = serde_json::json!({ "sessionId": self.session.id() }); let _value = self .session .client() - .call(rpc_methods::SESSION_GITHUBAUTH_GETSTATUS, Some(wire_params)) + .call(rpc_methods::SESSION_GITHUBAUTH_LOGOUT, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Updates the session's auth credentials used for outbound model and API requests. + /// Logs out a specific GitHub authentication. /// - /// Wire method: `session.gitHubAuth.setCredentials`. + /// Wire method: `session.gitHubAuth.logoutUser`. /// /// # Parameters /// - /// * `params` - New auth credentials to install on the session. Omit to leave credentials unchanged. + /// * `params` - Parameters identifying a GitHub authentication to log out. /// /// # Returns /// - /// Indicates whether the credential update succeeded. + /// Whether the requested authentication was logged out. /// ///
/// @@ -4790,17 +5480,45 @@ impl<'a> SessionRpcGitHubAuth<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn set_credentials( + pub(crate) async fn logout_user( &self, - params: SessionSetCredentialsParams, - ) -> Result { + params: SessionAuthLogoutUserRequest, + ) -> 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_GITHUBAUTH_SETCREDENTIALS, + rpc_methods::SESSION_GITHUBAUTH_LOGOUTUSER, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Gets validation errors from the most recent authentication attempt. + /// + /// Wire method: `session.gitHubAuth.lastAuthErrors`. + /// + /// # Returns + /// + /// Validation errors from the most recent authentication attempt. + /// + ///
+ /// + /// **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 last_auth_errors(&self) -> Result { + let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + let _value = self + .session + .client() + .call( + rpc_methods::SESSION_GITHUBAUTH_LASTAUTHERRORS, Some(wire_params), ) .await?; @@ -5430,6 +6148,36 @@ impl<'a> SessionRpcMcp<'a> { Ok(()) } + /// Releases any turns waiting on an in-flight MCP load without cancelling the load, letting the agent proceed while MCP servers finish connecting in the background. No-op when no MCP load is in flight or waiting turns were already released. + /// + /// Wire method: `session.mcp.moveLoadingToBackground`. + /// + /// # Returns + /// + /// Result of moving in-flight MCP loading to the background. + /// + ///
+ /// + /// **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_loading_to_background( + &self, + ) -> Result { + let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + let _value = self + .session + .client() + .call( + rpc_methods::SESSION_MCP_MOVELOADINGTOBACKGROUND, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + /// Reloads MCP server connections for the session with an explicit host-provided configuration. /// /// Wire method: `session.mcp.reloadWithConfig`. @@ -5596,7 +6344,7 @@ impl<'a> SessionRpcMcp<'a> { /// /// # Parameters /// - /// * `params` - Opaque auth info used to configure GitHub MCP. + /// * `params` - Credential-free authentication identity used to configure GitHub MCP. /// /// # Returns /// @@ -6151,6 +6899,36 @@ impl<'a> SessionRpcMcpOauth<'a> { Ok(serde_json::from_value(_value)?) } + /// Passively probes a configured remote MCP server to classify whether OAuth is required or a cached/override token is accepted. Does not start OAuth, emit pending OAuth requests, or mutate MCP connection state. + /// + /// Wire method: `session.mcp.oauth.probe`. + /// + /// # Parameters + /// + /// * `params` - Remote MCP server name for a passive OAuth status probe. + /// + /// # Returns + /// + /// Passive MCP OAuth probe result. `authenticated` means the server accepted the probe request while an OAuth-origin access token was attached; it does not prove the server required or independently validated that token. The probe does not make a second unauthenticated request. Failed is an expected probe-domain outcome; JSON-RPC errors are reserved for API-call failures. + /// + ///
+ /// + /// **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 probe(&self, params: McpOauthProbeRequest) -> 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_PROBE, Some(wire_params)) + .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`. @@ -6626,6 +7404,10 @@ impl<'a> SessionRpcMode<'a> { /// /// * `params` - Agent interaction mode to apply to the session. /// + /// # Returns + /// + /// Outcome of a session mode change, including any model switch it triggered and follow-up the host must perform. + /// ///
/// /// **Experimental.** This API is part of an experimental wire-protocol surface @@ -6633,7 +7415,7 @@ impl<'a> SessionRpcMode<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn set(&self, params: ModeSetRequest) -> Result<(), Error> { + pub async fn set(&self, params: ModeSetRequest) -> 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 @@ -6641,7 +7423,7 @@ impl<'a> SessionRpcMode<'a> { .client() .call(rpc_methods::SESSION_MODE_SET, Some(wire_params)) .await?; - Ok(()) + Ok(serde_json::from_value(_value)?) } } @@ -6710,6 +7492,42 @@ impl<'a> SessionRpcModel<'a> { Ok(serde_json::from_value(_value)?) } + /// Resolves and applies organization-managed and repository model overlays. + /// + /// Wire method: `session.model.applyStartupOverlay`. + /// + /// # Parameters + /// + /// * `params` - Managed, repository, and CLI model overrides to overlay onto the session at startup. + /// + /// # Returns + /// + /// The model identifier active on the session after the switch. + /// + ///
+ /// + /// **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 apply_startup_overlay( + &self, + params: ModelApplyStartupOverlayRequest, + ) -> 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_MODEL_APPLYSTARTUPOVERLAY, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + /// Updates the session's reasoning effort without changing the selected model. /// /// Wire method: `session.model.setReasoningEffort`. @@ -7106,17 +7924,17 @@ impl<'a> SessionRpcPermissions<'a> { Ok(serde_json::from_value(_value)?) } - /// 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. + /// Sets the permission mode for the session. `manual` follows the normal approval flow, `assisted` attaches LLM safety recommendations, and `allow-all` automatically approves permission requests. The result returns the authoritative post-mutation mode so callers can update local state without racing the `session.permissions_changed` notification. /// - /// Wire method: `session.permissions.setAllowAll`. + /// Wire method: `session.permissions.setMode`. /// /// # Parameters /// - /// * `params` - Allow-all mode to apply for the session. + /// * `params` - Permission mode to apply for the session. /// /// # Returns /// - /// Indicates whether the operation succeeded and reports the post-mutation state. + /// Indicates whether the requested permission mode was applied and reports the authoritative post-mutation mode. /// ///
/// @@ -7125,30 +7943,27 @@ impl<'a> SessionRpcPermissions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn set_allow_all( + pub async fn set_mode( &self, - params: PermissionsSetAllowAllRequest, - ) -> Result { + params: PermissionsSetModeRequest, + ) -> 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_SETALLOWALL, - Some(wire_params), - ) + .call(rpc_methods::SESSION_PERMISSIONS_SETMODE, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Returns the current allow-all permission mode for the session. + /// Returns the current permission mode for the session. /// - /// Wire method: `session.permissions.getAllowAll`. + /// Wire method: `session.permissions.getMode`. /// /// # Returns /// - /// Current allow-all permission mode. + /// Current permission mode. /// ///
/// @@ -7157,15 +7972,12 @@ impl<'a> SessionRpcPermissions<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn get_allow_all(&self) -> Result { + pub async fn get_mode(&self) -> Result { let wire_params = serde_json::json!({ "sessionId": self.session.id() }); let _value = self .session .client() - .call( - rpc_methods::SESSION_PERMISSIONS_GETALLOWALL, - Some(wire_params), - ) + .call(rpc_methods::SESSION_PERMISSIONS_GETMODE, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } @@ -7546,7 +8358,7 @@ impl<'a> SessionRpcPermissionsPaths<'a> { Ok(serde_json::from_value(_value)?) } - /// Adds a directory to the session's allow-list. + /// Adds a directory to the session's allow-list and activates conventional skill and agent definitions under it. /// /// Wire method: `session.permissions.paths.add`. /// @@ -8685,6 +9497,42 @@ impl<'a> SessionRpcRemote<'a> { } } +/// `session.sandbox.*` RPCs. +#[derive(Clone, Copy)] +pub struct SessionRpcSandbox<'a> { + pub(crate) session: &'a Session, +} + +impl<'a> SessionRpcSandbox<'a> { + /// Returns whether managed policy requires sandbox enforcement and whether an enforcement failure has permanently blocked the session. + /// + /// Wire method: `session.sandbox.getEnforcementStatus`. + /// + /// # Returns + /// + /// Managed sandbox enforcement state for a 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 get_enforcement_status(&self) -> Result { + let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + let _value = self + .session + .client() + .call( + rpc_methods::SESSION_SANDBOX_GETENFORCEMENTSTATUS, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } +} + /// `session.schedule.*` RPCs. #[derive(Clone, Copy)] pub struct SessionRpcSchedule<'a> { @@ -9741,6 +10589,108 @@ pub struct SessionRpcTools<'a> { } impl<'a> SessionRpcTools<'a> { + /// Executes one tool from the session's currently offered tool set through the native invocation pipeline. + /// + /// Wire method: `session.tools.execute`. + /// + /// # Parameters + /// + /// * `params` - A tool name and arguments to execute through the session's native invocation pipeline. + /// + /// # Returns + /// + /// Canonical result returned by a session tool. + /// + ///
+ /// + /// **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 execute(&self, params: ToolsExecuteRequest) -> 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_TOOLS_EXECUTE, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Returns the Rust-owned built-in tool descriptors used to construct the session's offered tool set. + /// + /// Wire method: `session.tools.getBuiltinDescriptors`. + /// + /// # Parameters + /// + /// * `params` - Options controlling how Rust-owned built-in tool descriptors are materialized. + /// + /// # Returns + /// + /// Rust-owned built-in tool descriptors 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 get_builtin_descriptors( + &self, + params: ToolsGetBuiltinDescriptorsRequest, + ) -> 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_TOOLS_GETBUILTINDESCRIPTORS, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Projects a completed task_complete tool call into its label-safe session event payload. + /// + /// Wire method: `session.tools.taskCompleteEventData`. + /// + /// # Parameters + /// + /// * `params` - Task-completion tool arguments and final result used to build a label-safe session event payload. + /// + /// # Returns + /// + /// Task completion notification with summary from the 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 task_complete_event_data( + &self, + params: ToolsTaskCompleteEventDataRequest, + ) -> 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_TOOLS_TASKCOMPLETEEVENTDATA, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + /// Provides the result for a pending external tool call. /// /// Wire method: `session.tools.handlePendingToolCall`. @@ -9833,6 +10783,36 @@ impl<'a> SessionRpcTools<'a> { Ok(serde_json::from_value(_value)?) } + /// Atomically replaces the complete externally implemented tool list supplied by the calling connection. Built-in, MCP/plugin, extension-discovered, subagent, and tools supplied by other connections remain unchanged. + /// + /// Wire method: `session.tools.set`. + /// + /// # Parameters + /// + /// * `params` - Complete externally implemented tool list for the calling connection. An empty list removes every tool previously supplied by that connection. + /// + /// # Returns + /// + /// Empty result after replacing the calling connection's externally implemented tools. + /// + ///
+ /// + /// **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(&self, params: ToolsSetRequest) -> 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_TOOLS_SET, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + /// Updates the current session's live subagent settings after user settings change. The persisted user settings remain the source of truth for future sessions. /// /// Wire method: `session.tools.updateSubagentSettings`. @@ -9887,7 +10867,7 @@ impl<'a> SessionRpcUi<'a> { /// /// # Returns /// - /// Transient answer generated from current conversation context. + /// Completed transient query. Ordered chunks and the terminal outcome are also delivered through `ui.ephemeral_query` session events while it runs. /// ///
/// diff --git a/rust/src/generated/session_events.rs b/rust/src/generated/session_events.rs index ef3c395b10..eb63f11438 100644 --- a/rust/src/generated/session_events.rs +++ b/rust/src/generated/session_events.rs @@ -39,8 +39,17 @@ pub enum SessionEventType { SessionModelChange, #[serde(rename = "session.mode_changed")] SessionModeChanged, + #[serde(rename = "session.mode_notice_delivered")] + SessionModeNoticeDelivered, #[serde(rename = "session.session_limits_changed")] SessionSessionLimitsChanged, + /// + ///
+ /// + /// **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 = "session.permissions_changed")] SessionPermissionsChanged, #[serde(rename = "session.plan_changed")] @@ -71,6 +80,51 @@ pub enum SessionEventType { SessionCompactionComplete, #[serde(rename = "session.task_complete")] SessionTaskComplete, + /// + ///
+ /// + /// **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 = "session.completion_receipt")] + SessionCompletionReceipt, + /// + ///
+ /// + /// **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 = "session.fusion_route_started")] + SessionFusionRouteStarted, + /// + ///
+ /// + /// **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 = "session.fusion_route_failed")] + SessionFusionRouteFailed, + /// + ///
+ /// + /// **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 = "session.fusion_resolved")] + SessionFusionResolved, + /// + ///
+ /// + /// **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 = "session.fusion_completed")] + SessionFusionCompleted, #[serde(rename = "user.message")] UserMessage, #[serde(rename = "pending_messages.modified")] @@ -79,8 +133,46 @@ pub enum SessionEventType { AssistantTurnStart, #[serde(rename = "assistant.turn_retry")] AssistantTurnRetry, + #[serde(rename = "agent.interrupted")] + AgentInterrupted, #[serde(rename = "assistant.intent")] AssistantIntent, + /// + ///
+ /// + /// **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 = "assistant.fusion_phase_started")] + AssistantFusionPhaseStarted, + /// + ///
+ /// + /// **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 = "assistant.fusion_phase_activity")] + AssistantFusionPhaseActivity, + /// + ///
+ /// + /// **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 = "assistant.fusion_phase_completed")] + AssistantFusionPhaseCompleted, + /// + ///
+ /// + /// **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 = "assistant.fusion_phase_failed")] + AssistantFusionPhaseFailed, #[serde(rename = "assistant.server_tool_progress")] AssistantServerToolProgress, #[serde(rename = "assistant.reasoning")] @@ -103,8 +195,12 @@ pub enum SessionEventType { AssistantIdle, #[serde(rename = "assistant.usage")] AssistantUsage, + #[serde(rename = "prompt_cache_break")] + PromptCacheBreak, #[serde(rename = "model.call_failure")] ModelCallFailure, + #[serde(rename = "model.call_finished")] + ModelCallFinished, #[serde(rename = "model.call_start")] ModelCallStart, #[serde(rename = "abort")] @@ -123,8 +219,12 @@ pub enum SessionEventType { ToolSearchActivated, #[serde(rename = "skill.invoked")] SkillInvoked, + #[serde(rename = "sandbox.decision")] + SandboxDecision, #[serde(rename = "subagent.started")] SubagentStarted, + #[serde(rename = "subagent.configured")] + SubagentConfigured, #[serde(rename = "subagent.completed")] SubagentCompleted, #[serde(rename = "subagent.failed")] @@ -178,6 +278,15 @@ pub enum SessionEventType { McpHeadersRefreshCompleted, #[serde(rename = "session.custom_notification")] SessionCustomNotification, + /// + ///
+ /// + /// **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 = "ui.ephemeral_query")] + UiEphemeralQuery, #[serde(rename = "external_tool.requested")] ExternalToolRequested, #[serde(rename = "external_tool.completed")] @@ -244,6 +353,24 @@ pub enum SessionEventType { ///
#[serde(rename = "factory.run_updated")] FactoryRunUpdated, + /// + ///
+ /// + /// **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_started")] + FactoryRunStarted, + /// + ///
+ /// + /// **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_settled")] + FactoryRunSettled, #[serde(rename = "session.skills_loaded")] SessionSkillsLoaded, #[serde(rename = "session.custom_agents_updated")] @@ -358,8 +485,17 @@ pub enum SessionEventData { SessionModelChange(SessionModelChangeData), #[serde(rename = "session.mode_changed")] SessionModeChanged(SessionModeChangedData), + #[serde(rename = "session.mode_notice_delivered")] + SessionModeNoticeDelivered(SessionModeNoticeDeliveredData), #[serde(rename = "session.session_limits_changed")] SessionSessionLimitsChanged(SessionSessionLimitsChangedData), + /// + ///
+ /// + /// **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 = "session.permissions_changed")] SessionPermissionsChanged(SessionPermissionsChangedData), #[serde(rename = "session.plan_changed")] @@ -390,6 +526,51 @@ pub enum SessionEventData { SessionCompactionComplete(SessionCompactionCompleteData), #[serde(rename = "session.task_complete")] SessionTaskComplete(SessionTaskCompleteData), + /// + ///
+ /// + /// **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 = "session.completion_receipt")] + SessionCompletionReceipt(SessionCompletionReceiptData), + /// + ///
+ /// + /// **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 = "session.fusion_route_started")] + SessionFusionRouteStarted(SessionFusionRouteStartedData), + /// + ///
+ /// + /// **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 = "session.fusion_route_failed")] + SessionFusionRouteFailed(SessionFusionRouteFailedData), + /// + ///
+ /// + /// **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 = "session.fusion_resolved")] + SessionFusionResolved(SessionFusionResolvedData), + /// + ///
+ /// + /// **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 = "session.fusion_completed")] + SessionFusionCompleted(SessionFusionCompletedData), #[serde(rename = "user.message")] UserMessage(UserMessageData), #[serde(rename = "pending_messages.modified")] @@ -398,8 +579,46 @@ pub enum SessionEventData { AssistantTurnStart(AssistantTurnStartData), #[serde(rename = "assistant.turn_retry")] AssistantTurnRetry(AssistantTurnRetryData), + #[serde(rename = "agent.interrupted")] + AgentInterrupted(AgentInterruptedData), #[serde(rename = "assistant.intent")] AssistantIntent(AssistantIntentData), + /// + ///
+ /// + /// **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 = "assistant.fusion_phase_started")] + AssistantFusionPhaseStarted(AssistantFusionPhaseStartedData), + /// + ///
+ /// + /// **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 = "assistant.fusion_phase_activity")] + AssistantFusionPhaseActivity(AssistantFusionPhaseActivityData), + /// + ///
+ /// + /// **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 = "assistant.fusion_phase_completed")] + AssistantFusionPhaseCompleted(AssistantFusionPhaseCompletedData), + /// + ///
+ /// + /// **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 = "assistant.fusion_phase_failed")] + AssistantFusionPhaseFailed(AssistantFusionPhaseFailedData), #[serde(rename = "assistant.server_tool_progress")] AssistantServerToolProgress(AssistantServerToolProgressData), #[serde(rename = "assistant.reasoning")] @@ -422,8 +641,12 @@ pub enum SessionEventData { AssistantIdle(AssistantIdleData), #[serde(rename = "assistant.usage")] AssistantUsage(AssistantUsageData), + #[serde(rename = "prompt_cache_break")] + PromptCacheBreak(PromptCacheBreakData), #[serde(rename = "model.call_failure")] ModelCallFailure(ModelCallFailureData), + #[serde(rename = "model.call_finished")] + ModelCallFinished(ModelCallFinishedData), #[serde(rename = "model.call_start")] ModelCallStart(ModelCallStartData), #[serde(rename = "abort")] @@ -442,8 +665,12 @@ pub enum SessionEventData { ToolSearchActivated(ToolSearchActivatedData), #[serde(rename = "skill.invoked")] SkillInvoked(SkillInvokedData), + #[serde(rename = "sandbox.decision")] + SandboxDecision(SandboxDecisionData), #[serde(rename = "subagent.started")] SubagentStarted(SubagentStartedData), + #[serde(rename = "subagent.configured")] + SubagentConfigured(SubagentConfiguredData), #[serde(rename = "subagent.completed")] SubagentCompleted(SubagentCompletedData), #[serde(rename = "subagent.failed")] @@ -490,6 +717,15 @@ pub enum SessionEventData { McpHeadersRefreshCompleted(McpHeadersRefreshCompletedData), #[serde(rename = "session.custom_notification")] SessionCustomNotification(SessionCustomNotificationData), + /// + ///
+ /// + /// **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 = "ui.ephemeral_query")] + UiEphemeralQuery(UiEphemeralQueryData), #[serde(rename = "external_tool.requested")] ExternalToolRequested(ExternalToolRequestedData), #[serde(rename = "external_tool.completed")] @@ -556,6 +792,24 @@ pub enum SessionEventData { ///
#[serde(rename = "factory.run_updated")] FactoryRunUpdated(FactoryRunUpdatedData), + /// + ///
+ /// + /// **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_started")] + FactoryRunStarted(FactoryRunStartedData), + /// + ///
+ /// + /// **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_settled")] + FactoryRunSettled(FactoryRunSettledData), #[serde(rename = "session.skills_loaded")] SessionSkillsLoaded(SessionSkillsLoadedData), #[serde(rename = "session.custom_agents_updated")] @@ -725,6 +979,9 @@ pub struct SessionStartData { /// Whether the session was already in use by another client at start time #[serde(skip_serializing_if = "Option::is_none")] pub already_in_use: Option, + /// Auto routing preference selected at session creation time + #[serde(skip_serializing_if = "Option::is_none")] + pub auto_tier: Option, /// Working directory and git context at session start #[serde(skip_serializing_if = "Option::is_none")] pub context: Option, @@ -774,6 +1031,9 @@ pub struct SessionResumeData { /// Whether the session was already in use by another client at resume time #[serde(skip_serializing_if = "Option::is_none")] pub already_in_use: Option, + /// Auto routing preference active at resume time + #[serde(skip_serializing_if = "Option::is_none")] + pub auto_tier: Option, /// Updated working directory and git context at resume time #[serde(skip_serializing_if = "Option::is_none")] pub context: Option, @@ -859,6 +1119,9 @@ pub struct SessionIdleData { /// True when the preceding agentic loop was cancelled via abort signal #[serde(skip_serializing_if = "Option::is_none")] pub aborted: Option, + /// The session mode the agent was operating in when it went idle, when the mode is known. Lets turn-scoped consumers distinguish an autopilot continuation boundary (where the agent keeps working after this idle) from a genuine turn completion. + #[serde(skip_serializing_if = "Option::is_none")] + pub mode: Option, } /// Session event "session.title_changed". Session title change payload containing the new display title @@ -994,6 +1257,9 @@ pub struct SessionModelChangeData { /// Reasoning summary mode after the model change, if applicable #[serde(skip_serializing_if = "Option::is_none")] pub reasoning_summary: Option, + /// Origin of the effective model change, when known. + #[serde(skip_serializing_if = "Option::is_none")] + pub source: Option, /// Output verbosity level after the model change, if applicable #[serde(skip_serializing_if = "Option::is_none")] pub verbosity: Option, @@ -1009,6 +1275,17 @@ pub struct SessionModeChangedData { pub previous_mode: SessionMode, } +/// Session event "session.mode_notice_delivered". Records that a mode transition notice reached the model so cache-stable mode tools can remain offered across resume. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionModeNoticeDeliveredData { + /// Model-visible transition notice persisted for a mid-turn delivery + #[serde(skip_serializing_if = "Option::is_none")] + pub content: Option, + /// Mode established by the delivered transition notice + pub mode: SessionMode, +} + /// Session event "session.session_limits_changed". Session limits update details. Null clears the limits. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -1017,11 +1294,18 @@ pub struct SessionSessionLimitsChangedData { pub session_limits: Option, } -/// Session event "session.permissions_changed". Permissions change details carrying the aggregate allow-all transition. +/// Session event "session.permissions_changed". Permission-mode transition 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, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionPermissionsChangedData { - /// Allow-all mode after the change + /// Explicit LLM judge model override used by assisted mode; omitted when the provider default applies /// ///
/// @@ -1030,10 +1314,8 @@ pub struct SessionPermissionsChangedData { /// ///
#[serde(skip_serializing_if = "Option::is_none")] - pub allow_all_permission_mode: Option, - /// Aggregate allow-all flag after the change - pub allow_all_permissions: bool, - /// Allow-all mode before the change + pub assisted_approval_model: Option, + /// Permission mode after the change /// ///
/// @@ -1041,10 +1323,16 @@ pub struct SessionPermissionsChangedData { /// and may change or be removed in future SDK or CLI releases. /// ///
- #[serde(skip_serializing_if = "Option::is_none")] - pub previous_allow_all_permission_mode: Option, - /// Aggregate allow-all flag before the change - pub previous_allow_all_permissions: bool, + pub mode: PermissionMode, + /// Permission mode before the change + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ pub previous_mode: PermissionMode, } /// Session event "session.plan_changed". Plan file operation details indicating what changed @@ -1140,18 +1428,6 @@ pub struct SessionSnapshotRewindData { pub up_to_event_id: String, } -/// Aggregate code change metrics for the session -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ShutdownCodeChanges { - /// List of file paths that were modified during the session - pub files_modified: Vec, - /// Total number of lines added during the session - pub lines_added: i64, - /// Total number of lines removed during the session - pub lines_removed: i64, -} - /// Request count and cost metrics #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -1226,6 +1502,36 @@ pub struct ShutdownModelMetric { pub usage: ShutdownModelMetricUsage, } +/// Usage attributed to one agent instance at session shutdown. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ShutdownAgentMetric { + /// Human-readable label for this subagent invocation, copied from the originating `subagent.started` event. For task-tool subagents this is the invocation's task description rather than the agent's configured display name, so group by `agentName` for stable per-agent labels. + #[serde(skip_serializing_if = "Option::is_none")] + pub agent_display_name: Option, + /// Configured agent name, when this is a subagent + #[serde(skip_serializing_if = "Option::is_none")] + pub agent_name: Option, + /// Per-model usage for this agent, keyed by model identifier + pub model_metrics: HashMap, + /// Time spent in model API calls by this agent, in milliseconds + pub total_api_duration_ms: i64, + /// Accumulated nano-AI units cost for this agent + pub total_nano_aiu: f64, +} + +/// Aggregate code change metrics for the session +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ShutdownCodeChanges { + /// List of file paths that were modified during the session + pub files_modified: Vec, + /// Total number of lines added during the session + pub lines_added: i64, + /// Total number of lines removed during the session + pub lines_removed: i64, +} + /// A session-wide shutdown token-type entry storing the accumulated token count. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -1238,6 +1544,9 @@ pub struct ShutdownTokenDetail { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionShutdownData { + /// Per-agent usage breakdown, keyed by agent instance identifier. The main conversation uses the stable key `main`. + #[serde(skip_serializing_if = "Option::is_none")] + pub agent_metrics: Option>, /// Aggregate code change metrics for the session pub code_changes: ShutdownCodeChanges, /// Non-system message token count at shutdown @@ -1309,6 +1618,10 @@ pub struct SessionUsageCheckpointData { #[doc(hidden)] #[serde(skip_serializing_if = "Option::is_none")] pub(crate) model_cache_state: Option>, + /// Internal per-conversation prompt-cache-break detector baselines restored on resume + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) prompt_cache_break_state: Option>, /// Session-wide accumulated nano-AI units cost at checkpoint time pub total_nano_aiu: f64, /// Total number of premium API requests used at checkpoint time @@ -1470,6 +1783,9 @@ pub struct CompactionCompleteCompactionTokensUsed { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionCompactionCompleteData { + /// Canonical model identifier used for model-specific behavior when replaying compaction + #[serde(skip_serializing_if = "Option::is_none")] + pub behavior_model_id: Option, /// Checkpoint snapshot number created for recovery #[serde(skip_serializing_if = "Option::is_none")] pub checkpoint_number: Option, @@ -1552,17 +1868,307 @@ pub struct SessionTaskCompleteData { pub summary: Option, } -/// Session event "user.message". Payload of `user.message` with displayed and model-transformed content, attachments, source/delivery metadata, mode, and telemetry IDs. +/// Inclusive durable event range summarized by a completion receipt. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UserMessageData { - /// The agent mode that was active when this message was sent +pub struct CompletionReceiptEventRange { + /// Identifier of the assistant turn-end event that ends the covered exchange. Always equals the receipt's sourceEventId, so either field is a valid join key. + pub end_event_id: String, + /// Identifier of the user message that starts the covered exchange. + pub start_event_id: String, +} + +/// Final structured tool completion in the covered event range. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CompletionReceiptFinalTool { + /// Process exit code from a structured shell result, when available. #[serde(skip_serializing_if = "Option::is_none")] - pub agent_mode: Option, - /// Files, selections, or GitHub references attached to the message + pub exit_code: Option, + /// Structured success or failure status from the tool completion event. + pub status: CompletionReceiptToolStatus, + /// Unique identifier of the completed tool call. + pub tool_call_id: String, + /// Tool name from the matching tool execution start event, when available. #[serde(skip_serializing_if = "Option::is_none")] - pub attachments: Option>, - /// The user's message text as displayed in the timeline + pub tool_name: Option, +} + +/// Session event "session.completion_receipt". Behavior-neutral record of structured runtime facts present when an agent completion decision is accepted. +/// +///
+/// +/// **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 SessionCompletionReceiptData { + /// One-based accepted completion receipt ordinal in the durable session history. + pub attempt: i64, + /// Inclusive durable event range summarized by this receipt. + pub event_range: CompletionReceiptEventRange, + /// Number of failed structured tool completions in the covered range. + pub failed_tool_count: i64, + /// Final structured tool completion in the covered range, when one exists. + #[serde(skip_serializing_if = "Option::is_none")] + pub final_tool: Option, + /// Version of the completion receipt payload. + pub schema_version: i64, + /// Identifier of the assistant turn-end event that supplied the accepted completion boundary. This is the receipt's idempotency key, and always equals eventRange.endEventId. + pub source_event_id: String, + /// Runtime reason the completion decision was accepted. + pub stop_reason: CompletionReceiptStopReason, + /// Number of successful structured tool completions in the covered range. + pub successful_tool_count: i64, +} + +/// Session event "session.fusion_route_started". Experimental transient signal that HydraFusion routing has started for an eligible 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 SessionFusionRouteStartedData { + /// Identifier for this routing attempt before a durable Fusion turn exists. + pub attempt_id: String, + /// HydraFusion routing policy requested for the turn. + #[serde(skip_serializing_if = "Option::is_none")] + pub policy: Option, + /// Synthetic HydraFusion model selected for the session. + #[serde(skip_serializing_if = "Option::is_none")] + pub synthetic_model: Option, + /// Kind of turn being routed. + pub turn_kind: FusionTurnKind, +} + +/// Session event "session.fusion_route_failed". Experimental durable HydraFusion routing failure and the deterministic concrete fallback selected for the 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 SessionFusionRouteFailedData { + /// Identifier of the routing attempt that failed. + pub attempt_id: String, + /// Provider or validation error detail, when available. + #[serde(skip_serializing_if = "Option::is_none")] + pub error_message: Option, + /// Concrete model selected as the deterministic fallback. + pub fallback_model: String, + /// HydraFusion routing policy requested for the turn. + pub policy: String, + /// Stable machine-readable reason for the routing failure. + pub reason: String, + /// Elapsed routing time in milliseconds before the failure. + #[serde(skip_serializing_if = "Option::is_none")] + pub routing_latency_ms: Option, + /// Synthetic HydraFusion model selected for the session. + pub synthetic_model: String, +} + +/// Durable server recommendation for subsequent HydraFusion turns. +/// +///
+/// +/// **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 FusionFollowUpRecommendation { + /// Recommended routing action for the next compaction turn. + pub compaction_turn: FusionFollowUpAction, + /// Recommended routing action for the next user-message turn. + pub user_turn: FusionFollowUpAction, +} + +/// Presentation-neutral phase planned for a HydraFusion 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 FusionPhasePlanStep { + /// Whether the phase executes only when an earlier phase requests it. + pub conditional: bool, + /// Kind of phase that may execute. + pub kind: FusionPhaseKind, + /// Semantic role assigned to the phase. + pub role: String, + /// Conversation scope in which the phase executes. + pub scope: FusionConversationScope, +} + +/// Validated HydraFusion routing capability scores. +/// +///
+/// +/// **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 FusionScores { + /// Code-generation capability score returned by the authenticated router. + pub code_gen: f64, + /// Debugging capability score returned by the authenticated router. + pub debugging: f64, + /// Reasoning capability score returned by the authenticated router. + pub reasoning: f64, + /// Tool-use capability score returned by the authenticated router. + pub tool_use: f64, +} + +/// Session event "session.fusion_resolved". Experimental durable validated HydraFusion route and turn 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 SessionFusionResolvedData { + /// Version of the validated HydraFusion event contract. + pub contract_version: i64, + /// Concrete model used when the planned primary model cannot execute. + pub fallback_model: String, + /// Router recommendation controlling reuse or rerouting on later turns. + #[serde(skip_serializing_if = "Option::is_none")] + pub follow_up: Option, + /// Concrete model recommended for eligible follow-up turns. + pub follow_up_model: String, + /// Stable identifier for the resolved HydraFusion turn. + pub fusion_id: String, + /// Version of the executable model universe used for selection. + #[serde(skip_serializing_if = "Option::is_none")] + pub model_universe_version: Option, + /// Validated orchestration pattern selected for the turn. + pub pattern: FusionPattern, + /// Presentation-neutral phase plan for clients that render workflow progress. + /// + ///
+ /// + /// **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 phase_plan: Option>, + /// Version of the validated execution-plan format. + #[serde(skip_serializing_if = "Option::is_none")] + pub plan_version: Option, + /// HydraFusion routing policy used to resolve the plan. + pub policy: String, + /// Version of the local routing policy. + #[serde(skip_serializing_if = "Option::is_none")] + pub policy_version: Option, + /// Concrete model selected for the primary solver phase. + pub primary_model: String, + /// Router implementation that supplied the plan. + #[serde(skip_serializing_if = "Option::is_none")] + pub route_source: Option, + /// Elapsed time in milliseconds required to resolve and validate the route. + #[serde(skip_serializing_if = "Option::is_none")] + pub routing_latency_ms: Option, + /// Identifier of the local policy rule that matched. + #[serde(skip_serializing_if = "Option::is_none")] + pub rule_id: Option, + /// Zero-based index of the local policy rule that matched. + #[serde(skip_serializing_if = "Option::is_none")] + pub rule_index: Option, + /// Human-readable name of the local policy rule that matched. + #[serde(skip_serializing_if = "Option::is_none")] + pub rule_name: Option, + /// Validated capability scores used to select the route. + #[serde(skip_serializing_if = "Option::is_none")] + pub scores: Option, + /// Concrete model selected for the review or judge phase, when required. + pub secondary_model: Option, + /// Synthetic HydraFusion model selected for the session. + pub synthetic_model: String, + /// Identifier of the session turn associated with the route. + pub turn_id: String, +} + +/// Session event "session.fusion_completed". Experimental durable aggregate outcome of a HydraFusion 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 SessionFusionCompletedData { + /// Total cached input tokens reported across all phases. + pub cached_tokens: i64, + /// Total tokens written to prompt cache across all phases. + #[serde(skip_serializing_if = "Option::is_none")] + pub cache_write_tokens: Option, + /// Idempotency identifier for the authoritative final commit. + pub commit_id: String, + /// Reason the turn used a degraded route, when applicable. + pub degraded_reason: Option, + /// Total elapsed execution time for the HydraFusion turn in milliseconds. + pub duration_ms: f64, + /// Concrete model that supplied the authoritative final content. + pub final_source_model: Option, + /// Phase whose output supplied the authoritative final content. + pub final_source_phase_id: Option, + /// Concrete model recommended for eligible follow-up turns. + pub follow_up_model: String, + /// Stable identifier for the completed HydraFusion turn. + pub fusion_id: String, + /// Total input tokens consumed across all phases. + pub input_tokens: i64, + /// Stable aggregate outcome of the HydraFusion turn. + pub outcome: String, + /// Total output tokens produced across all phases. + pub output_tokens: i64, + /// HydraFusion orchestration pattern executed for the turn. + pub pattern: FusionPattern, + /// Number of concrete phases attempted by the turn. + pub phase_count: i64, + /// Total concrete model requests made across all phases. + pub request_count: i64, + /// Synthetic HydraFusion model selected for the session. + pub synthetic_model: String, + /// Total normalized AI-unit cost reported across all phases, in nano-AIU. + pub total_nano_aiu: f64, + /// Identifier of the session turn associated with the completion. + pub turn_id: String, +} + +/// Session event "user.message". Payload of `user.message` with displayed and model-transformed content, attachments, source/delivery metadata, mode, and telemetry IDs. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UserMessageData { + /// The agent mode that was active when this message was sent + #[serde(skip_serializing_if = "Option::is_none")] + pub agent_mode: Option, + /// Files, selections, or GitHub references attached to the message + #[serde(skip_serializing_if = "Option::is_none")] + pub attachments: Option>, + /// The user's message text as displayed in the timeline pub content: String, /// 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. #[serde(skip_serializing_if = "Option::is_none")] @@ -1573,6 +2179,9 @@ pub struct UserMessageData { /// True when this user message was auto-injected by autopilot's continuation loop rather than typed by the user; used to distinguish autopilot-driven turns in telemetry. #[serde(skip_serializing_if = "Option::is_none")] pub is_autopilot_continuation: Option, + /// Stable identity of the logical user message, matching the ID returned by send and retained by pending queue snapshots + #[serde(skip_serializing_if = "Option::is_none")] + pub message_id: Option, /// 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 #[serde(skip_serializing_if = "Option::is_none")] pub native_document_path_fallback_paths: Option>, @@ -1588,6 +2197,9 @@ pub struct UserMessageData { /// Transformed version of the message sent to the model, with XML wrapping, timestamps, and other augmentations for prompt caching #[serde(skip_serializing_if = "Option::is_none")] pub transformed_content: Option, + /// The agent-loop turn ID that consumed this message; absent when no agent-loop turn consumed it + #[serde(skip_serializing_if = "Option::is_none")] + pub turn_id: Option, } /// Session event "pending_messages.modified". Empty payload; the event signals that the pending message queue has changed @@ -1623,6 +2235,48 @@ pub struct AssistantTurnRetryData { pub turn_id: String, } +/// Session event "agent.interrupted". Metadata for work the user interrupted while the agent was running +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AgentInterruptedData { + /// What the agent was doing when the user interrupted it + pub activity: AgentInterruptedActivity, + /// For an interrupted model call: the provider endpoint the request targeted + #[serde(skip_serializing_if = "Option::is_none")] + pub api_endpoint: Option, + /// For an interrupted model call: whether the user interrupted before any token arrived or while the response was streaming + #[serde(skip_serializing_if = "Option::is_none")] + pub cancel_phase: Option, + /// How long the interrupted work had been running, in milliseconds + pub elapsed_ms: f64, + /// For an interrupted background-agent batch: how many background sub-agents the stop swept. Counts accepted cancellations, so an agent cancelled as a cascade of its interrupted parent is covered by that parent rather than counted again. + #[serde(skip_serializing_if = "Option::is_none")] + pub interrupted_agent_count: Option, + /// For an interrupted model call: the model the request targeted + #[serde(skip_serializing_if = "Option::is_none")] + pub model: Option, + /// For a mid-stream interrupt: the observed time to first observable output, in milliseconds. Deliberately distinct from the `ttftMs` reported on a successful model call, which measures time to first stream event. + #[serde(skip_serializing_if = "Option::is_none")] + pub output_ttft_ms: Option, + /// For an interrupted model call: the reasoning effort the request asked for + #[serde(skip_serializing_if = "Option::is_none")] + pub reasoning_effort: Option, + /// Subset of `toolNames` whose tool metadata marks the tool name as safe to record unhashed in telemetry. + #[serde(skip_serializing_if = "Option::is_none")] + pub safe_tool_names: Option>, + /// Tool call identifiers that were still running + #[serde(skip_serializing_if = "Option::is_none")] + pub tool_call_ids: Option>, + /// Names of the tools that were still running. More than one when the model requested a parallel fan-out. + #[serde(skip_serializing_if = "Option::is_none")] + pub tool_names: Option>, + /// For an interrupted model call: the transport the request used + #[serde(skip_serializing_if = "Option::is_none")] + pub transport: Option, + /// Zero-based agentic-loop iteration the interrupt landed in + pub turn: i64, +} + /// Session event "assistant.intent". Agent intent description for current activity or plan #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -1631,6 +2285,196 @@ pub struct AssistantIntentData { pub intent: String, } +/// Session event "assistant.fusion_phase_started". Experimental transient HydraFusion phase/model/role signal. +/// +///
+/// +/// **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 AssistantFusionPhaseStartedData { + /// Conversation scope in which the phase executes. + pub conversation_scope: FusionConversationScope, + /// Identifier of the HydraFusion turn containing the phase. + pub fusion_id: String, + /// Concrete model executing the phase. + pub model: String, + /// HydraFusion orchestration pattern containing the phase. + pub pattern: FusionPattern, + /// Stable identifier for the concrete phase. + pub phase_id: String, + /// Kind of phase being executed. + pub phase_kind: FusionPhaseKind, + /// Semantic role assigned to the phase. + pub role: String, +} + +/// Session event "assistant.fusion_phase_activity". Experimental content-safe activity signal for a running HydraFusion 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, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AssistantFusionPhaseActivityData { + /// Kind of real activity observed. + pub activity: FusionPhaseActivityKind, + /// Conversation scope in which the phase executes. + pub conversation_scope: FusionConversationScope, + /// Identifier of the HydraFusion turn containing the phase. + pub fusion_id: String, + /// HydraFusion orchestration pattern containing the phase. + pub pattern: FusionPattern, + /// Stable identifier for the concrete phase. + pub phase_id: String, + /// Kind of phase currently executing. + pub phase_kind: FusionPhaseKind, + /// Semantic role assigned to the phase. + pub role: String, + /// Opaque hashed correlation token for matching tool-started and tool-completed activity within this Fusion activity stream. It is not the tool call identifier exposed by tool lifecycle events. + #[serde(skip_serializing_if = "Option::is_none")] + pub tool_call_id: Option, + /// Cumulative private response bytes observed for this model call. The event never includes response text. + #[serde(skip_serializing_if = "Option::is_none")] + pub total_response_size_bytes: Option, +} + +/// Internal durable terminal request staged by a HydraFusion phase until an idempotent final commit selects it. +/// +///
+/// +/// **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 FusionStagedTerminal { + pub arguments: String, + pub assistant_message: serde_json::Value, + pub phase_id: String, + pub tool_call_id: String, + pub tool_name: String, +} + +/// Aggregate concrete-model usage for one HydraFusion 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, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct FusionPhaseUsage { + /// Total cached input tokens reported for the phase. + pub cached_tokens: i64, + /// Total tokens written to prompt cache during the phase. + #[serde(skip_serializing_if = "Option::is_none")] + pub cache_write_tokens: Option, + /// Total input tokens consumed by the phase. + pub input_tokens: i64, + /// Total output tokens produced by the phase. + pub output_tokens: i64, + /// Number of concrete model requests made by the phase. + pub request_count: i64, + /// Total normalized AI-unit cost reported for the phase, in nano-AIU. + pub total_nano_aiu: f64, +} + +/// Session event "assistant.fusion_phase_completed". Experimental durable HydraFusion phase output and lossless replay checkpoint. +/// +///
+/// +/// **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 AssistantFusionPhaseCompletedData { + /// Provider-normalized textual output produced by the phase. + pub content: String, + /// Conversation scope in which the phase executed. + pub conversation_scope: FusionConversationScope, + /// Elapsed execution time for the phase in milliseconds. + pub duration_ms: f64, + /// Identifier of the HydraFusion turn containing the phase. + pub fusion_id: String, + /// Concrete model that executed the phase. + pub model: String, + /// Stable identifier for the completed phase. + pub phase_id: String, + /// Kind of phase that completed. + pub phase_kind: FusionPhaseKind, + /// Exact provider-normalized message used to reconstruct canonical model history. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) projection_message: Option, + /// Projection action for the exact internal message. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) projection_mode: Option, + /// Semantic role assigned to the completed phase. + pub role: String, + /// Terminal request held outside canonical state until selected by the final commit. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) staged_terminal: Option, + /// Durable outcome status of the phase. + pub status: FusionPhaseStatus, + /// Aggregate concrete-model usage consumed by the phase. + pub usage: FusionPhaseUsage, + /// Structured judge or critic verdict, when the phase produces one. + pub verdict: Option, +} + +/// Session event "assistant.fusion_phase_failed". Experimental durable typed HydraFusion phase failure and degradation transition. +/// +///
+/// +/// **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 AssistantFusionPhaseFailedData { + /// Conversation scope in which the phase executed. + pub conversation_scope: FusionConversationScope, + /// Identifier of the fallback phase used to continue the turn after degradation. + #[serde(skip_serializing_if = "Option::is_none")] + pub degraded_to_phase_id: Option, + /// Elapsed execution time before the phase failed, in milliseconds. + pub duration_ms: f64, + /// Provider or execution error detail, when available. + #[serde(skip_serializing_if = "Option::is_none")] + pub error_message: Option, + /// Identifier of the HydraFusion turn containing the phase. + pub fusion_id: String, + /// Concrete model that attempted the phase. + pub model: String, + /// Stable identifier for the failed phase. + pub phase_id: String, + /// Kind of phase that failed. + pub phase_kind: FusionPhaseKind, + /// Stable machine-readable reason for the phase failure. + pub reason: String, + /// Semantic role assigned to the failed phase. + pub role: String, + /// Durable outcome status of the phase. + pub status: FusionPhaseStatus, + /// Aggregate concrete-model usage consumed before the failure. + pub usage: FusionPhaseUsage, +} + /// Session event "assistant.server_tool_progress". Live progress signal for a provider-hosted server tool (e.g. hosted web search) while it runs, before the finalized serverTools envelope lands on the terminal assistant.message #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -1651,6 +2495,7 @@ pub struct AssistantReasoningData { pub content: String, /// Unique identifier for this reasoning block pub reasoning_id: String, + /// Per-request treatment/eligibility signal returned by the Copilot API in the `X-GitHub-Copilot-Request-TE` response header for the associated model call; `false` when the header was absent or unparseable. #[serde(skip_serializing_if = "Option::is_none")] pub rte: Option, } @@ -1775,6 +2620,66 @@ pub struct Citations { pub spans: Vec, } +/// Experimental attribution linking an ordinary event to the HydraFusion turn, phase, and concrete source that produced it. +/// +///
+/// +/// **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 FusionAttribution { + /// Idempotency identifier for the authoritative commit, when the event belongs to the selected output. + #[serde(skip_serializing_if = "Option::is_none")] + pub commit_id: Option, + /// Conversation scope in which the concrete phase executed. + #[serde(skip_serializing_if = "Option::is_none")] + pub conversation_scope: Option, + /// Stable identifier for the HydraFusion turn that produced the event. + pub fusion_id: String, + /// HydraFusion orchestration pattern selected for the turn. + pub pattern: String, + /// Identifier of the concrete phase that produced the event. + #[serde(skip_serializing_if = "Option::is_none")] + pub phase_id: Option, + /// Kind of concrete phase that produced the event. + #[serde(skip_serializing_if = "Option::is_none")] + pub phase_kind: Option, + /// HydraFusion routing policy used for the turn. + pub policy: String, + /// Semantic role assigned to the concrete phase. + #[serde(skip_serializing_if = "Option::is_none")] + pub role: Option, + /// Concrete model that produced the attributed event. + #[serde(skip_serializing_if = "Option::is_none")] + pub source_model: Option, + /// Phase whose output supplied the authoritative content, when different from the executing phase. + #[serde(skip_serializing_if = "Option::is_none")] + pub source_phase_id: Option, + /// Synthetic HydraFusion model selected for the session. + pub synthetic_model: String, +} + +/// Neutral provider-tagged reasoning content blocks preserved verbatim for round-tripping +/// +///
+/// +/// **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 AssistantMessageReasoningBlocks { + /// Provider-native reasoning items or content blocks preserved verbatim, in order. A single response can carry several, and provider signatures or identifiers may depend on their exact content and ordering. + #[serde(skip_serializing_if = "Option::is_none")] + pub blocks: Option>, + /// Model provider that produced these reasoning blocks. + pub provider: String, +} + /// Neutral provider-tagged server-side tool-use payload (tool search, advisor) for verbatim round-tripping /// ///
@@ -1786,17 +2691,32 @@ pub struct Citations { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AssistantMessageServerTools { + /// Advisor model identifier associated with the server-tool payload. #[serde(skip_serializing_if = "Option::is_none")] pub advisor_model: Option, + /// Provider function-call namespaces keyed by function-call identifier. #[serde(skip_serializing_if = "Option::is_none")] pub function_call_namespaces: Option>, + /// Provider-native server-tool call and output items preserved verbatim for replay. #[serde(skip_serializing_if = "Option::is_none")] pub items: Option>, + /// Model provider that produced this server-tool payload. pub provider: String, + /// Raw provider content blocks retained for verbatim round-tripping. #[serde(skip_serializing_if = "Option::is_none")] pub raw_content_blocks: Option>, } +/// Hosted program that requested this client tool call +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AssistantMessageToolRequestCaller { + /// Provider-assigned identifier for the hosted caller. + pub caller_id: String, + /// Kind of hosted caller that requested the client tool call. + pub r#type: AssistantMessageToolRequestCallerType, +} + /// A tool invocation request from the assistant #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -1804,6 +2724,9 @@ pub struct AssistantMessageToolRequest { /// Arguments to pass to the tool, format depends on the tool #[serde(skip_serializing_if = "Option::is_none")] pub arguments: Option, + /// Hosted program that requested this client tool call + #[serde(skip_serializing_if = "Option::is_none")] + pub caller: Option, /// Resolved intention summary describing what this specific call does #[serde(skip_serializing_if = "Option::is_none")] pub intention_summary: Option, @@ -1856,6 +2779,16 @@ pub struct AssistantMessageData { /// Encrypted reasoning content from OpenAI models. Session-bound and stripped on resume. #[serde(skip_serializing_if = "Option::is_none")] pub encrypted_content: Option, + /// Experimental HydraFusion source attribution for this ordinary authoritative assistant message. + /// + ///
+ /// + /// **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 fusion: Option, /// CAPI interaction ID for correlating this message with upstream telemetry #[serde(skip_serializing_if = "Option::is_none")] pub interaction_id: Option, @@ -1875,6 +2808,9 @@ pub struct AssistantMessageData { /// Generation phase for phased-output models (e.g., thinking vs. response phases) #[serde(skip_serializing_if = "Option::is_none")] pub phase: Option, + /// Neutral provider-tagged reasoning content blocks preserved verbatim for round-tripping. `reasoningText` and `reasoningOpaque` are a lossy derived view of these blocks, retained for display. + #[serde(skip_serializing_if = "Option::is_none")] + pub reasoning_blocks: Option, /// Opaque/encrypted extended thinking data from Anthropic models. Session-bound and stripped on resume. #[serde(skip_serializing_if = "Option::is_none")] pub reasoning_opaque: Option, @@ -1887,6 +2823,7 @@ 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, + /// Per-request treatment/eligibility signal returned by the Copilot API in the `X-GitHub-Copilot-Request-TE` response header for the associated model call; `false` when the header was absent or unparseable. #[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 @@ -2022,6 +2959,9 @@ pub(crate) struct AssistantUsageQuotaSnapshot { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AssistantUsageData { + /// Number of accepted speculative prediction tokens + #[serde(skip_serializing_if = "Option::is_none")] + pub accepted_prediction_tokens: Option, /// Completion ID from the model provider (e.g., chatcmpl-abc123) #[serde(skip_serializing_if = "Option::is_none")] pub api_call_id: Option, @@ -2032,12 +2972,20 @@ pub struct AssistantUsageData { #[doc(hidden)] #[serde(skip_serializing_if = "Option::is_none")] pub(crate) available_tool_count: Option, + /// Whether the provider reported prompt-cache usage details for this call + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) cache_details_reported: 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, /// Number of tokens read from prompt cache #[serde(skip_serializing_if = "Option::is_none")] pub cache_read_tokens: Option, + /// Effective prompt-cache lifetime in seconds for this call + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) cache_ttl_seconds: Option, /// Number of tokens written to prompt cache #[serde(skip_serializing_if = "Option::is_none")] pub cache_write_tokens: Option, @@ -2063,6 +3011,20 @@ pub struct AssistantUsageData { /// 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". #[serde(skip_serializing_if = "Option::is_none")] pub finish_reason: Option, + /// How the prompt-cache frontier was determined for this call + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) frontier_source: Option, + /// Experimental HydraFusion attribution for this concrete model call's usage. + /// + ///
+ /// + /// **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 fusion: Option, /// What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls #[serde(skip_serializing_if = "Option::is_none")] pub initiator: Option, @@ -2075,6 +3037,18 @@ pub struct AssistantUsageData { /// Average inter-token latency in milliseconds. Only available for streaming requests #[serde(skip_serializing_if = "Option::is_none")] pub inter_token_latency_ms: Option, + /// Whether Auto mode was selected for this model call + #[serde(skip_serializing_if = "Option::is_none")] + pub is_auto: Option, + /// Whether this model call used a bring-your-own-key provider + #[serde(skip_serializing_if = "Option::is_none")] + pub is_byok: Option, + /// Requested maximum output tokens used for this model call + #[serde(skip_serializing_if = "Option::is_none")] + pub max_output_tokens: Option, + /// Effective maximum prompt-token limit used for this model call + #[serde(skip_serializing_if = "Option::is_none")] + pub max_prompt_tokens: Option, /// Model identifier used for this API call pub model: String, /// Number of tool calls returned by the model @@ -2084,6 +3058,9 @@ pub struct AssistantUsageData { /// Number of output tokens produced #[serde(skip_serializing_if = "Option::is_none")] pub output_tokens: Option, + /// Time to first observable model output in milliseconds. Includes text, reasoning, and tool-call output; only available for streaming requests that produce observable output. + #[serde(skip_serializing_if = "Option::is_none")] + pub output_ttft_ms: Option, /// Parent tool call ID when this usage originates from a sub-agent #[doc(hidden)] #[deprecated] @@ -2099,25 +3076,121 @@ pub struct AssistantUsageData { /// Reasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh", "max") #[serde(skip_serializing_if = "Option::is_none")] pub reasoning_effort: Option, + /// Reasoning summary mode used for this model call, if applicable + #[serde(skip_serializing_if = "Option::is_none")] + pub reasoning_summary: Option, /// Number of output tokens used for reasoning (e.g., chain-of-thought) #[serde(skip_serializing_if = "Option::is_none")] pub reasoning_tokens: Option, + /// Number of rejected speculative prediction tokens + #[serde(skip_serializing_if = "Option::is_none")] + pub rejected_prediction_tokens: Option, + /// Per-request treatment/eligibility signal returned by the Copilot API in the `X-GitHub-Copilot-Request-TE` response header for the associated model call; `false` when the header was absent or unparseable. + #[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, + /// Transport used for this model call (http or websocket) + #[serde(skip_serializing_if = "Option::is_none")] + pub transport: Option, +} + +/// Session event "prompt_cache_break". A detected loss of a previously cached prompt prefix +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PromptCacheBreakData { + /// Request state whose cached prefix fell short + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) after_request: Option, + /// Name of the sub-agent whose conversation broke, stamped by the parent bridge + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) agent_name: Option, + /// Request state that established the prior cache frontier + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) before_request: Option, + /// Names of the cache-configuration fields that changed + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) cache_config_changed_fields: Option>, + /// All reasons that contributed to the cache break, ordered by precedence + pub contributing_reasons: Vec, + /// Prior cached prompt frontier in tokens + pub frontier_tokens: i64, + /// Model that held the prior cache frontier, when the call changed models + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) model_from: Option, + /// Model this call targeted, when the call changed models + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) model_to: Option, + /// The highest-precedence reason for the cache break + pub primary_reason: String, + /// Fraction of the prior cache frontier that survived + pub retention_ratio: f64, + /// Index of the first conversation message whose content changed + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) rewrite_message_index: Option, + /// Shape of the history rewrite, for example whether the history grew or shrank + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) rewrite_shape: Option, + /// Subsystems that announced a history rewrite before this call, for example compaction or truncation + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) rewrite_source: Option>, + /// Cached prefix tokens lost since the prior call + pub shortfall_tokens: i64, + /// Number of cached prefix tokens that survived + pub survived_tokens: i64, + /// Names of the system-prompt segments whose content changed + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) system_segments_changed: Option>, + /// Telemetry-safe names of tools added since the prior call + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) tools_added: Option>, + /// Raw names of tools added since the prior call, restricted because a tool name can be user-authored + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) tools_added_raw: Option>, + /// Telemetry-safe names of tools whose definition changed since the prior call + #[doc(hidden)] #[serde(skip_serializing_if = "Option::is_none")] - pub rte: Option, - /// Copilot service request ID (x-copilot-service-request-id header) for CAPI log correlation + pub(crate) tools_redefined: Option>, + /// Raw names of tools redefined since the prior call, restricted because a tool name can be user-authored + #[doc(hidden)] #[serde(skip_serializing_if = "Option::is_none")] - pub service_request_id: Option, - /// Time to first token in milliseconds. Only available for streaming requests + pub(crate) tools_redefined_raw: Option>, + /// Telemetry-safe names of tools removed since the prior call + #[doc(hidden)] #[serde(skip_serializing_if = "Option::is_none")] - pub time_to_first_token_ms: Option, - /// Tool-call counts keyed by tool name + pub(crate) tools_removed: Option>, + /// Raw names of tools removed since the prior call, restricted because a tool name can be user-authored #[doc(hidden)] #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) tool_counts: Option>, - /// Number of tokens used by tool definitions for this call + pub(crate) tools_removed_raw: Option>, + /// Whether the tool list kept its members but changed their order #[doc(hidden)] #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) tool_token_count: Option, + pub(crate) tools_reordered: Option, } /// Content-free structural summary of the failing request for diagnosing malformed 4xx calls @@ -2169,9 +3242,22 @@ pub struct ModelCallFailureData { /// Whether the failure originated from an API response or the request transport #[serde(skip_serializing_if = "Option::is_none")] pub failure_kind: Option, + /// Experimental HydraFusion attribution for this failed concrete model call. + /// + ///
+ /// + /// **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 fusion: Option, /// What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls #[serde(skip_serializing_if = "Option::is_none")] pub initiator: Option, + /// Authoritative interaction classification for the failed call, matching `assistant.usage.interactionType` (for example `conversation-agent`, `conversation-subagent`, or `conversation-sampling`). Absent when the producer cannot classify the interaction. + #[serde(skip_serializing_if = "Option::is_none")] + pub interaction_type: Option, /// Whether the session selected Auto mode for the failed call #[serde(skip_serializing_if = "Option::is_none")] pub is_auto: Option, @@ -2200,6 +3286,7 @@ 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, + /// Per-request treatment/eligibility signal returned by the Copilot API in the `X-GitHub-Copilot-Request-TE` response header for the associated model call; `false` when the header was absent or unparseable. #[serde(skip_serializing_if = "Option::is_none")] pub rte: Option, /// Copilot service request ID (x-copilot-service-request-id header) for CAPI log correlation @@ -2215,10 +3302,40 @@ pub struct ModelCallFailureData { pub transport: Option, } +/// Session event "model.call_finished". Final lifecycle outcome for one logical model dispatch. A logical dispatch may include internal reconnect or fallback work, so event count is not provider HTTP-request count. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ModelCallFinishedData { + /// Whether an accepted successful response requested the exact name and command semantics of a built-in file edit tool, including an external tool explicitly replacing that built-in name. Absent when the logical dispatch did not produce an accepted response. + #[serde(skip_serializing_if = "Option::is_none")] + pub contains_built_in_file_edit_request: Option, + /// Monotonic elapsed time spent in the logical model dispatch, including any internal transport reconnect or fallback and excluding orchestrator retry backoff, tool execution, confirmations, and post-response processing + pub dispatch_duration_ms: f64, + /// Version of the built-in file-edit semantic classifier used for this event + pub edit_classifier_version: i64, + /// Identifier of the user interaction that owns the model dispatch, matching assistant.turn_start.interactionId when available + #[serde(skip_serializing_if = "Option::is_none")] + pub interaction_id: Option, + /// Final outcome after post-response acceptance processing + pub outcome: ModelCallFinishedOutcome, + /// Agent-loop iteration within the interaction that initiated the model dispatch + pub turn_id: String, +} + /// Session event "model.call_start". Model API dispatch metadata for internal telemetry #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ModelCallStartData { + /// Experimental HydraFusion attribution for this concrete model call. + /// + ///
+ /// + /// **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 fusion: Option, /// Model identifier used for this API call, when known #[serde(skip_serializing_if = "Option::is_none")] pub model: Option, @@ -2316,6 +3433,16 @@ pub struct ToolExecutionStartData { /// When true, the tool output should be displayed expanded (verbatim) in the CLI timeline #[serde(skip_serializing_if = "Option::is_none")] pub display_verbatim: Option, + /// Experimental HydraFusion attribution for this tool execution. + /// + ///
+ /// + /// **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 fusion: Option, /// Name of the MCP server hosting this tool, when the tool is an MCP tool #[serde(skip_serializing_if = "Option::is_none")] pub mcp_server_name: Option, @@ -2330,6 +3457,7 @@ pub struct ToolExecutionStartData { #[deprecated] #[serde(skip_serializing_if = "Option::is_none")] pub parent_tool_call_id: Option, + /// Per-request treatment/eligibility signal returned by the Copilot API in the `X-GitHub-Copilot-Request-TE` response header for the associated model call; `false` when the header was absent or unparseable. #[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. @@ -2378,6 +3506,78 @@ pub struct ToolExecutionCompleteError { pub message: String, } +/// Binary result returned by a tool for the model +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PersistedBinaryImage { + /// Base64-encoded binary data + pub data: String, + /// Human-readable description of the binary data + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + /// Optional metadata from the producing tool. + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option>, + /// MIME type of the binary data + pub mime_type: String, + /// Binary result type discriminator. Use "image" for images and "resource" for other binary data. + pub r#type: PersistedBinaryImageType, +} + +/// A binary result whose data was omitted from persistence due to the inline size limit +/// +///
+/// +/// **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 OmittedBinaryResult { + /// Decoded byte length of the omitted binary data + pub byte_length: i64, + /// Human-readable description of the binary data + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + /// Optional metadata from the producing tool. + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option>, + /// MIME type of the omitted binary data + pub mime_type: String, + /// Why the binary data is absent: it exceeded the inline size limit, or its asset was unavailable + pub omitted_reason: OmittedBinaryOmittedReason, + /// Binary result type discriminator. Use "image" for images and "resource" for other binary data. + pub r#type: OmittedBinaryType, +} + +/// A reference to binary data persisted once on a session.binary_asset event and shared 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 BinaryAssetReference { + /// Content-addressed id of the session.binary_asset event that holds this binary's bytes (e.g. "sha256:..."). + pub asset_id: String, + /// Decoded byte length of the referenced binary data + pub byte_length: i64, + /// Human-readable description of the binary data + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + /// Optional metadata from the producing tool. + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option>, + /// MIME type of the referenced binary data + pub mime_type: String, + /// Binary result type discriminator. Use "image" for images and "resource" for other binary data. + pub r#type: BinaryAssetReferenceType, +} + /// A source supplied by a tool that should be made available to the model as citable content. /// ///
@@ -2441,6 +3641,9 @@ pub struct ToolExecutionCompleteContentShellExit { pub cwd: Option, /// Exit code from the completed shell command pub exit_code: i64, + /// Path reported in the shell session's filesystem namespace when shell output exceeded the configured large-output threshold. + #[serde(skip_serializing_if = "Option::is_none")] + pub output_file_path: Option, /// Output associated with this shell command, if available. May be partial, truncated, or a preview; not guaranteed to be full output. #[serde(skip_serializing_if = "Option::is_none")] pub output_preview: Option, @@ -2561,12 +3764,16 @@ pub struct ToolExecutionCompleteContentResource { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ToolExecutionCompleteUIResourceMetaUICsp { + /// Domains the UI resource may use as document base URIs. #[serde(skip_serializing_if = "Option::is_none")] pub base_uri_domains: Option>, + /// Domains the UI resource may connect to. #[serde(skip_serializing_if = "Option::is_none")] pub connect_domains: Option>, + /// Domains the UI resource may embed as nested frames. #[serde(skip_serializing_if = "Option::is_none")] pub frame_domains: Option>, + /// Domains from which the UI resource may load scripts, styles, images, and other resources. #[serde(skip_serializing_if = "Option::is_none")] pub resource_domains: Option>, } @@ -2616,11 +3823,13 @@ pub struct ToolExecutionCompleteUIResourceMetaUI { /// CSP domain allowlists for an MCP Apps UI resource, including connect, resource, frame, and base URI domains. #[serde(skip_serializing_if = "Option::is_none")] pub csp: Option, + /// Optional dedicated origin for the rendered MCP Apps UI resource. #[serde(skip_serializing_if = "Option::is_none")] pub domain: Option, /// Browser permission metadata for an MCP Apps UI resource, including camera, microphone, geolocation, and clipboard-write. #[serde(skip_serializing_if = "Option::is_none")] pub permissions: Option, + /// Whether the host should render a border around the MCP Apps UI resource. #[serde(skip_serializing_if = "Option::is_none")] pub prefers_border: Option, } @@ -2666,7 +3875,7 @@ pub struct ToolExecutionCompleteResult { /// ///
#[serde(skip_serializing_if = "Option::is_none")] - pub binary_results_for_llm: Option>, + pub binary_results_for_llm: Option>, /// Provider-neutral source material this tool makes available to the model as citable content. Persisted so it survives session resume. Experimental. /// ///
@@ -2745,6 +3954,16 @@ pub struct ToolExecutionCompleteData { /// Error details when the tool execution failed #[serde(skip_serializing_if = "Option::is_none")] pub error: Option, + /// Experimental HydraFusion attribution for this tool completion. + /// + ///
+ /// + /// **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 fusion: Option, /// CAPI interaction ID for correlating this tool execution with upstream telemetry #[serde(skip_serializing_if = "Option::is_none")] pub interaction_id: Option, @@ -2772,6 +3991,7 @@ pub struct ToolExecutionCompleteData { /// Tool execution result on success #[serde(skip_serializing_if = "Option::is_none")] pub result: Option, + /// Per-request treatment/eligibility signal returned by the Copilot API in the `X-GitHub-Copilot-Request-TE` response header for the associated model call; `false` when the header was absent or unparseable. #[serde(skip_serializing_if = "Option::is_none")] pub rte: Option, /// Whether this tool execution ran inside a sandbox container @@ -2814,12 +4034,15 @@ pub struct SkillInvokedData { /// Description of the skill from its SKILL.md frontmatter #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, + /// Whether model invocation is disabled for this skill + #[serde(skip_serializing_if = "Option::is_none")] + pub disable_model_invocation: Option, /// Model identifier active when the skill was invoked, when known #[serde(skip_serializing_if = "Option::is_none")] pub model: Option, /// Name of the invoked skill pub name: String, - /// File path to the SKILL.md definition + /// File path to the SKILL.md definition, or an empty string for an SDK-provided skill without a filesystem identity pub path: String, /// Name of the plugin this skill originated from, when applicable #[serde(skip_serializing_if = "Option::is_none")] @@ -2827,7 +4050,7 @@ pub struct SkillInvokedData { /// Version of the plugin this skill originated from, when applicable #[serde(skip_serializing_if = "Option::is_none")] pub plugin_version: Option, - /// Source identifier for where the skill was discovered. Known values include: project (workspace skill), inherited (parent-directory skill), personal-copilot (~/.copilot/skills), personal-agents (~/.agents/skills), custom (configured directory), plugin (installed plugin), builtin (bundled runtime skill), and remote (org/enterprise skill) + /// Source identifier for where the skill was discovered. Known values include: project (workspace skill), inherited (parent-directory skill), personal-copilot (~/.copilot/skills), personal-agents (~/.agents/skills), custom (configured directory), plugin (installed plugin), builtin (bundled runtime skill), remote (org/enterprise skill), and sdk (SDK-provided skill) #[serde(skip_serializing_if = "Option::is_none")] pub source: Option, /// What triggered the skill invocation: `user-invoked` (explicit user action, such as via a slash command or UI affordance), `agent-invoked` (agent requested the skill), or `context-load` (loaded as part of another context, such as preloading skills configured on a custom agent or subagent) @@ -2835,6 +4058,11 @@ pub struct SkillInvokedData { pub trigger: Option, } +/// Session event "sandbox.decision". Payload of `sandbox.decision`, a bounded governance record of what the process sandbox was configured to do and whether it took effect. Discriminated by `kind`. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SandboxDecisionData {} + /// Session event "subagent.started". Sub-agent startup details including parent tool call and agent information #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -2845,13 +4073,44 @@ pub struct SubagentStartedData { pub agent_display_name: String, /// Internal name of the sub-agent pub agent_name: String, + /// Type of the sub-agent selected at spawn time. + #[serde(skip_serializing_if = "Option::is_none")] + pub agent_type: Option, + /// Whether the sub-agent runs synchronously or in the background. + #[serde(skip_serializing_if = "Option::is_none")] + pub execution_mode: Option, + /// Root id of the factory run that spawned this sub-agent, when it was spawned by one. + #[serde(skip_serializing_if = "Option::is_none")] + pub factory_run_id: Option, /// Model the sub-agent will run with, when known at start. #[serde(skip_serializing_if = "Option::is_none")] pub model: Option, + /// Task-registry ID of the spawning sub-agent. Absent when the root session spawned this child. + #[serde(skip_serializing_if = "Option::is_none")] + pub parent_id: Option, + /// Whether this sub-agent can be resumed. Currently always false. + #[serde(skip_serializing_if = "Option::is_none")] + pub resumable: Option, /// Tool call ID of the parent tool invocation that spawned this sub-agent pub tool_call_id: String, } +/// Session event "subagent.configured". Resolved runtime configuration for a configured sub-agent +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SubagentConfiguredData { + /// Resolved context tier, when configured for the model + #[serde(skip_serializing_if = "Option::is_none")] + pub context_tier: Option, + /// Resolved model the sub-agent will run with + pub model: String, + /// Whether the sub-agent accepts follow-up turns + pub multi_turn: bool, + /// Resolved reasoning effort, when configured for the model + #[serde(skip_serializing_if = "Option::is_none")] + pub reasoning_effort: Option, +} + /// Session event "subagent.completed". Sub-agent completion details for successful execution #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -2860,12 +4119,33 @@ 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, + /// Whether the first model actually dispatched matched the user's configured preference + #[serde(skip_serializing_if = "Option::is_none")] + pub configured_model_matches_actual: Option, + /// Concrete model the user configured for this sub-agent via `/subagents`, when present + #[serde(skip_serializing_if = "Option::is_none")] + pub configured_model_preference: Option, /// Wall-clock duration of the sub-agent execution in milliseconds #[serde(skip_serializing_if = "Option::is_none")] pub duration_ms: Option, + /// Whether the explicit task-call model matched the user's configured preference + #[serde(skip_serializing_if = "Option::is_none")] + pub explicit_model_matches_preference: Option, + /// Explicit model supplied by the parent agent on the task call, when present + #[serde(skip_serializing_if = "Option::is_none")] + pub explicit_model_override: Option, + /// First model for which the sub-agent started an inference request, when one was dispatched + #[serde(skip_serializing_if = "Option::is_none")] + pub first_dispatched_model: Option, /// Model used by the sub-agent #[serde(skip_serializing_if = "Option::is_none")] pub model: Option, + /// Why an explicit task-call model did not become the effective model + #[serde(skip_serializing_if = "Option::is_none")] + pub model_override_reason: Option, /// Tool call ID of the parent tool invocation that spawned this sub-agent pub tool_call_id: String, /// Total tokens (input + output) consumed by the sub-agent @@ -2884,14 +4164,32 @@ pub struct SubagentFailedData { pub agent_display_name: String, /// Internal name of the sub-agent pub agent_name: String, + /// Whether the first model actually dispatched matched the user's configured preference + #[serde(skip_serializing_if = "Option::is_none")] + pub configured_model_matches_actual: Option, + /// Concrete model the user configured for this sub-agent via `/subagents`, when present + #[serde(skip_serializing_if = "Option::is_none")] + pub configured_model_preference: Option, /// Wall-clock duration of the sub-agent execution in milliseconds #[serde(skip_serializing_if = "Option::is_none")] pub duration_ms: Option, /// Error message describing why the sub-agent failed pub error: String, + /// Whether the explicit task-call model matched the user's configured preference + #[serde(skip_serializing_if = "Option::is_none")] + pub explicit_model_matches_preference: Option, + /// Explicit model supplied by the parent agent on the task call, when present + #[serde(skip_serializing_if = "Option::is_none")] + pub explicit_model_override: Option, + /// First model for which the sub-agent started an inference request, when one was dispatched + #[serde(skip_serializing_if = "Option::is_none")] + pub first_dispatched_model: Option, /// Model selected for the sub-agent, when known #[serde(skip_serializing_if = "Option::is_none")] pub model: Option, + /// Why an explicit task-call model did not become the effective model + #[serde(skip_serializing_if = "Option::is_none")] + pub model_override_reason: Option, /// Tool call ID of the parent tool invocation that spawned this sub-agent pub tool_call_id: String, /// Total tokens (input + output) consumed before the sub-agent failed @@ -2927,9 +4225,12 @@ pub struct HookStartData { pub hook_invocation_id: String, /// Type of hook being invoked (e.g., "preToolUse", "postToolUse", "sessionStart") pub hook_type: String, - /// Input data passed to the hook + /// Input data passed to the hook. For postToolUse hooks the retained copy served by session.eventLog.read (and by a resumed session) elides the tool result's inline `contents`/`uiResource` and replaces an over-long `textResultForLlm` with a `[copilot:elided ...]` marker, to keep a multi-megabyte payload out of the durable event log; the live subscription stream still delivers the full value. Read the adjacent tool.execution_complete event for the tool result itself. #[serde(skip_serializing_if = "Option::is_none")] pub input: Option, + /// Tool call ID of the parent tool invocation when this event originates from a sub-agent + #[serde(skip_serializing_if = "Option::is_none")] + pub parent_tool_call_id: Option, } /// Error details when the hook failed @@ -2960,6 +4261,9 @@ pub struct HookEndData { /// Output data produced by the hook #[serde(skip_serializing_if = "Option::is_none")] pub output: Option, + /// Tool call ID of the parent tool invocation when this event originates from a sub-agent + #[serde(skip_serializing_if = "Option::is_none")] + pub parent_tool_call_id: Option, /// Whether the hook completed successfully pub success: bool, } @@ -3173,6 +4477,16 @@ pub struct PermissionRequestMcp { /// 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, + /// Advisory runtime permission recommendation. The SDK host remains responsible for deciding the request and may reject it. + /// + ///
+ /// + /// **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 permission_recommendation: Option, /// Whether this MCP tool is read-only (no side effects) pub read_only: bool, /// Name of the MCP server providing the tool @@ -3213,6 +4527,30 @@ pub struct PermissionRequestUrl { pub url: String, } +/// Assisted-approval judge information attached to a permission request. Present only in assisted mode; its absence means the judge did not evaluate the request. The `recommendation` conveys the judge's disposition for this 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, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PermissionAssistedApproval { + /// 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, + /// The assisted-approval safety judge's outcome for this request. + pub recommendation: AssistedApprovalRecommendation, +} + /// Memory operation permission request #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -3220,6 +4558,16 @@ pub struct PermissionRequestMemory { /// Whether this is a store or vote memory operation #[serde(skip_serializing_if = "Option::is_none")] pub action: Option, + /// Assisted-approval judge information for this request; present only in assisted mode. + /// + ///
+ /// + /// **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 assisted_approval: Option, /// Source references for the stored fact (store only) #[serde(skip_serializing_if = "Option::is_none")] pub citations: Option, @@ -3236,6 +4584,12 @@ pub struct PermissionRequestMemory { /// Reason for the vote (vote only) #[serde(skip_serializing_if = "Option::is_none")] pub reason: Option, + /// Repository name with owner associated with the stored memory (store only) + #[serde(skip_serializing_if = "Option::is_none")] + pub repo_nwo: Option, + /// Scope of the stored memory (store only) + #[serde(skip_serializing_if = "Option::is_none")] + pub scope: Option, /// Topic or subject of the memory (store only) #[serde(skip_serializing_if = "Option::is_none")] pub subject: Option, @@ -3256,6 +4610,9 @@ pub struct PermissionRequestCustomTool { /// 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 the tool declared that permission may be skipped unless a deny rule matches + #[serde(skip_serializing_if = "Option::is_none")] + pub skip_permission: Option, /// Tool call ID that triggered this permission request #[serde(skip_serializing_if = "Option::is_none")] pub tool_call_id: Option, @@ -3325,12 +4682,16 @@ pub struct PermissionRequestFactory { pub approval_key: String, /// Whether this factory is eligible for persistent approval pub can_persist_approval: bool, + /// Factory-declared AI-credit limit before any run/resume caller override is applied. #[serde(skip_serializing_if = "Option::is_none")] pub declared_max_ai_credits: Option, + /// Factory-declared concurrent-subagent limit before any run/resume caller override is applied. #[serde(skip_serializing_if = "Option::is_none")] pub declared_max_concurrent_subagents: Option, + /// Factory-declared total-subagent limit before any run/resume caller override is applied. #[serde(skip_serializing_if = "Option::is_none")] pub declared_max_total_subagents: Option, + /// Factory-declared active-time limit in seconds before any run/resume caller override is applied. #[serde(skip_serializing_if = "Option::is_none")] pub declared_timeout_seconds: Option, /// Factory description @@ -3381,35 +4742,29 @@ pub struct PermissionRequestExtensionPermissionAccess { pub tool_call_id: Option, } -/// 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.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
+/// Extension sensitive environment variable access request #[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. +pub struct PermissionRequestExtensionEnvAccess { + /// Names of the sensitive environment variables the extension is requesting. Values never appear here. + pub environment_variables: Vec, + /// Name of the extension requesting environment variable access + pub extension_name: String, + /// Permission kind discriminator + pub kind: PermissionRequestExtensionEnvAccessKind, + /// When true, managed policy requires an explicit user decision and automatic approval must be bypassed. #[serde(skip_serializing_if = "Option::is_none")] - pub model: Option, - /// Human-readable reason for the judge's recommendation, when available. + pub managed_approval_required: Option, + /// Tool call ID that triggered this permission request #[serde(skip_serializing_if = "Option::is_none")] - pub reason: Option, - /// The auto-approval safety judge's outcome for this request. - pub recommendation: AutoApprovalRecommendation, + pub tool_call_id: Option, } /// Shell command permission prompt #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PermissionPromptRequestCommands { - /// Auto-approval judge information for this request; present only when auto mode is enabled. + /// Assisted-approval judge information for this request; present only in assisted mode. /// ///
/// @@ -3418,7 +4773,7 @@ pub struct PermissionPromptRequestCommands { /// ///
#[serde(skip_serializing_if = "Option::is_none")] - pub auto_approval: Option, + pub assisted_approval: Option, /// Whether the UI can offer session-wide approval for this command pattern pub can_offer_session_approval: bool, /// Command identifiers covered by this approval prompt @@ -3444,7 +4799,7 @@ pub struct PermissionPromptRequestCommands { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PermissionPromptRequestWrite { - /// Auto-approval judge information for this request; present only when auto mode is enabled. + /// Assisted-approval judge information for this request; present only in assisted mode. /// ///
/// @@ -3453,7 +4808,7 @@ pub struct PermissionPromptRequestWrite { /// ///
#[serde(skip_serializing_if = "Option::is_none")] - pub auto_approval: Option, + pub assisted_approval: Option, /// Whether the UI can offer session-wide approval for file write operations pub can_offer_session_approval: bool, /// Unified diff showing the proposed changes @@ -3479,7 +4834,7 @@ pub struct PermissionPromptRequestWrite { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PermissionPromptRequestRead { - /// Auto-approval judge information for this request; present only when auto mode is enabled. + /// Assisted-approval judge information for this request; present only in assisted mode. /// ///
/// @@ -3488,7 +4843,7 @@ pub struct PermissionPromptRequestRead { /// ///
#[serde(skip_serializing_if = "Option::is_none")] - pub auto_approval: Option, + pub assisted_approval: Option, /// Human-readable description of why the file is being read pub intention: String, /// Prompt kind discriminator @@ -3510,7 +4865,7 @@ pub struct PermissionPromptRequestMcp { /// Arguments to pass to the MCP tool #[serde(skip_serializing_if = "Option::is_none")] pub args: Option, - /// Auto-approval judge information for this request; present only when auto mode is enabled. + /// Assisted-approval judge information for this request; present only in assisted mode. /// ///
/// @@ -3519,9 +4874,22 @@ pub struct PermissionPromptRequestMcp { /// ///
#[serde(skip_serializing_if = "Option::is_none")] - pub auto_approval: Option, + pub assisted_approval: Option, + /// Whether the host may offer a server-wide "approve all tools from this server" blanket. Absent is treated as true; the runtime sends false when managed policy disables bypass-permissions mode, which forbids the server-wide escalation while still allowing per-tool approval. + #[serde(skip_serializing_if = "Option::is_none")] + pub can_offer_server_wide_approval: Option, /// Prompt kind discriminator pub kind: PermissionPromptRequestMcpKind, + /// Advisory runtime permission recommendation. The host remains responsible for deciding the request and may reject it. + /// + ///
+ /// + /// **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 permission_recommendation: Option, /// Name of the MCP server providing the tool pub server_name: String, /// Tool call ID that triggered this permission request @@ -3537,7 +4905,7 @@ pub struct PermissionPromptRequestMcp { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PermissionPromptRequestUrl { - /// Auto-approval judge information for this request; present only when auto mode is enabled. + /// Assisted-approval judge information for this request; present only in assisted mode. /// ///
/// @@ -3546,7 +4914,7 @@ pub struct PermissionPromptRequestUrl { /// ///
#[serde(skip_serializing_if = "Option::is_none")] - pub auto_approval: Option, + pub assisted_approval: Option, /// Human-readable description of why the URL is being accessed pub intention: String, /// Prompt kind discriminator @@ -3577,7 +4945,7 @@ pub struct PermissionPromptRequestMemory { /// Whether this is a store or vote memory operation #[serde(skip_serializing_if = "Option::is_none")] pub action: Option, - /// Auto-approval judge information for this request; present only when auto mode is enabled. + /// Assisted-approval judge information for this request; present only in assisted mode. /// ///
/// @@ -3586,7 +4954,7 @@ pub struct PermissionPromptRequestMemory { /// ///
#[serde(skip_serializing_if = "Option::is_none")] - pub auto_approval: Option, + pub assisted_approval: Option, /// Source references for the stored fact (store only) #[serde(skip_serializing_if = "Option::is_none")] pub citations: Option, @@ -3615,7 +4983,7 @@ pub struct PermissionPromptRequestCustomTool { /// Arguments to pass to the custom tool #[serde(skip_serializing_if = "Option::is_none")] pub args: Option, - /// Auto-approval judge information for this request; present only when auto mode is enabled. + /// Assisted-approval judge information for this request; present only in assisted mode. /// ///
/// @@ -3624,7 +4992,7 @@ pub struct PermissionPromptRequestCustomTool { /// ///
#[serde(skip_serializing_if = "Option::is_none")] - pub auto_approval: Option, + pub assisted_approval: Option, /// Prompt kind discriminator pub kind: PermissionPromptRequestCustomToolKind, /// Tool call ID that triggered this permission request @@ -3642,7 +5010,7 @@ pub struct PermissionPromptRequestCustomTool { pub struct PermissionPromptRequestPath { /// Underlying permission kind that needs path approval pub access_kind: PermissionPromptRequestPathAccessKind, - /// Auto-approval judge information for this request; present only when auto mode is enabled. + /// Assisted-approval judge information for this request; present only in assisted mode. /// ///
/// @@ -3651,7 +5019,7 @@ pub struct PermissionPromptRequestPath { /// ///
#[serde(skip_serializing_if = "Option::is_none")] - pub auto_approval: Option, + pub assisted_approval: Option, /// Prompt kind discriminator pub kind: PermissionPromptRequestPathKind, /// File paths that require explicit approval @@ -3665,7 +5033,7 @@ pub struct PermissionPromptRequestPath { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PermissionPromptRequestHook { - /// Auto-approval judge information for this request; present only when auto mode is enabled. + /// Assisted-approval judge information for this request; present only in assisted mode. /// ///
/// @@ -3674,7 +5042,7 @@ pub struct PermissionPromptRequestHook { /// ///
#[serde(skip_serializing_if = "Option::is_none")] - pub auto_approval: Option, + pub assisted_approval: Option, /// Optional message from the hook explaining why confirmation is needed #[serde(skip_serializing_if = "Option::is_none")] pub hook_message: Option, @@ -3694,7 +5062,7 @@ pub struct PermissionPromptRequestHook { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PermissionPromptRequestExtensionManagement { - /// Auto-approval judge information for this request; present only when auto mode is enabled. + /// Assisted-approval judge information for this request; present only in assisted mode. /// ///
/// @@ -3703,7 +5071,7 @@ pub struct PermissionPromptRequestExtensionManagement { /// ///
#[serde(skip_serializing_if = "Option::is_none")] - pub auto_approval: Option, + pub assisted_approval: Option, /// Name of the extension being managed #[serde(skip_serializing_if = "Option::is_none")] pub extension_name: Option, @@ -3722,7 +5090,7 @@ pub struct PermissionPromptRequestExtensionManagement { 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. + /// Assisted-approval judge information for this request; present only in assisted mode. /// ///
/// @@ -3731,15 +5099,19 @@ pub struct PermissionPromptRequestFactory { /// ///
#[serde(skip_serializing_if = "Option::is_none")] - pub auto_approval: Option, + pub assisted_approval: Option, /// Whether this factory is eligible for persistent approval pub can_persist_approval: bool, + /// Factory-declared AI-credit limit before any run/resume caller override is applied. #[serde(skip_serializing_if = "Option::is_none")] pub declared_max_ai_credits: Option, + /// Factory-declared concurrent-subagent limit before any run/resume caller override is applied. #[serde(skip_serializing_if = "Option::is_none")] pub declared_max_concurrent_subagents: Option, + /// Factory-declared total-subagent limit before any run/resume caller override is applied. #[serde(skip_serializing_if = "Option::is_none")] pub declared_max_total_subagents: Option, + /// Factory-declared active-time limit in seconds before any run/resume caller override is applied. #[serde(skip_serializing_if = "Option::is_none")] pub declared_timeout_seconds: Option, /// Factory description @@ -3776,7 +5148,7 @@ pub struct PermissionPromptRequestFactory { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PermissionPromptRequestExtensionPermissionAccess { - /// Auto-approval judge information for this request; present only when auto mode is enabled. + /// Assisted-approval judge information for this request; present only in assisted mode. /// ///
/// @@ -3785,7 +5157,7 @@ pub struct PermissionPromptRequestExtensionPermissionAccess { /// ///
#[serde(skip_serializing_if = "Option::is_none")] - pub auto_approval: Option, + pub assisted_approval: Option, /// Capabilities the extension is requesting pub capabilities: Vec, /// Name of the extension requesting permission access @@ -3797,10 +5169,38 @@ pub struct PermissionPromptRequestExtensionPermissionAccess { pub tool_call_id: Option, } +/// Extension sensitive environment variable access prompt +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PermissionPromptRequestExtensionEnvAccess { + /// Assisted-approval judge information for this request; present only in assisted mode. + /// + ///
+ /// + /// **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 assisted_approval: Option, + /// Names of the sensitive environment variables the extension is requesting. Values never appear here. + pub environment_variables: Vec, + /// Name of the extension requesting environment variable access + pub extension_name: String, + /// Prompt kind discriminator + pub kind: PermissionPromptRequestExtensionEnvAccessKind, + /// Tool call ID that triggered this permission request + #[serde(skip_serializing_if = "Option::is_none")] + pub tool_call_id: Option, +} + /// Session event "permission.requested". Permission request notification requiring client approval with request details #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PermissionRequestedData { + /// Agent mode captured from the owning turn when permission evaluation began. + #[serde(skip_serializing_if = "Option::is_none")] + pub agent_mode: Option, /// Details of the permission being requested pub permission_request: PermissionRequest, /// Derived user-facing permission prompt details for UI consumers @@ -3822,6 +5222,9 @@ pub struct PermissionRequestedData { pub struct PermissionApproved { /// The permission request was approved pub kind: PermissionApprovedKind, + /// Whether a managed approval policy already handled this request + #[serde(skip_serializing_if = "Option::is_none")] + pub managed_approval_handled: Option, } /// Session-scoped tool-approval rule for specific shell command identifiers. @@ -3912,6 +5315,18 @@ pub struct UserToolSessionApprovalExtensionPermissionAccess { pub kind: UserToolSessionApprovalExtensionPermissionAccessKind, } +/// Session-scoped tool-approval rule for an extension's access to sensitive environment variables, keyed by extension name and the exact set of variable names. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UserToolSessionApprovalExtensionEnvAccess { + /// Names of the sensitive environment variables this approval covers. Values are never persisted. + pub environment_variables: Vec, + /// Extension name + pub extension_name: String, + /// Extension environment access approval kind + pub kind: UserToolSessionApprovalExtensionEnvAccessKind, +} + /// Permission response variant that approves a request and remembers the provided approval for the rest of the session. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -3920,6 +5335,9 @@ pub struct PermissionApprovedForSession { pub approval: UserToolSessionApproval, /// Approved and remembered for the rest of the session pub kind: PermissionApprovedForSessionKind, + /// Whether a managed approval policy already handled this request + #[serde(skip_serializing_if = "Option::is_none")] + pub managed_approval_handled: Option, } /// Permission response variant that approves a request and persists the provided approval to a project location key. @@ -3932,6 +5350,9 @@ pub struct PermissionApprovedForLocation { pub kind: PermissionApprovedForLocationKind, /// The location key (git root or cwd) to persist the approval to pub location_key: String, + /// Whether a managed approval policy already handled this request + #[serde(skip_serializing_if = "Option::is_none")] + pub managed_approval_handled: Option, } /// Permission response variant indicating the request was cancelled before use, with an optional reason. @@ -4264,6 +5685,32 @@ pub struct SessionCustomNotificationData { pub version: Option, } +/// Session event "ui.ephemeral_query". Ordered output and terminal state for a transient query that does not modify conversation 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 UiEphemeralQueryData { + /// Full response text, present for the `completed` phase. + #[serde(skip_serializing_if = "Option::is_none")] + pub answer: Option, + /// Ordered text delta, present for the `chunk` phase. + #[serde(skip_serializing_if = "Option::is_none")] + pub chunk: Option, + /// Model or transport failure message, present for the `failed` phase. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// Current query lifecycle phase. + pub phase: UIEphemeralQueryPhase, + /// Runtime-minted query identifier. + pub request_id: RequestId, +} + /// Session event "external_tool.requested". External tool invocation request for client-side tool execution #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -4271,6 +5718,9 @@ pub struct ExternalToolRequestedData { /// Arguments to pass to the external tool #[serde(skip_serializing_if = "Option::is_none")] pub arguments: Option, + /// Stable provider identity captured with an extension-owned tool definition; hosts use it to route the request to the same provider that was offered to the model + #[serde(skip_serializing_if = "Option::is_none")] + pub provider_id: Option, /// Unique identifier for this request; used to respond via session.respondToExternalTool() pub request_id: RequestId, /// Session ID that this external tool request belongs to @@ -4447,7 +5897,7 @@ pub struct SessionAutoModeResolvedData { 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, then the policy helper, per ordinary key, while permissions compose restrictively across device, server, policy-helper, and SDK-client layers. The account-scoped `getManagedSettings()` API does not include session-local client injection. Marked experimental while the managed-settings surface stabilizes. /// ///
/// @@ -4460,21 +5910,30 @@ 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 server and device each supplied a permission allowlist, so enforcement intersects them and the flattened settings payload omits `permissions.allow`. + /// 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 policy-helper managed-settings layer was present. The policy helper is the weakest channel: it fills keys no enterprise source set and can never replace one. + #[serde(skip_serializing_if = "Option::is_none")] + pub policy_helper_managed: Option, + /// Whether the effective sandbox policy forces the sandbox on *only* because managed policy could not be determined, rather than because the policy requires it. Lets clients tell a user whose `--no-sandbox` was overridden that the sandbox stayed on as a fail-closed fallback, instead of attributing it to an administrator who set no such policy. + #[serde(skip_serializing_if = "Option::is_none")] + pub sandbox_enabled_by_undetermined_policy: 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`, `client`, or `policyHelper` when exactly one channel contributed; `mixed` when multiple channels contributed; otherwise `none`. Consult the per-channel booleans for exact provenance. pub source: ManagedSettingsResolvedSource, } @@ -4551,6 +6010,9 @@ pub struct CapabilitiesChangedData { pub struct ExitPlanModeRequestedData { /// Available actions the user can take pub actions: Vec, + /// Model the session had selected when the plan was authored, when one is known + #[serde(skip_serializing_if = "Option::is_none")] + pub model: Option, /// Full content of the plan file pub plan_content: String, /// Recommended action to preselect for the user @@ -4607,9 +6069,55 @@ pub struct SessionBackgroundTasksChangedData {} pub struct FactoryRunUpdatedData { /// Monotonic revision now available for the run. pub revision: i64, + /// Factory run identifier. + pub run_id: String, +} + +/// Session event "factory.run_started". Ephemeral signal that a factory run attempt began executing. +/// +///
+/// +/// **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 FactoryRunStartedData { + /// Attempt number this start committed; a resumed run increments it. + pub attempt: i64, + /// Name of the factory this run executes. Low cardinality by construction. + pub factory_name: String, + /// Identifier of the factory run that started. pub run_id: String, } +/// Session event "factory.run_settled". Ephemeral signal that a factory run reached a terminal status. +/// +///
+/// +/// **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 FactoryRunSettledData { + /// AI credits this run consumed, in nano-AIU. + pub consumed_nano_aiu: i64, + /// Subagents this run consumed against its limits. + pub consumed_subagents: i64, + /// Active milliseconds accumulated across every attempt of this run. + pub elapsed_ms: i64, + /// Typed failure class recorded on the run, when it failed with one (e.g. `factory_limit_reached`). + #[serde(skip_serializing_if = "Option::is_none")] + pub failure_type: Option, + /// Identifier of the factory run that settled. + pub run_id: String, + /// Terminal status the run committed. + pub status: FactoryRunSettledStatus, +} + /// 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")] @@ -4617,6 +6125,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 @@ -4626,7 +6137,7 @@ pub struct SkillsLoadedSkill { /// Absolute path to the skill file, if available #[serde(skip_serializing_if = "Option::is_none")] pub path: Option, - /// Source location type (e.g., project, personal-copilot, plugin, builtin) + /// Source location type (e.g., project, personal-copilot, plugin, builtin, remote, sdk) pub source: SkillSource, /// Whether the skill can be invoked by the user as a slash command pub user_invocable: bool, @@ -4640,7 +6151,7 @@ pub struct SessionSkillsLoadedData { pub skills: Vec, } -/// A single loaded custom agent in `session.custom_agents_updated`, with identity, source, tools, invocability, and model override. +/// A single loaded custom agent in `session.custom_agents_updated`, with identity, source, tools, invocability, and authored model configuration. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CustomAgentsUpdatedAgent { @@ -4653,6 +6164,12 @@ pub struct CustomAgentsUpdatedAgent { /// Model override for this agent, if set #[serde(skip_serializing_if = "Option::is_none")] pub model: Option, + /// Whether authored models are preferences or required constraints + #[serde(skip_serializing_if = "Option::is_none")] + pub model_policy: Option, + /// Authored model ids in priority order, if configured + #[serde(skip_serializing_if = "Option::is_none")] + pub models: Option>, /// Internal name of the agent pub name: String, /// Source location: user, project, inherited, remote, or plugin @@ -5018,6 +6535,24 @@ pub struct McpAppToolCallCompleteData { pub tool_name: String, } +/// Routing preference used when the session model is `auto`. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AutoTier { + /// Optimize for efficiency. + #[serde(rename = "efficiency")] + Efficiency, + /// Balance efficiency and intelligence. + #[serde(rename = "balance")] + Balance, + /// Optimize for intelligence. + #[serde(rename = "intelligence")] + Intelligence, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Hosting platform type of the repository (github or ado) #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum WorkingDirectoryContextHostType { @@ -5084,6 +6619,24 @@ pub enum Verbosity { Unknown, } +/// The session mode the agent is operating in +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SessionMode { + /// 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, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + 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 { @@ -5138,25 +6691,49 @@ pub enum AutopilotObjectiveChangedStatus { Unknown, } -/// The session mode the agent is operating in +/// Origin of an effective session model change. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum SessionMode { - /// 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, +pub enum ModelChangeSource { + /// The user selected a model directly with `/model `. + #[serde(rename = "model_command")] + ModelCommand, + /// The user selected the model with `/settings`. + #[serde(rename = "settings_command")] + SettingsCommand, + /// The user selected the model with the `/config` alias. + #[serde(rename = "config_command")] + ConfigCommand, + /// The user selected the model in the model picker, including the picker opened by bare `/model`. + #[serde(rename = "model_picker")] + ModelPicker, + /// Organization-managed settings selected the model. + #[serde(rename = "managed_settings")] + ManagedSettings, + /// Repository settings selected the model. + #[serde(rename = "repo_settings")] + RepoSettings, + /// Startup model resolution selected the model. + #[serde(rename = "startup")] + Startup, + /// Selecting an agent selected its configured model. + #[serde(rename = "agent")] + Agent, + /// Entering, leaving, or reconfiguring plan mode selected the model. + #[serde(rename = "plan_mode")] + PlanMode, + /// The runtime selected the model automatically, such as rate-limit recovery or refusal fallback. + #[serde(rename = "automatic")] + Automatic, + /// An SDK or RPC caller selected the model. + #[serde(rename = "sdk")] + Sdk, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Allow-all mode for the session. +/// Permission mode for the session. /// ///
/// @@ -5165,16 +6742,16 @@ pub enum SessionMode { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum PermissionAllowAllMode { +pub enum PermissionMode { /// Permission requests follow the normal approval flow. - #[serde(rename = "off")] - Off, + #[serde(rename = "manual")] + Manual, + /// Permission requests include an LLM safety recommendation; clients may automatically approve requests judged acceptable. + #[serde(rename = "assisted")] + Assisted, /// Tool, path, and URL permission requests are automatically approved. - #[serde(rename = "on")] - On, - /// 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. - #[serde(rename = "auto")] - Auto, + #[serde(rename = "allow-all")] + AllowAll, /// Unknown variant for forward compatibility. #[default] #[serde(other)] @@ -5286,6 +6863,176 @@ pub enum TaskCompletionOutcome { Unknown, } +/// Structured terminal status from a tool completion event. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CompletionReceiptToolStatus { + /// The tool completed successfully. + #[serde(rename = "success")] + Success, + /// The tool failed without a more specific structured status. + #[serde(rename = "failure")] + Failure, + /// The tool exceeded its time budget. + #[serde(rename = "timeout")] + Timeout, + /// The user rejected the tool call. + #[serde(rename = "rejected")] + Rejected, + /// The permissions service denied the tool call. + #[serde(rename = "denied")] + Denied, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Runtime reason the completion decision was accepted. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CompletionReceiptStopReason { + /// The model reached a natural terminal response. + #[serde(rename = "natural")] + Natural, + /// A terminal tool ended the interaction. + #[serde(rename = "terminal_tool")] + TerminalTool, + /// The configured agentStop continuation limit was reached. + #[serde(rename = "agent_stop_block_limit")] + AgentStopBlockLimit, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Kind of turn for which HydraFusion routing is running. +/// +///
+/// +/// **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 FusionTurnKind { + /// A user-message turn. + #[serde(rename = "user")] + User, + /// A conversation-compaction turn. + #[serde(rename = "compaction")] + Compaction, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Server-recommended routing behavior for a later HydraFusion 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 FusionFollowUpAction { + /// Reuse the durable primary model without routing. + #[serde(rename = "reuse_primary")] + ReusePrimary, + /// Request a new routing decision. + #[serde(rename = "reroute")] + Reroute, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Validated HydraFusion execution pattern. +/// +///
+/// +/// **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 FusionPattern { + /// Run one primary solver phase. + #[serde(rename = "single")] + Single, + /// Run a primary phase, a judge, and an optional repair. + #[serde(rename = "cascade")] + Cascade, + /// Run a primary draft, a read-only critique, and a revision. + #[serde(rename = "critique")] + Critique, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// HydraFusion phase kind. +/// +///
+/// +/// **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 FusionPhaseKind { + /// Primary solver phase. + #[serde(rename = "primary")] + Primary, + /// Read-only cascade judge phase. + #[serde(rename = "judge")] + Judge, + /// Cascade repair phase. + #[serde(rename = "repair")] + Repair, + /// Initial critique-pattern draft phase. + #[serde(rename = "draft")] + Draft, + /// Read-only critique phase. + #[serde(rename = "critic")] + Critic, + /// Critique-pattern revision phase. + #[serde(rename = "revision")] + Revision, + /// Follow-up phase continuing from the resolved model. + #[serde(rename = "follow_up")] + FollowUp, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Conversation scope in which a HydraFusion phase executes. +/// +///
+/// +/// **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 FusionConversationScope { + /// Canonical root conversation history. + #[serde(rename = "root")] + Root, + /// Isolated read-only review history that does not enter the root conversation. + #[serde(rename = "review")] + Review, + /// 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 { @@ -5325,6 +7072,132 @@ pub enum UserMessageDelivery { Unknown, } +/// What the agent was doing when the user interrupted it. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AgentInterruptedActivity { + /// A request to the model was open. + #[serde(rename = "model_call")] + ModelCall, + /// The turn was sleeping between retry attempts. + #[serde(rename = "retry_backoff")] + RetryBackoff, + /// One or more tools were executing. + #[serde(rename = "tool_call")] + ToolCall, + /// Background sub-agents were running while the main loop was idle. + #[serde(rename = "background_agent")] + BackgroundAgent, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Where the interruption landed relative to the first streamed token. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AgentInterruptedCancelPhase { + /// No output had been produced when the request was cancelled. + #[serde(rename = "pre_first_token")] + PreFirstToken, + /// The response was already streaming when the request was cancelled. + #[serde(rename = "mid_stream")] + MidStream, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Transport used for a failed model call +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum ModelCallFailureTransport { + /// HTTP transport, including SSE streams. + #[serde(rename = "http")] + Http, + /// WebSocket transport. + #[serde(rename = "websocket")] + Websocket, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Content-safe activity observed while a HydraFusion phase is running. +/// +///
+/// +/// **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 FusionPhaseActivityKind { + /// The provider produced additional private output bytes. + #[serde(rename = "model_output")] + ModelOutput, + /// A tool began executing inside the phase. + #[serde(rename = "tool_started")] + ToolStarted, + /// A tool finished executing inside the phase. + #[serde(rename = "tool_completed")] + ToolCompleted, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// How a durable phase checkpoint contributes its exact message to canonical root 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, PartialEq, Eq, Serialize, Deserialize)] +pub enum FusionProjectionMode { + /// Append the exact root message immediately. + #[serde(rename = "append")] + Append, + /// Hold a terminal message outside canonical history until the final commit selects it. + #[serde(rename = "staged")] + Staged, + /// Do not project the checkpoint into root history. + #[serde(rename = "none")] + None, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Durable outcome status of a HydraFusion 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 FusionPhaseStatus { + /// The phase completed successfully. + #[serde(rename = "succeeded")] + Succeeded, + /// The phase failed. + #[serde(rename = "failed")] + Failed, + /// The phase was cancelled. + #[serde(rename = "cancelled")] + Cancelled, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum AssistantMessageToolRequestType { @@ -5365,6 +7238,17 @@ pub enum CitationProvider { Unknown, } +/// Hosted program caller type +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AssistantMessageToolRequestCallerType { + #[serde(rename = "program")] + Program, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// API endpoint used for this model call, matching CAPI supported_endpoints vocabulary #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum AssistantUsageApiEndpoint { @@ -5386,6 +7270,21 @@ pub enum AssistantUsageApiEndpoint { Unknown, } +/// Transport used for a successful model call +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AssistantUsageTransport { + /// HTTP transport, including SSE streams. + #[serde(rename = "http")] + Http, + /// WebSocket transport. + #[serde(rename = "websocket")] + Websocket, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// For HTTP 400 failures only: whether the response carried a structured CAPI error envelope (structured_error, a deterministic validation failure) or no error body (bodyless, the transient gateway/proxy signature). Absent for non-400 failures. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum ModelCallFailureBadRequestKind { @@ -5434,15 +7333,21 @@ pub enum ModelCallFailureSource { Unknown, } -/// Transport used for a failed model call +/// Final outcome of one logical model dispatch after response acceptance processing #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum ModelCallFailureTransport { - /// HTTP transport, including SSE streams. - #[serde(rename = "http")] - Http, - /// WebSocket transport. - #[serde(rename = "websocket")] - Websocket, +pub enum ModelCallFinishedOutcome { + /// The provider response was accepted for continued agent processing. + #[serde(rename = "success")] + Success, + /// The dispatch ended with a provider or transport error. + #[serde(rename = "error")] + Error, + /// The dispatch was cancelled before an accepted response was produced. + #[serde(rename = "cancelled")] + Cancelled, + /// The provider response was rejected during post-response acceptance processing. + #[serde(rename = "rejected")] + Rejected, /// Unknown variant for forward compatibility. #[default] #[serde(other)] @@ -5485,6 +7390,82 @@ pub enum ToolExecutionStartToolDescriptionMetaUIVisibility { Unknown, } +/// Binary result type discriminator. Use "image" for images and "resource" for other binary data. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum PersistedBinaryImageType { + /// Binary image data. + #[serde(rename = "image")] + Image, + /// Other binary resource data. + #[serde(rename = "resource")] + Resource, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Why the binary data is absent: it exceeded the inline size limit, or its asset was unavailable +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum OmittedBinaryOmittedReason { + /// Bytes exceeded the session's inline size limit. + #[serde(rename = "too_large")] + TooLarge, + /// The referenced binary asset could not be found (e.g. a truncated log). + #[serde(rename = "asset_unavailable")] + AssetUnavailable, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Binary result type discriminator. Use "image" for images and "resource" for other binary data. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum OmittedBinaryType { + /// Binary image data. + #[serde(rename = "image")] + Image, + /// Other binary resource data. + #[serde(rename = "resource")] + Resource, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Binary result type discriminator. Use "image" for images and "resource" for other binary data. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum BinaryAssetReferenceType { + /// Binary image data. + #[serde(rename = "image")] + Image, + /// Other binary resource data. + #[serde(rename = "resource")] + Resource, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// A model-facing binary result as persisted: full inline data, a size-omitted marker, or a deduplicated asset reference +/// +///
+/// +/// **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 PersistedBinaryResult { + PersistedBinaryImage(PersistedBinaryImage), + OmittedBinaryResult(OmittedBinaryResult), + BinaryAssetReference(BinaryAssetReference), +} + /// Content block type discriminator #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum ToolExecutionCompleteContentTextType { @@ -5672,6 +7653,25 @@ pub enum PermissionRequestMcpKind { Mcp, } +/// Advisory recommendation the runtime attaches to a permission request whose origin it can vouch for by construction. Unlike the auto-approval judge this does not depend on auto mode and does not evaluate what the tool call does; its absence simply means the runtime has no opinion and the request follows the host's normal approval flow. +/// +///
+/// +/// **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 PermissionRecommendation { + /// The runtime vouches for the request's origin and recommends approving it without prompting. The host still owns the decision and may deny it; deny rules, managed policy, and the auto-approval safety judge all outrank this recommendation. + #[serde(rename = "approve")] + Approve, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Permission kind discriminator #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum PermissionRequestUrlKind { @@ -5695,6 +7695,65 @@ pub enum PermissionRequestMemoryAction { Unknown, } +/// Why the assisted-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 AssistedApprovalJudgeFailureReason { + /// 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 assisted-approval safety judge for a permission request. Present only in assisted mode; its absence means the judge did not evaluate the 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 AssistedApprovalRecommendation { + /// The judge evaluated the request and recommends automatically approving it. + #[serde(rename = "approve")] + Approve, + /// The judge evaluated the request and does not recommend automatically approving it; explicit approval is required. Whether that means prompting, denying, or something else is the consumer's decision. + #[serde(rename = "requireApproval")] + RequireApproval, + /// Assisted mode is enabled, but this request category is never automatically approvable (for example, sandbox-bypass requests), so the judge was not consulted. + #[serde(rename = "excluded")] + Excluded, + /// The judge was consulted but did not return a usable recommendation, so the request requires explicit approval. + #[serde(rename = "error")] + Error, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Vote direction (vote only) #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum PermissionRequestMemoryDirection { @@ -5718,6 +7777,21 @@ pub enum PermissionRequestMemoryKind { Memory, } +/// Scope of a stored memory. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum PermissionRequestMemoryScope { + /// Store the memory for the current repository. + #[serde(rename = "repository")] + Repository, + /// Store the memory for the current user. + #[serde(rename = "user")] + User, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Permission kind discriminator #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum PermissionRequestCustomToolKind { @@ -5773,6 +7847,14 @@ pub enum PermissionRequestExtensionPermissionAccessKind { ExtensionPermissionAccess, } +/// Permission kind discriminator +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum PermissionRequestExtensionEnvAccessKind { + #[serde(rename = "extension-env-access")] + #[default] + ExtensionEnvAccess, +} + /// Details of the permission being requested #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -5788,65 +7870,7 @@ pub enum PermissionRequest { 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). -/// -///
-/// -/// **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 AutoApprovalRecommendation { - /// The judge evaluated the request and recommends automatically approving it. - #[serde(rename = "approve")] - Approve, - /// The judge evaluated the request and does not recommend auto-approving it; explicit approval is required. Whether that means prompting, denying, or something else is the consumer's decision. - #[serde(rename = "requireApproval")] - RequireApproval, - /// Auto mode is enabled, but this request category is never auto-approvable (for example, sandbox-bypass requests), so the judge was not consulted. - #[serde(rename = "excluded")] - Excluded, - /// The judge was consulted but did not return a usable recommendation, so the request requires explicit approval. - #[serde(rename = "error")] - Error, - /// Unknown variant for forward compatibility. - #[default] - #[serde(other)] - Unknown, + ExtensionEnvAccess(PermissionRequestExtensionEnvAccess), } /// Prompt kind discriminator @@ -5963,6 +7987,14 @@ pub enum PermissionPromptRequestExtensionPermissionAccessKind { ExtensionPermissionAccess, } +/// Prompt kind discriminator +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum PermissionPromptRequestExtensionEnvAccessKind { + #[serde(rename = "extension-env-access")] + #[default] + ExtensionEnvAccess, +} + /// Derived user-facing permission prompt details for UI consumers #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -5979,6 +8011,7 @@ pub enum PermissionPromptRequest { ExtensionManagement(PermissionPromptRequestExtensionManagement), Factory(PermissionPromptRequestFactory), ExtensionPermissionAccess(PermissionPromptRequestExtensionPermissionAccess), + ExtensionEnvAccess(PermissionPromptRequestExtensionEnvAccess), } /// The permission request was approved @@ -6061,6 +8094,14 @@ pub enum UserToolSessionApprovalExtensionPermissionAccessKind { ExtensionPermissionAccess, } +/// Extension environment access approval kind +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum UserToolSessionApprovalExtensionEnvAccessKind { + #[serde(rename = "extension-env-access")] + #[default] + ExtensionEnvAccess, +} + /// The approval to add as a session-scoped rule #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -6074,6 +8115,7 @@ pub enum UserToolSessionApproval { ExtensionManagement(UserToolSessionApprovalExtensionManagement), Factory(UserToolSessionApprovalFactory), ExtensionPermissionAccess(UserToolSessionApprovalExtensionPermissionAccess), + ExtensionEnvAccess(UserToolSessionApprovalExtensionEnvAccess), } /// Approved and remembered for the rest of the session @@ -6278,6 +8320,37 @@ pub enum McpHeadersRefreshCompletedOutcome { Unknown, } +/// Lifecycle phase for a Rust-owned ephemeral query stream. +/// +///
+/// +/// **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 UIEphemeralQueryPhase { + /// The ephemeral query stream has begun. + #[serde(rename = "started")] + Started, + /// A partial result chunk was produced by the stream. + #[serde(rename = "chunk")] + Chunk, + /// The ephemeral query stream finished successfully. + #[serde(rename = "completed")] + Completed, + /// The ephemeral query stream ended with an error. + #[serde(rename = "failed")] + Failed, + /// The ephemeral query stream was cancelled before completing. + #[serde(rename = "aborted")] + Aborted, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// The user's auto-mode-switch choice #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum AutoModeSwitchResponse { @@ -6335,16 +8408,25 @@ 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, + /// A policy helper registered by device or server policy contributed. Device registration takes priority when present. + #[serde(rename = "policyHelper")] + PolicyHelper, + /// More than one channel contributed. Ordinary keys resolve device over server over policy helper 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. @@ -6368,21 +8450,24 @@ pub enum ManagedSettingsEnforcedAction { /// For a `bypass_permissions_blocked` action, which permission-escalation primitive was refused #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum ManagedSettingsEnforcedEscalation { - /// Full allow-all ("/allow-all on") permissions — auto-approving tools, paths, and URLs. + /// Full allow-all permissions — automatically approving tools, paths, and URLs. #[serde(rename = "allow_all")] AllowAll, - /// Auto-approval of all tool permission requests. + /// Automatic approval of all tool permission requests. #[serde(rename = "approve_all")] ApproveAll, - /// Advisory auto-approval ("/allow-all auto") mode — keeps normal prompt paths and adds LLM-advised approval, distinct from full allow-all. - #[serde(rename = "auto_approval")] - AutoApproval, + /// Assisted mode — keeps normal prompt paths and adds an LLM recommendation, distinct from allow-all. + #[serde(rename = "assisted_approval")] + AssistedApproval, /// Unrestricted filesystem access outside the session's allowed directories. #[serde(rename = "unrestricted_paths")] UnrestrictedPaths, /// Unrestricted URL fetch access. #[serde(rename = "unrestricted_urls")] UnrestrictedUrls, + /// A server-wide MCP "Always Allow" (or `--allow-tool `) blanket that would auto-approve every tool from an MCP server. Capped to per-tool approval; each tool still prompts. + #[serde(rename = "server_wide_mcp_approval")] + ServerWideMcpApproval, /// Unknown variant for forward compatibility. #[default] #[serde(other)] @@ -6410,7 +8495,28 @@ pub enum ExitPlanModeAction { Unknown, } -/// Source location type (e.g., project, personal-copilot, plugin, builtin) +/// Terminal status a factory run committed. A settled run is never `pending` or `running`, so those two members of the run-status domain are deliberately absent. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum FactoryRunSettledStatus { + /// The factory body resolved and its result was committed. + #[serde(rename = "completed")] + Completed, + /// The run was stopped by a limit, an approval refusal or another policy decision. + #[serde(rename = "halted")] + Halted, + /// The run was cancelled by its caller or by session disposal. + #[serde(rename = "cancelled")] + Cancelled, + /// The run failed, with `failureType` carrying the class when it has one. + #[serde(rename = "error")] + Error, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Source location type (e.g., project, personal-copilot, plugin, builtin, sdk) #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum SkillSource { /// Skill defined in the current project's skill directories. @@ -6434,6 +8540,24 @@ pub enum SkillSource { /// Skill bundled with the runtime. #[serde(rename = "builtin")] Builtin, + /// Pathless skill supplied lazily by an SDK skill provider. + #[serde(rename = "sdk")] + Sdk, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Whether configured models are advisory preferences or required constraints +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AgentModelPolicy { + /// Treat the authored models as advisory preferences that callers may override. + #[serde(rename = "preferred")] + Preferred, + /// Require subagent execution to use one of the authored models. + #[serde(rename = "required")] + Required, /// Unknown variant for forward compatibility. #[default] #[serde(other)] diff --git a/rust/src/github_token.rs b/rust/src/github_token.rs new file mode 100644 index 0000000000..c5eaa63adf --- /dev/null +++ b/rust/src/github_token.rs @@ -0,0 +1,378 @@ +//! Session-scoped GitHub token provider callbacks. + +use std::collections::HashMap; +use std::future::Future; +use std::sync::{Arc, OnceLock, Weak}; + +use async_trait::async_trait; +use parking_lot::Mutex; +use serde_json::Value; + +use crate::generated::api_types::{ + GitHubTokenAcquireReason, GitHubTokenAcquireRequest, GitHubTokenAcquireResult, + GitHubTokenAcquireResultCancelled, GitHubTokenAcquireResultToken, +}; +use crate::{Client, ClientInner, JsonRpcError, JsonRpcRequest, JsonRpcResponse, error_codes}; + +/// Why the runtime is requesting a GitHub token. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum GitHubTokenRequestReason { + /// The session needs its initial token. + Initial, + /// The session needs a refreshed token. + Refresh, +} + +/// Context supplied when the runtime needs a GitHub token for a session. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct GitHubTokenProviderArgs { + /// Effective GitHub host for which a token is required. + pub host: String, + /// Session receiving the token, when the runtime has assigned its ID. + pub session_id: Option, + /// Whether this is the initial token acquisition or a refresh. + pub reason: GitHubTokenRequestReason, +} + +/// A GitHub access token returned by a session token provider. +/// +/// `expires_in_seconds` is the positive remaining lifetime when the callback +/// completes. Production GitHub tokens typically last eight hours. +pub struct GitHubToken { + access_token: String, + expires_in_seconds: i64, + token_type: Option, +} + +impl GitHubToken { + /// Construct a token response with its remaining lifetime in seconds. + pub fn new(access_token: impl Into, expires_in_seconds: i64) -> Self { + Self { + access_token: access_token.into(), + expires_in_seconds, + token_type: None, + } + } + + /// Override the OAuth token type. The runtime defaults to `bearer` when unset. + pub fn with_token_type(mut self, token_type: impl Into) -> Self { + self.token_type = Some(token_type.into()); + self + } + + fn into_wire(self) -> GitHubTokenAcquireResultToken { + GitHubTokenAcquireResultToken { + access_token: self.access_token, + expires_in: self.expires_in_seconds, + kind: Default::default(), + token_type: self.token_type, + } + } +} + +impl std::fmt::Debug for GitHubToken { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("GitHubToken") + .field("access_token", &"") + .field("expires_in_seconds", &self.expires_in_seconds) + .field("token_type", &self.token_type) + .finish() + } +} + +/// Result of acquiring a session-scoped GitHub token. +pub enum GitHubTokenProviderResult { + /// A token was acquired. + Token(GitHubToken), + /// The host cancelled acquisition. + Cancelled, +} + +impl std::fmt::Debug for GitHubTokenProviderResult { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Token(token) => f.debug_tuple("Token").field(token).finish(), + Self::Cancelled => f.write_str("Cancelled"), + } + } +} + +/// Async callback used to acquire GitHub tokens for one session. +#[async_trait] +pub trait GitHubTokenProvider: Send + Sync { + /// Acquire a token or explicitly cancel the request. + /// + /// Initial cancellation, errors, and invalid token responses reject session + /// creation or resume instead of falling back to ambient authentication. + async fn get_token( + &self, + args: GitHubTokenProviderArgs, + ) -> Result; +} + +#[async_trait] +impl GitHubTokenProvider for F +where + F: Fn(GitHubTokenProviderArgs) -> Fut + Send + Sync, + Fut: Future> + Send, +{ + async fn get_token( + &self, + args: GitHubTokenProviderArgs, + ) -> Result { + (self)(args).await + } +} + +#[derive(Default)] +struct RegistryState { + providers: HashMap>, + session_owners: HashMap, +} + +pub(crate) struct GitHubTokenRegistry { + state: Mutex, + client: OnceLock>, +} + +impl GitHubTokenRegistry { + pub(crate) fn new() -> Self { + Self { + state: Mutex::new(RegistryState::default()), + client: OnceLock::new(), + } + } + + pub(crate) fn set_client(&self, client: Weak) { + let _ = self.client.set(client); + } + + pub(crate) fn register(&self, provider: Arc) -> String { + let registration_id = uuid::Uuid::new_v4().to_string(); + self.state + .lock() + .providers + .insert(registration_id.clone(), provider); + registration_id + } + + pub(crate) fn claim(&self, registration_id: &str, session_id: crate::SessionId) { + let mut state = self.state.lock(); + if let Some(previous) = state + .session_owners + .insert(session_id, registration_id.to_string()) + && previous != registration_id + { + state.providers.remove(&previous); + } + } + + pub(crate) fn unregister(&self, registration_id: &str) { + let mut state = self.state.lock(); + state.providers.remove(registration_id); + state + .session_owners + .retain(|_, owned| owned != registration_id); + } + + pub(crate) fn retire_session(&self, session_id: &crate::SessionId) { + let mut state = self.state.lock(); + if let Some(registration_id) = state.session_owners.remove(session_id) { + state.providers.remove(®istration_id); + } + } + + pub(crate) fn clear(&self) { + let mut state = self.state.lock(); + state.providers.clear(); + state.session_owners.clear(); + } + + pub(crate) async fn dispatch(&self, request: JsonRpcRequest) { + let Some(inner) = self.client.get().and_then(Weak::upgrade) else { + return; + }; + let client = Client::from_inner(inner); + let params = request + .params + .clone() + .unwrap_or(Value::Object(serde_json::Map::new())); + let params: GitHubTokenAcquireRequest = match serde_json::from_value(params) { + Ok(params) => params, + Err(error) => { + send_error( + &client, + request.id, + error_codes::INVALID_PARAMS, + &format!("invalid params: {error}"), + ) + .await; + return; + } + }; + let provider = self + .state + .lock() + .providers + .get(¶ms.registration_id) + .cloned(); + let Some(provider) = provider else { + send_error( + &client, + request.id, + error_codes::INTERNAL_ERROR, + "unknown GitHub token provider registration", + ) + .await; + return; + }; + + let reason = match params.reason { + GitHubTokenAcquireReason::Initial => GitHubTokenRequestReason::Initial, + GitHubTokenAcquireReason::Refresh => GitHubTokenRequestReason::Refresh, + GitHubTokenAcquireReason::Unknown => { + send_error( + &client, + request.id, + error_codes::INVALID_PARAMS, + "unknown GitHub token acquisition reason", + ) + .await; + return; + } + }; + + match provider + .get_token(GitHubTokenProviderArgs { + host: params.host, + session_id: params.session_id, + reason, + }) + .await + { + Ok(GitHubTokenProviderResult::Token(token)) => { + respond( + &client, + request.id, + GitHubTokenAcquireResult::Token(token.into_wire()), + ) + .await; + } + Ok(GitHubTokenProviderResult::Cancelled) => { + respond( + &client, + request.id, + GitHubTokenAcquireResult::Cancelled(GitHubTokenAcquireResultCancelled { + kind: Default::default(), + }), + ) + .await; + } + Err(error) => { + send_error( + &client, + request.id, + error_codes::INTERNAL_ERROR, + &format!("GitHub token provider failed: {error}"), + ) + .await; + } + } + } +} + +pub(crate) struct GitHubTokenRegistration { + registry: Arc, + id: String, +} + +impl GitHubTokenRegistration { + pub(crate) fn new(registry: Arc, id: String) -> Self { + Self { registry, id } + } + + pub(crate) fn id(&self) -> &str { + &self.id + } + + pub(crate) fn claim(&self, session_id: crate::SessionId) { + self.registry.claim(&self.id, session_id); + } +} + +impl Drop for GitHubTokenRegistration { + fn drop(&mut self) { + self.registry.unregister(&self.id); + } +} + +async fn respond(client: &Client, request_id: u64, result: GitHubTokenAcquireResult) { + match serde_json::to_value(result) { + Ok(result) => { + let _ = client + .send_response(&JsonRpcResponse { + jsonrpc: "2.0".to_string(), + id: request_id, + result: Some(result), + error: None, + }) + .await; + } + Err(_) => { + send_error( + client, + request_id, + error_codes::INTERNAL_ERROR, + "serialization failure", + ) + .await; + } + } +} + +async fn send_error(client: &Client, request_id: u64, code: i32, message: &str) { + let _ = client + .send_response(&JsonRpcResponse { + jsonrpc: "2.0".to_string(), + id: request_id, + result: None, + error: Some(JsonRpcError { + code, + message: message.to_string(), + data: None, + }), + }) + .await; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn token_debug_is_redacted() { + let token = GitHubToken::new("do-not-print", 28_800); + assert!(!format!("{token:?}").contains("do-not-print")); + } + + #[test] + fn retiring_session_removes_its_provider() { + let registry = GitHubTokenRegistry::new(); + let provider = Arc::new(|_args: GitHubTokenProviderArgs| async { + Ok(GitHubTokenProviderResult::Cancelled) + }); + let registration_id = registry.register(provider); + let session_id = crate::SessionId::from("session-1"); + registry.claim(®istration_id, session_id.clone()); + + registry.retire_session(&session_id); + + assert!( + !registry + .state + .lock() + .providers + .contains_key(®istration_id) + ); + } +} diff --git a/rust/src/handler.rs b/rust/src/handler.rs index 77edf919c9..61d9b192cb 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,11 +99,43 @@ 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, + /// response_capability: None, + /// 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, + } } } @@ -238,10 +294,11 @@ pub trait McpAuthHandler: Send + Sync + 'static { ) -> McpAuthResult; } -/// Handler for `user_input.requested` events from the `ask_user` tool. +/// Handler for `user_input.requested` events from the legacy question-and-answer +/// `ask_user` variant. /// -/// When unset, `requestUserInput: false` goes on the wire and the -/// `ask_user` tool is disabled for the session. +/// When unset, `requestUserInput: false` goes on the wire, so this client +/// cannot handle legacy user-input requests. #[async_trait] pub trait UserInputHandler: Send + Sync + 'static { /// Answer a question on behalf of the user. Return `None` to signal @@ -338,7 +395,10 @@ mod tests { .await; assert!(matches!( result, - PermissionResult::Decision(PermissionDecision::ApproveOnce(_)) + PermissionResult::Decision { + decision: PermissionDecision::ApproveOnce(_), + .. + } )); } @@ -356,7 +416,10 @@ mod tests { .await; assert!(matches!( result, - PermissionResult::Decision(PermissionDecision::UserNotAvailable(_)) + PermissionResult::Decision { + decision: PermissionDecision::UserNotAvailable(_), + .. + } )); } @@ -386,7 +449,10 @@ mod tests { .await; assert!(matches!( result, - PermissionResult::Decision(PermissionDecision::Reject(_)) + PermissionResult::Decision { + decision: PermissionDecision::Reject(_), + .. + } )); } diff --git a/rust/src/jsonrpc.rs b/rust/src/jsonrpc.rs index fbdc96505b..25a405080b 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 cafa3c5968..fd7f12cf14 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -10,6 +10,8 @@ mod canvas_dispatch; #[cfg(feature = "bundled-cli")] pub(crate) mod embeddedcli; mod errors; +/// Connection-level extension launch profile provider. +pub mod extension_launch_provider; /// In-process FFI transport hosting the runtime cdylib (`Transport::InProcess`). #[cfg(feature = "bundled-in-process")] pub(crate) mod ffi; @@ -22,6 +24,8 @@ pub mod copilot_request_handler; /// `#[doc(hidden)]` — re-exports the generated telemetry payload types. #[doc(hidden)] pub mod github_telemetry; +/// Session-scoped GitHub token provider callbacks. +pub mod github_token; /// Event handler traits for session lifecycle. pub mod handler; /// Lifecycle hook callbacks (pre/post tool use, prompt submission, session start/end). @@ -29,6 +33,7 @@ pub mod hooks; mod jsonrpc; /// Permission-policy helpers that produce a [`handler::PermissionHandler`]. pub mod permission; +mod process_tree; /// BYOK bearer-token provider callbacks. pub mod provider_token; mod provider_token_dispatch; @@ -78,6 +83,10 @@ use std::sync::{Arc, OnceLock}; use std::time::{Duration, Instant}; use async_trait::async_trait; +pub use github_token::{ + GitHubToken, GitHubTokenProvider, GitHubTokenProviderArgs, GitHubTokenProviderResult, + GitHubTokenRequestReason, +}; /// Re-export of [`indexmap::IndexMap`], used for order-preserving maps in the /// public API (e.g. [`Tool::parameters`](types::Tool::parameters) and /// `SessionConfig::mcp_servers`) so serialized key order stays deterministic. @@ -173,8 +182,10 @@ pub enum Transport { /// How the SDK locates the GitHub Copilot CLI binary. #[derive(Debug, Clone, Default)] pub enum CliProgram { - /// Auto-resolve: `COPILOT_CLI_PATH` → embedded CLI → dev cache. - /// This is the default. + /// Auto-resolve the transport's program. Managed child-process transports + /// select `COPILOT_CLI_PATH`, then the bundled runtime wrapper. In-process + /// transport loads the wrapper's adjacent runtime library directly unless + /// `COPILOT_CLI_PATH` explicitly selects a legacy embedded host. #[default] Resolve, /// Use an explicit binary path (skips resolution). @@ -198,12 +209,9 @@ pub const HAS_BUNDLED_CLI: bool = cfg!(has_bundled_cli); /// Returns the path to the bundled Copilot CLI, extracting it from the /// embedded archive on first call. /// -/// This is the same path [`Client::start`] resolves to when -/// [`ClientOptions::program`] is [`CliProgram::Resolve`], no -/// `COPILOT_CLI_PATH` override is set, and no -/// [`ClientOptions::bundled_cli_extract_dir`] is configured — exposing -/// it directly so callers (health checks, diagnostics, version probes) -/// can reach the bundled binary without spinning up a full [`Client`]. +/// This exposes the CLI artifact directly for callers such as health checks, +/// diagnostics, version probes, and in-process hosting. Managed child-process +/// transports resolve the bundled `copilot-runtime` wrapper instead. /// /// Subsequent calls return the cached result. Extraction is skipped when /// an already-published binary passes a cheap integrity re-check; a @@ -229,12 +237,35 @@ pub fn install_bundled_cli() -> Option { } } +/// Returns the path to the bundled `copilot-runtime` executable, extracting it +/// with adjacent `runtime.node` on first call. +/// +/// This is intended for health checks and intermediate launchers that need the +/// concrete managed runtime path before [`Client::start`]. Subsequent calls +/// return the cached result. +/// +/// Returns `None` when the `bundled-cli` feature is off, the target platform +/// isn't supported, or extraction failed. It does not fall back to the +/// build-time extraction cache. +pub fn install_bundled_runtime() -> Option { + #[cfg(feature = "bundled-cli")] + { + embeddedcli::runtime_path() + } + #[cfg(not(feature = "bundled-cli"))] + { + None + } +} + /// Options for starting a [`Client`]. /// /// When `program` is [`CliProgram::Resolve`] (the default), [`Client::start`] -/// uses `COPILOT_CLI_PATH` when set to a real file. Otherwise it uses the -/// bundled Copilot CLI when the default `bundled-cli` cargo feature is enabled, -/// or the build-time extracted dev-cache CLI when that feature is disabled. +/// uses `COPILOT_CLI_PATH` when set to a real file. Managed child-process +/// transports next use the bundled `copilot-runtime` wrapper. In-process +/// transport loads the wrapper's adjacent runtime library. With `bundled-cli` +/// disabled, the corresponding artifact is resolved from the build-time +/// extraction cache. /// /// Set `program` to [`CliProgram::Path`] to use an explicit binary instead. /// This skips auto-resolution entirely. @@ -254,6 +285,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 @@ -300,6 +336,14 @@ pub struct ClientOptions { /// [`CopilotRequestHandler`] /// instead of issuing the calls itself. pub request_handler: Option>, + /// Connection-level extension launch profile provider. + /// + /// When set, the SDK registers itself with the runtime during + /// [`Client::start`] before any session can be created. Incoming + /// `extensionLaunchProvider.resolve` requests are dispatched independently + /// of sessions. + pub extension_launch_provider: + Option>, /// Connection-level GitHub telemetry forwarding callback (experimental). /// /// When set, every session created or resumed on this client opts into @@ -357,6 +401,102 @@ pub struct ClientOptions { /// (the default) or are stripped to a minimal/safe baseline. See /// [`ClientMode`] for the contract and trade-offs. pub mode: ClientMode, + /// Declares the integrating application's identity, forwarded to the runtime on + /// the `server.connect` handshake. Declaring it lets the telemetry the + /// runtime emits on this connection be attributed to a consistent surface + /// (the application and its Copilot integration) instead of the runtime's own + /// build. All fields are optional; leave it `None` to keep the runtime's + /// default attribution. + pub client_info: Option, +} + +/// Identity of the integrating application, declared on the `server.connect` +/// handshake. +/// +/// Declaring it lets the telemetry the runtime emits on the connection be +/// attributed to a single, consistent surface instead of the runtime's own +/// build. All fields are optional; an empty field is omitted from the +/// handshake. +/// +/// The struct is `#[non_exhaustive]`, so construct it with [`ClientInfo::new`] +/// and the `with_*` builder methods rather than a struct literal. This lets the +/// SDK add identity fields in future releases without a breaking change. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +#[non_exhaustive] +pub struct ClientInfo { + /// Name of the application using the SDK. + pub application_name: Option, + /// Version of the application using the SDK. + pub application_version: Option, + /// Optional name of a specific integration within the application, such as an + /// extension or plugin. + pub integration_name: Option, + /// Optional version of the integration identified by [`Self::integration_name`]. + pub integration_version: Option, +} + +impl ClientInfo { + /// Create an empty `ClientInfo`. Populate fields with the `with_*` builder + /// methods; every field is optional. + pub fn new() -> Self { + Self::default() + } + + /// Set the name of the application using the SDK. + pub fn with_application_name(mut self, application_name: impl Into) -> Self { + self.application_name = Some(application_name.into()); + self + } + + /// Set the version of the application using the SDK. + pub fn with_application_version(mut self, application_version: impl Into) -> Self { + self.application_version = Some(application_version.into()); + self + } + + /// Set the name of a specific integration within the application, such as an + /// extension or plugin. + pub fn with_integration_name(mut self, integration_name: impl Into) -> Self { + self.integration_name = Some(integration_name.into()); + self + } + + /// Set the version of the integration identified by + /// [`Self::with_integration_name`]. + pub fn with_integration_version(mut self, integration_version: impl Into) -> Self { + self.integration_version = Some(integration_version.into()); + self + } + + /// Returns `true` when no field carries a non-empty value, in which case the + /// SDK omits `clientInfo` from the handshake and the runtime keeps its + /// default attribution. + fn is_empty(&self) -> bool { + Self::non_empty(&self.application_name).is_none() + && Self::non_empty(&self.application_version).is_none() + && Self::non_empty(&self.integration_name).is_none() + && Self::non_empty(&self.integration_version).is_none() + } + + /// Clone the field only when it holds a non-empty string, so empty fields are + /// dropped from the handshake. + fn non_empty(value: &Option) -> Option { + value.as_ref().filter(|s| !s.is_empty()).cloned() + } + + /// Map onto the generated connect wire shape, dropping empty fields. Returns + /// `None` when no field carries a non-empty value. + fn to_wire(&self) -> Option { + if self.is_empty() { + return None; + } + Some(crate::generated::api_types::ConnectClientInfo { + editor_name: Self::non_empty(&self.application_name), + editor_version: Self::non_empty(&self.application_version), + extension_name: Self::non_empty(&self.integration_name), + extension_version: Self::non_empty(&self.integration_version), + }) + } } impl std::fmt::Debug for ClientOptions { @@ -368,6 +508,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", @@ -388,6 +532,10 @@ impl std::fmt::Debug for ClientOptions { "request_handler", &self.request_handler.as_ref().map(|_| ""), ) + .field( + "extension_launch_provider", + &self.extension_launch_provider.as_ref().map(|_| ""), + ) .field( "on_github_telemetry", &self.on_github_telemetry.as_ref().map(|_| ""), @@ -400,6 +548,7 @@ impl std::fmt::Debug for ClientOptions { .field("base_directory", &self.base_directory) .field("enable_remote_sessions", &self.enable_remote_sessions) .field("bundled_cli_extract_dir", &self.bundled_cli_extract_dir) + .field("client_info", &self.client_info) .finish() } } @@ -632,6 +781,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, @@ -640,6 +790,7 @@ impl Default for ClientOptions { on_list_models: None, session_fs: None, request_handler: None, + extension_launch_provider: None, on_github_telemetry: None, on_get_trace_context: None, telemetry: None, @@ -647,6 +798,7 @@ impl Default for ClientOptions { enable_remote_sessions: false, bundled_cli_extract_dir: None, mode: ClientMode::default(), + client_info: None, } } } @@ -724,6 +876,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; @@ -785,6 +950,18 @@ impl ClientOptions { self } + /// Register a connection-level extension launch profile provider. + /// + /// The provider is wrapped in [`Arc`] internally and registered with the + /// runtime before [`Client::start`] returns. + pub fn with_extension_launch_provider

(mut self, provider: P) -> Self + where + P: crate::extension_launch_provider::ExtensionLaunchProvider, + { + self.extension_launch_provider = Some(Arc::new(provider)); + self + } + /// Register a connection-level GitHub telemetry forwarding callback /// (internal/experimental). Registering a callback auto-enables telemetry /// forwarding on every session created or resumed on this client; the @@ -830,8 +1007,8 @@ impl ClientOptions { self } - /// Override the directory where the bundled CLI binary is extracted on - /// first use. See [`Self::bundled_cli_extract_dir`]. + /// Override the directory where bundled CLI and runtime artifacts are + /// extracted on first use. See [`Self::bundled_cli_extract_dir`]. /// /// Only applies when the `bundled-cli` cargo feature is on. With /// `bundled-cli` disabled (`default-features = false`), set @@ -852,6 +1029,14 @@ impl ClientOptions { self.mode = mode; self } + + /// Declare the integrating application's identity, forwarded to the runtime on + /// the `server.connect` handshake so its telemetry is attributed to a + /// consistent surface. See [`Self::client_info`]. + pub fn with_client_info(mut self, client_info: ClientInfo) -> Self { + self.client_info = Some(client_info); + self + } } /// Validate a [`SessionFsConfig`] before sending `sessionFs.setProvider`. @@ -988,6 +1173,7 @@ impl std::fmt::Debug for Client { struct ClientInner { child: parking_lot::Mutex>, + process_tree: parking_lot::Mutex>, #[cfg(feature = "bundled-in-process")] /// In-process FFI runtime host, set only for [`Transport::InProcess`]. /// Closing it tears down the native runtime connection. @@ -997,6 +1183,7 @@ struct ClientInner { request_rx: parking_lot::Mutex>>, notification_tx: broadcast::Sender, router: router::SessionRouter, + github_token_registry: Arc, negotiated_protocol_version: OnceLock, state: parking_lot::Mutex, lifecycle_tx: broadcast::Sender, @@ -1007,6 +1194,7 @@ struct ClientInner { /// Inbound `llmInference.*` dispatcher, installed when /// [`ClientOptions::request_handler`] is set. llm_inference: OnceLock>, + extension_launch_provider: Arc, /// Connection-level GitHub telemetry forwarding callback, set from /// [`ClientOptions::on_github_telemetry`]. Drives the /// `enableGitHubTelemetryForwarding` wire flag and the @@ -1018,6 +1206,10 @@ struct ClientInner { /// `None` for stdio and for external-server transport without an /// explicit token. effective_connection_token: Option, + /// Application identity forwarded on the `connect` handshake, set from + /// [`ClientOptions::client_info`]. `None` keeps the runtime's default + /// telemetry attribution. + client_info: Option, /// SDK [`ClientMode`] captured at start time. Drives empty-mode safe /// defaults inside `create_session` / `resume_session`. pub(crate) mode: ClientMode, @@ -1071,6 +1263,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 { .. }) { @@ -1128,6 +1344,7 @@ impl Client { }; let session_fs_config = options.session_fs.clone(); let request_handler = options.request_handler.clone(); + let extension_launch_provider = options.extension_launch_provider.clone(); let session_fs_sqlite_declared = session_fs_config .as_ref() .and_then(|c| c.capabilities.as_ref()) @@ -1141,6 +1358,7 @@ impl Client { let resolve_start = Instant::now(); let resolved = resolve::copilot_binary_with_extract_dir( options.bundled_cli_extract_dir.as_deref(), + true, )?; let resolve_elapsed = resolve_start.elapsed(); timings.program_resolve_ms = Some(StartupTimings::millis(resolve_elapsed)); @@ -1148,7 +1366,7 @@ impl Client { elapsed_ms = resolve_elapsed.as_millis(), "Client::start CLI program resolution complete" ); - info!(path = %resolved.display(), "resolved copilot CLI"); + info!(path = %resolved.display(), "resolved copilot runtime"); #[cfg(windows)] { if let Some(ext) = resolved.extension().and_then(|e| e.to_str()).filter(|ext| { @@ -1196,21 +1414,24 @@ impl Client { reader, writer, None, + None, working_directory, options.on_list_models, + extension_launch_provider.clone(), session_fs_config.is_some(), session_fs_sqlite_declared, options.on_get_trace_context, options.on_github_telemetry, effective_connection_token.clone(), options.mode, + options.client_info, )? } Transport::Tcp { port, connection_token: _, } => { - let (mut child, actual_port, spawn_elapsed, port_wait_elapsed) = + let (mut child, tree, 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)); @@ -1227,18 +1448,21 @@ impl Client { reader, writer, Some(child), + tree, working_directory, options.on_list_models, + extension_launch_provider.clone(), session_fs_config.is_some(), session_fs_sqlite_declared, options.on_get_trace_context, options.on_github_telemetry, effective_connection_token.clone(), options.mode, + options.client_info, )? } Transport::Stdio => { - let (mut child, spawn_elapsed) = + let (mut child, tree, 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"); @@ -1248,14 +1472,17 @@ impl Client { stdout, stdin, Some(child), + tree, working_directory, options.on_list_models, + extension_launch_provider.clone(), session_fs_config.is_some(), session_fs_sqlite_declared, options.on_get_trace_context, options.on_github_telemetry, effective_connection_token.clone(), options.mode, + options.client_info, )? } Transport::InProcess => { @@ -1299,20 +1526,31 @@ impl Client { if !use_logged_in_user { args.push("--no-auto-login".to_string()); } - let host = crate::ffi::FfiHost::create(&program, environment, args)?; + let explicit_cli = std::env::var_os("COPILOT_CLI_PATH") + .map(PathBuf::from) + .filter(|path| path.is_file()); + let host = crate::ffi::FfiHost::create( + &program, + explicit_cli.as_deref(), + environment, + args, + )?; let (reader, writer, shared) = host.start().await?; let client = Self::from_transport( reader, writer, None, + None, working_directory, options.on_list_models, + extension_launch_provider.clone(), session_fs_config.is_some(), session_fs_sqlite_declared, options.on_get_trace_context, options.on_github_telemetry, effective_connection_token.clone(), options.mode, + options.client_info, )?; *client.inner.ffi_host.lock() = Some(shared); client @@ -1333,6 +1571,33 @@ impl Client { elapsed_ms = start_time.elapsed().as_millis(), "Client::start protocol verification complete" ); + let request_dispatcher = request_handler.map(|handler| { + let dispatcher = Arc::new(copilot_request_handler::CopilotRequestDispatcher::new( + handler, + )); + dispatcher.set_client(Arc::downgrade(&client.inner)); + let _ = client.inner.llm_inference.set(dispatcher.clone()); + dispatcher + }); + if client.inner.extension_launch_provider.is_configured() { + client.inner.router.ensure_started( + &client.inner.notification_tx, + &client.inner.request_rx, + client.inner.extension_launch_provider.clone(), + request_dispatcher.clone(), + client.inner.on_github_telemetry.clone(), + client.inner.github_token_registry.clone(), + ); + client.rpc().register_extension_launch_provider().await?; + } + 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| { @@ -1354,20 +1619,17 @@ impl Client { "Client::start session filesystem setup complete" ); } - if let Some(handler) = request_handler { + if let Some(dispatcher) = request_dispatcher { let llm_inference_start = Instant::now(); - let dispatcher = Arc::new(copilot_request_handler::CopilotRequestDispatcher::new( - handler, - )); - dispatcher.set_client(Arc::downgrade(&client.inner)); - let _ = client.inner.llm_inference.set(dispatcher.clone()); // Start the router early (before any session is registered) so the // startup model catalog request is dispatched to the handler. client.inner.router.ensure_started( &client.inner.notification_tx, &client.inner.request_rx, + client.inner.extension_launch_provider.clone(), Some(dispatcher.clone()), client.inner.on_github_telemetry.clone(), + client.inner.github_token_registry.clone(), ); client.rpc().llm_inference().set_provider().await?; let llm_inference_elapsed = llm_inference_start.elapsed(); @@ -1421,14 +1683,45 @@ impl Client { reader, writer, None, + None, + cwd, + None, + None, + false, + false, + None, + None, + None, + ClientMode::default(), + None, + ) + } + + /// Construct a [`Client`] from raw streams with a preset extension launch + /// provider, for integration testing connection-global reverse requests. + #[doc(hidden)] + #[cfg(any(test, feature = "test-support"))] + pub fn from_streams_with_extension_launch_provider( + reader: impl AsyncRead + Unpin + Send + 'static, + writer: impl AsyncWrite + Unpin + Send + 'static, + cwd: PathBuf, + provider: Arc, + ) -> Result { + Self::from_transport( + reader, + writer, + None, + None, cwd, None, + Some(provider), false, false, None, None, None, ClientMode::default(), + None, ) } @@ -1450,14 +1743,17 @@ impl Client { reader, writer, None, + None, cwd, None, + None, false, false, Some(provider), None, None, ClientMode::default(), + None, ) } @@ -1475,14 +1771,17 @@ impl Client { reader, writer, None, + None, cwd, None, + None, false, false, None, None, token, ClientMode::default(), + None, ) } @@ -1500,14 +1799,17 @@ impl Client { reader, writer, None, + None, cwd, None, + None, false, false, None, Some(on_github_telemetry), None, ClientMode::default(), + None, ) } @@ -1521,19 +1823,53 @@ impl Client { generate_connection_token() } + /// Construct a [`Client`] from raw streams with a preset + /// [`ClientInfo`], for integration testing the `connect` handshake's + /// application-identity forwarding path. + #[doc(hidden)] + #[cfg(any(test, feature = "test-support"))] + pub fn from_streams_with_client_info( + reader: impl AsyncRead + Unpin + Send + 'static, + writer: impl AsyncWrite + Unpin + Send + 'static, + cwd: PathBuf, + client_info: Option, + ) -> Result { + Self::from_transport( + reader, + writer, + None, + None, + cwd, + None, + None, + false, + false, + None, + None, + None, + ClientMode::default(), + client_info, + ) + } + #[allow(clippy::too_many_arguments)] fn from_transport( reader: impl AsyncRead + Unpin + Send + 'static, writer: impl AsyncWrite + Unpin + Send + 'static, child: Option, + process_tree: Option, cwd: PathBuf, on_list_models: Option>, + extension_launch_provider: Option< + Arc, + >, session_fs_configured: bool, session_fs_sqlite_declared: bool, on_get_trace_context: Option>, on_github_telemetry: Option, effective_connection_token: Option, mode: ClientMode, + client_info: Option, ) -> Result { let setup_start = Instant::now(); let (request_tx, request_rx) = mpsc::unbounded_channel::(); @@ -1548,9 +1884,16 @@ impl Client { let pid = child.as_ref().and_then(|c| c.id()); info!(pid = ?pid, "copilot CLI client ready"); + let github_token_registry = Arc::new(github_token::GitHubTokenRegistry::new()); + let extension_launch_provider = Arc::new( + extension_launch_provider::ExtensionLaunchProviderDispatcher::new( + extension_launch_provider, + ), + ); let client = Self { inner: Arc::new(ClientInner { child: parking_lot::Mutex::new(child), + process_tree: parking_lot::Mutex::new(process_tree), #[cfg(feature = "bundled-in-process")] ffi_host: parking_lot::Mutex::new(None), rpc, @@ -1558,6 +1901,7 @@ impl Client { request_rx: parking_lot::Mutex::new(Some(request_rx)), notification_tx: notification_broadcast_tx, router: router::SessionRouter::new(), + github_token_registry: github_token_registry.clone(), negotiated_protocol_version: OnceLock::new(), state: parking_lot::Mutex::new(ConnectionState::Connected), lifecycle_tx: broadcast::channel(256).0, @@ -1566,13 +1910,17 @@ impl Client { session_fs_configured, session_fs_sqlite_declared, llm_inference: OnceLock::new(), + extension_launch_provider: extension_launch_provider.clone(), on_github_telemetry, on_get_trace_context, effective_connection_token, mode, + client_info, startup_timings: OnceLock::new(), }), }; + github_token_registry.set_client(Arc::downgrade(&client.inner)); + extension_launch_provider.set_client(Arc::downgrade(&client.inner)); client.spawn_lifecycle_dispatcher(); debug!( elapsed_ms = setup_start.elapsed().as_millis(), @@ -1586,8 +1934,8 @@ impl Client { /// notifications via [`ClientInner::lifecycle_tx`] to subscribers /// returned by [`Self::subscribe_lifecycle`]. fn spawn_lifecycle_dispatcher(&self) { - let inner = Arc::clone(&self.inner); - let mut notif_rx = inner.notification_tx.subscribe(); + let mut notif_rx = self.inner.notification_tx.subscribe(); + let lifecycle_tx = self.inner.lifecycle_tx.clone(); tokio::spawn(async move { loop { match notif_rx.recv().await { @@ -1611,7 +1959,7 @@ impl Client { }; // `send` only errors when there are no subscribers — that's // the normal case before any consumer calls subscribe_lifecycle. - let _ = inner.lifecycle_tx.send(event); + let _ = lifecycle_tx.send(event); } Err(tokio::sync::broadcast::error::RecvError::Lagged(n)) => { warn!(missed = n, "lifecycle dispatcher lagged"); @@ -1624,6 +1972,7 @@ impl Client { fn build_command(program: &Path, options: &ClientOptions, working_directory: &Path) -> Command { let mut command = Command::new(program); + command.kill_on_drop(true); for arg in &options.prefix_args { command.arg(arg); } @@ -1684,13 +2033,6 @@ impl Client { .stdout(Stdio::piped()) .stderr(Stdio::piped()); - #[cfg(windows)] - { - use std::os::windows::process::CommandExt; - const CREATE_NO_WINDOW: u32 = 0x08000000; - command.as_std_mut().creation_flags(CREATE_NO_WINDOW); - } - command } @@ -1747,7 +2089,7 @@ impl Client { program: &Path, options: &ClientOptions, working_directory: &Path, - ) -> Result<(Child, Duration)> { + ) -> Result<(Child, Option, Duration)> { info!(cwd = ?working_directory, program = %program.display(), "spawning copilot CLI (stdio)"); let mut command = Self::build_command(program, options, working_directory); command @@ -1759,13 +2101,13 @@ impl Client { .args(&options.extra_args) .stdin(Stdio::piped()); let spawn_start = Instant::now(); - let child = command.spawn()?; + let (child, tree) = process_tree::spawn(&mut command)?; let spawn_elapsed = spawn_start.elapsed(); debug!( elapsed_ms = spawn_elapsed.as_millis(), "Client::spawn_stdio subprocess spawned" ); - Ok((child, spawn_elapsed)) + Ok((child, tree, spawn_elapsed)) } async fn spawn_tcp( @@ -1773,7 +2115,13 @@ impl Client { options: &ClientOptions, working_directory: &Path, port: u16, - ) -> Result<(Child, u16, Duration, Duration)> { + ) -> Result<( + Child, + Option, + 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 @@ -1785,7 +2133,7 @@ impl Client { .args(&options.extra_args) .stdin(Stdio::null()); let spawn_start = Instant::now(); - let mut child = command.spawn()?; + let (mut child, tree) = process_tree::spawn(&mut command)?; let spawn_elapsed = spawn_start.elapsed(); debug!( elapsed_ms = spawn_elapsed.as_millis(), @@ -1832,7 +2180,7 @@ impl Client { "Client::spawn_tcp TCP port wait complete" ); info!(port = %actual_port, "CLI server listening"); - Ok((child, actual_port, spawn_elapsed, port_wait_elapsed)) + Ok((child, tree, actual_port, spawn_elapsed, port_wait_elapsed)) } fn drain_stderr(child: &mut Child) { @@ -1988,8 +2336,10 @@ impl Client { self.inner.router.ensure_started( &self.inner.notification_tx, &self.inner.request_rx, + self.inner.extension_launch_provider.clone(), self.inner.llm_inference.get().cloned(), self.inner.on_github_telemetry.clone(), + self.inner.github_token_registry.clone(), ); self.inner.router.register(session_id) } @@ -1999,6 +2349,26 @@ impl Client { self.inner.router.unregister(session_id); } + pub(crate) fn register_github_token_provider( + &self, + provider: Arc, + ) -> github_token::GitHubTokenRegistration { + self.inner.router.ensure_started( + &self.inner.notification_tx, + &self.inner.request_rx, + self.inner.extension_launch_provider.clone(), + self.inner.llm_inference.get().cloned(), + self.inner.on_github_telemetry.clone(), + self.inner.github_token_registry.clone(), + ); + let id = self.inner.github_token_registry.register(provider); + github_token::GitHubTokenRegistration::new(self.inner.github_token_registry.clone(), id) + } + + pub(crate) fn retire_github_token_provider(&self, session_id: &SessionId) { + self.inner.github_token_registry.retire_session(session_id); + } + /// Returns the protocol version negotiated with the CLI server, if any. /// /// Set during [`start`](Self::start). Returns `None` if the server didn't @@ -2107,6 +2477,15 @@ impl Client { .on_github_telemetry .is_some() .then_some(true), + // Declare the integrating application's identity so the runtime attributes + // the telemetry it emits on this connection to a consistent surface + // instead of its own build. `None` when the app didn't supply it, and + // empty fields are dropped. + client_info: self + .inner + .client_info + .as_ref() + .and_then(ClientInfo::to_wire), }; let value = self .call( @@ -2193,6 +2572,7 @@ impl Client { Some(serde_json::json!({ "sessionId": session_id })), ) .await?; + self.retire_github_token_provider(session_id); Ok(()) } @@ -2204,8 +2584,10 @@ impl Client { self.inner.router.ensure_started( &self.inner.notification_tx, &self.inner.request_rx, + self.inner.extension_launch_provider.clone(), self.inner.llm_inference.get().cloned(), self.inner.on_github_telemetry.clone(), + self.inner.github_token_registry.clone(), ); } @@ -2229,6 +2611,7 @@ impl Client { } self.inner.router.unregister(&session_id); } + self.inner.github_token_registry.clear(); match self.list_sessions(None).await { Ok(sessions) => { @@ -2348,11 +2731,12 @@ impl Client { /// Cooperatively shut down the client and the CLI child process. /// /// Walks every still-registered session and sends `session.destroy` - /// for each one, asks SDK-owned runtimes to shut down, then kills the - /// CLI child. Errors from per-session destroys, runtime shutdown, and - /// the final child-kill are collected into - /// [`StopErrors`] rather than short-circuiting on the first failure - /// — so callers see the full picture of teardown. + /// for each one, asks SDK-owned runtimes to shut down, terminates the + /// Windows-owned CLI Job Object when present, and reaps the root process. + /// Errors from per-session destroys, runtime shutdown, and final process + /// termination are collected into [`StopErrors`] rather than + /// short-circuiting on the first failure — so callers see the full picture + /// of teardown. /// /// If you have already called [`Session::disconnect`] on every /// session this client created, the per-session destroy step is a @@ -2375,6 +2759,7 @@ impl Client { let pid = self.pid(); info!(pid = ?pid, "stopping CLI process"); let mut errors: Vec = Vec::new(); + self.inner.extension_launch_provider.clear(); // Snapshot the registered session IDs without holding the router // lock across the destroy RPCs. @@ -2398,6 +2783,7 @@ impl Client { } self.inner.router.unregister(&session_id); } + self.inner.github_token_registry.clear(); let should_shutdown_runtime = self.inner.child.lock().is_some(); #[cfg(feature = "bundled-in-process")] @@ -2439,8 +2825,14 @@ impl Client { } let child = self.inner.child.lock().take(); + let process_tree = self.inner.process_tree.lock().take(); *self.inner.state.lock() = ConnectionState::Disconnected; *self.inner.models_cache.lock() = Arc::new(tokio::sync::OnceCell::new()); + if let Some(process_tree) = process_tree + && let Err(error) = process_tree.terminate() + { + errors.push(error.into()); + } if let Some(mut child) = child { match child.try_wait() { Ok(Some(_status)) => {} @@ -2459,12 +2851,12 @@ impl Client { } } - // The runtime.shutdown RPC above already asked the runtime to clean up; - // closing here tears down the transport. + // Provider registration is scoped to the connection. Closing the + // transport unregisters it and prevents stale callbacks after stop. + self.inner.rpc.force_close(); #[cfg(feature = "bundled-in-process")] { if let Some(host) = self.inner.ffi_host.lock().take() { - self.inner.rpc.force_close(); host.close(); } } @@ -2481,10 +2873,9 @@ impl Client { /// /// Synchronous fallback when [`stop`](Self::stop) is unsuitable — for /// example when the awaiting tokio runtime is shutting down or the - /// process is wedged on I/O. Sends a kill signal without awaiting - /// reaper completion and immediately drops all per-session router - /// state so dependent tasks observe a closed channel rather than a - /// hang. + /// process is wedged on I/O. Terminates the Windows-owned CLI Job Object + /// when present and immediately drops all per-session router state so + /// dependent tasks observe a closed channel rather than a hang. /// /// # Cancel safety /// @@ -2509,6 +2900,12 @@ impl Client { pub fn force_stop(&self) { let pid = self.pid(); info!(pid = ?pid, "force-stopping CLI process"); + self.inner.extension_launch_provider.clear(); + if let Some(process_tree) = self.inner.process_tree.lock().take() + && let Err(error) = process_tree.terminate() + { + error!(pid = ?pid, %error, "failed to terminate CLI process tree"); + } if let Some(mut child) = self.inner.child.lock().take() && let Err(e) = child.start_kill() { @@ -2524,6 +2921,7 @@ impl Client { // Drop all session channels so any awaiters see a closed channel // instead of waiting for responses that will never arrive. self.inner.router.clear(); + self.inner.github_token_registry.clear(); *self.inner.state.lock() = ConnectionState::Disconnected; *self.inner.models_cache.lock() = Arc::new(tokio::sync::OnceCell::new()); } @@ -2569,8 +2967,13 @@ impl Client { impl Drop for ClientInner { fn drop(&mut self) { + let pid = self.child.lock().as_ref().and_then(Child::id); + if let Some(process_tree) = self.process_tree.lock().take() + && let Err(error) = process_tree.terminate() + { + error!(pid = ?pid, %error, "failed to terminate CLI process tree on drop"); + } if let Some(ref mut child) = *self.child.lock() { - let pid = child.id(); if let Err(e) = child.start_kill() { error!(pid = ?pid, error = %e, "failed to kill CLI process on drop"); } else { @@ -3215,10 +3618,113 @@ mod tests { client.force_stop(); } + #[cfg(any(unix, windows))] + #[tokio::test] + async fn dropping_last_client_kills_spawned_cli() { + let temp = tempfile::tempdir().unwrap(); + let ready = temp.path().join("ready"); + let survived = temp.path().join("survived"); + let child = test_child_command(temp.path(), &ready, &survived) + .spawn() + .unwrap(); + let (client_write, _server_read) = tokio::io::duplex(64); + let (_server_write, client_read) = tokio::io::duplex(64); + let client = Client::from_transport( + client_read, + client_write, + Some(child), + None, + temp.path().to_path_buf(), + None, + None, + false, + false, + None, + None, + None, + ClientMode::default(), + None, + ) + .unwrap(); + + wait_for_test_child(&ready).await; + drop(client); + + assert_test_child_killed(&survived).await; + } + + #[cfg(any(unix, windows))] + #[tokio::test] + async fn spawned_child_is_killed_when_dropped() { + let temp = tempfile::tempdir().unwrap(); + let ready = temp.path().join("ready"); + let survived = temp.path().join("survived"); + let child = test_child_command(temp.path(), &ready, &survived) + .spawn() + .unwrap(); + + wait_for_test_child(&ready).await; + drop(child); + + assert_test_child_killed(&survived).await; + } + + #[cfg(any(unix, windows))] + fn test_child_command(temp: &Path, ready: &Path, survived: &Path) -> Command { + #[cfg(unix)] + let mut command = { + let mut command = + Client::build_command(Path::new("sh"), &ClientOptions::default(), temp); + command.args([ + "-c", + "printf ready > \"$READY\"; sleep 1; printf survived > \"$SURVIVED\"", + ]); + command + }; + #[cfg(windows)] + let mut command = { + let mut command = + Client::build_command(Path::new("powershell.exe"), &ClientOptions::default(), temp); + command.args([ + "-NoLogo", + "-NoProfile", + "-NonInteractive", + "-Command", + "Set-Content -LiteralPath $env:READY ready; Start-Sleep -Seconds 1; Set-Content -LiteralPath $env:SURVIVED survived", + ]); + command + }; + command.env("READY", ready).env("SURVIVED", survived); + command + } + + #[cfg(any(unix, windows))] + async fn wait_for_test_child(ready: &Path) { + let deadline = tokio::time::Instant::now() + Duration::from_secs(30); + while !ready.exists() { + assert!( + tokio::time::Instant::now() < deadline, + "child did not report readiness" + ); + tokio::time::sleep(Duration::from_millis(10)).await; + } + } + + #[cfg(any(unix, windows))] + async fn assert_test_child_killed(survived: &Path) { + tokio::time::sleep(Duration::from_millis(1500)).await; + + assert!( + !survived.exists(), + "child survived after its owner was dropped" + ); + } + fn client_with_list_models_handler(handler: Arc) -> Client { Client { inner: Arc::new(ClientInner { child: parking_lot::Mutex::new(None), + process_tree: parking_lot::Mutex::new(None), #[cfg(feature = "bundled-in-process")] ffi_host: parking_lot::Mutex::new(None), rpc: { @@ -3232,6 +3738,7 @@ mod tests { request_rx: parking_lot::Mutex::new(None), notification_tx: broadcast::channel(16).0, router: router::SessionRouter::new(), + github_token_registry: Arc::new(github_token::GitHubTokenRegistry::new()), negotiated_protocol_version: OnceLock::new(), state: parking_lot::Mutex::new(ConnectionState::Connected), lifecycle_tx: broadcast::channel(16).0, @@ -3240,10 +3747,14 @@ mod tests { session_fs_configured: false, session_fs_sqlite_declared: false, llm_inference: OnceLock::new(), + extension_launch_provider: Arc::new( + extension_launch_provider::ExtensionLaunchProviderDispatcher::new(None), + ), on_github_telemetry: None, on_get_trace_context: None, effective_connection_token: None, mode: ClientMode::default(), + client_info: None, startup_timings: OnceLock::new(), }), } diff --git a/rust/src/permission.rs b/rust/src/permission.rs index e353ce3153..57c0785708 100644 --- a/rust/src/permission.rs +++ b/rust/src/permission.rs @@ -97,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, } } @@ -152,7 +151,10 @@ 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(_), + .. + } )); } @@ -164,7 +166,10 @@ mod tests { assert!(matches!( h.handle(SessionId::from("s"), RequestId::new("1"), request) .await, - PermissionResult::Decision(crate::types::PermissionDecision::UserNotAvailable(_)) + PermissionResult::Decision { + decision: crate::types::PermissionDecision::UserNotAvailable(_), + .. + } )); } @@ -174,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(_), + .. + } )); } @@ -184,7 +192,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(_), + .. + } )); } @@ -208,7 +219,10 @@ mod tests { assert!(matches!( h.handle(SessionId::from("s"), RequestId::new("1"), request) .await, - PermissionResult::Decision(crate::types::PermissionDecision::Reject(_)) + PermissionResult::Decision { + decision: crate::types::PermissionDecision::Reject(_), + .. + } )); } @@ -233,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(_), + .. + } )); } @@ -256,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/process_tree.rs b/rust/src/process_tree.rs new file mode 100644 index 0000000000..8dc7a451c5 --- /dev/null +++ b/rust/src/process_tree.rs @@ -0,0 +1,195 @@ +//! Windows crash-safe ownership of an SDK-spawned CLI process. +//! +//! A Job Object with `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE` lets Windows +//! terminate the CLI when the SDK-hosting process exits abruptly, even when +//! Rust cleanup code never runs. Other platforms retain Tokio's direct-child +//! ownership because no equivalent product failure has been demonstrated. + +use std::io; + +use tokio::process::{Child, Command}; + +pub(crate) fn spawn(command: &mut Command) -> io::Result<(Child, Option)> { + #[cfg(windows)] + { + platform::spawn(command).map(|(child, tree)| (child, Some(ProcessTree(Some(tree))))) + } + #[cfg(not(windows))] + { + command.spawn().map(|child| (child, None)) + } +} + +pub(crate) struct ProcessTree(Option); + +impl ProcessTree { + pub(crate) fn terminate(mut self) -> io::Result<()> { + self.0.take().expect("process tree is armed").terminate() + } +} + +impl Drop for ProcessTree { + fn drop(&mut self) { + if let Some(tree) = self.0.take() { + let _ = tree.terminate(); + } + } +} + +#[cfg(not(windows))] +mod platform { + pub(super) struct Tree; + + impl Tree { + pub(super) fn terminate(&self) -> std::io::Result<()> { + unreachable!("process-tree ownership is Windows-only") + } + } +} + +#[cfg(windows)] +mod platform { + use std::mem::size_of; + use std::os::windows::process::CommandExt; + use std::{io, ptr}; + + use tokio::process::{Child, Command}; + use windows_sys::Win32::Foundation::{CloseHandle, HANDLE, INVALID_HANDLE_VALUE}; + use windows_sys::Win32::System::Diagnostics::ToolHelp::{ + CreateToolhelp32Snapshot, TH32CS_SNAPTHREAD, THREADENTRY32, Thread32First, Thread32Next, + }; + use windows_sys::Win32::System::JobObjects::{ + AssignProcessToJobObject, CreateJobObjectW, JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, + JOBOBJECT_EXTENDED_LIMIT_INFORMATION, JobObjectExtendedLimitInformation, + SetInformationJobObject, TerminateJobObject, + }; + use windows_sys::Win32::System::Threading::{ + CREATE_NO_WINDOW, CREATE_SUSPENDED, OpenThread, ResumeThread, THREAD_SUSPEND_RESUME, + }; + + struct OwnedHandle(HANDLE); + + // SAFETY: Win32 handles may be used and closed from any thread. + unsafe impl Send for OwnedHandle {} + unsafe impl Sync for OwnedHandle {} + + impl Drop for OwnedHandle { + fn drop(&mut self) { + // SAFETY: this value uniquely owns a valid handle. + unsafe { + CloseHandle(self.0); + } + } + } + + pub(super) struct Tree { + job: OwnedHandle, + } + + pub(super) fn spawn(command: &mut Command) -> io::Result<(Child, Tree)> { + // The root cannot run or create descendants before Job assignment. + command + .as_std_mut() + .creation_flags(CREATE_NO_WINDOW | CREATE_SUSPENDED); + let mut child = command.spawn()?; + match attach_and_resume(&child) { + Ok(tree) => Ok((child, tree)), + Err(error) => { + let _ = child.start_kill(); + Err(error) + } + } + } + + fn attach_and_resume(child: &Child) -> io::Result { + // SAFETY: null security attributes and name create a private, + // non-inheritable Job Object. + let raw_job = unsafe { CreateJobObjectW(ptr::null(), ptr::null()) }; + if raw_job.is_null() { + return Err(io::Error::last_os_error()); + } + let job = OwnedHandle(raw_job); + + let mut limits = JOBOBJECT_EXTENDED_LIMIT_INFORMATION::default(); + limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + // SAFETY: `limits` has the layout required by the selected info class. + if unsafe { + SetInformationJobObject( + job.0, + JobObjectExtendedLimitInformation, + ptr::from_ref(&limits).cast(), + size_of::() as u32, + ) + } == 0 + { + return Err(io::Error::last_os_error()); + } + + let process = child.raw_handle().ok_or_else(|| { + io::Error::new( + io::ErrorKind::NotFound, + "CLI exited before Job Object assignment", + ) + })?; + // SAFETY: both handles are valid and the child is still suspended. + if unsafe { AssignProcessToJobObject(job.0, process.cast()) } == 0 { + return Err(io::Error::last_os_error()); + } + + resume_initial_thread(child.id().ok_or_else(|| { + io::Error::new(io::ErrorKind::NotFound, "CLI exited before thread resume") + })?)?; + Ok(Tree { job }) + } + + fn resume_initial_thread(pid: u32) -> io::Result<()> { + // SAFETY: the returned snapshot handle is owned and closed below. + let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0) }; + if snapshot == INVALID_HANDLE_VALUE { + return Err(io::Error::last_os_error()); + } + let snapshot = OwnedHandle(snapshot); + let mut entry = THREADENTRY32 { + dwSize: size_of::() as u32, + ..Default::default() + }; + + // SAFETY: `entry` has the documented size and remains live throughout + // enumeration. + let mut found = unsafe { Thread32First(snapshot.0, &mut entry) } != 0; + while found { + if entry.th32OwnerProcessID == pid { + // SAFETY: the thread id came from the live system snapshot. + let raw_thread = + unsafe { OpenThread(THREAD_SUSPEND_RESUME, 0, entry.th32ThreadID) }; + if raw_thread.is_null() { + return Err(io::Error::last_os_error()); + } + let thread = OwnedHandle(raw_thread); + // SAFETY: this is the root's suspended initial thread. + if unsafe { ResumeThread(thread.0) } == u32::MAX { + return Err(io::Error::last_os_error()); + } + return Ok(()); + } + // SAFETY: same valid snapshot and initialized entry as above. + found = unsafe { Thread32Next(snapshot.0, &mut entry) } != 0; + } + + Err(io::Error::new( + io::ErrorKind::NotFound, + "CLI initial thread was not found", + )) + } + + impl Tree { + pub(super) fn terminate(&self) -> io::Result<()> { + // SAFETY: the handle is a live Job Object owned by this value. + if unsafe { TerminateJobObject(self.job.0, 1) } != 0 { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } + } + } +} diff --git a/rust/src/resolve.rs b/rust/src/resolve.rs index 1c88283a27..d8b996a11a 100644 --- a/rust/src/resolve.rs +++ b/rust/src/resolve.rs @@ -5,9 +5,9 @@ //! 1. An explicit path supplied by the application via //! [`CliProgram::Path`](crate::CliProgram::Path). //! 2. The `COPILOT_CLI_PATH` environment variable. -//! 3. The bundled CLI embedded in this crate at build time (when the +//! 3. The bundled program embedded in this crate at build time (when the //! `bundled-cli` cargo feature is on, the default). -//! 4. The build-time-extracted CLI in the per-user cache (when +//! 4. The build-time-extracted program in the per-user cache (when //! `bundled-cli` is off). //! //! There is no PATH scanning and no walking of standard install locations. @@ -35,6 +35,7 @@ use crate::{Error, ErrorKind}; /// under it. pub(crate) fn copilot_binary_with_extract_dir( extract_dir: Option<&Path>, + use_runtime_wrapper: bool, ) -> Result { if let Ok(value) = env::var("COPILOT_CLI_PATH") { let candidate = PathBuf::from(&value); @@ -49,11 +50,21 @@ pub(crate) fn copilot_binary_with_extract_dir( #[cfg(feature = "bundled-cli")] { - let bundled = match extract_dir { - Some(dir) => crate::embeddedcli::install_at(dir), - None => crate::embeddedcli::path(), + let bundled = if use_runtime_wrapper { + match extract_dir { + Some(dir) => crate::embeddedcli::install_runtime_at(dir), + None => crate::embeddedcli::runtime_path(), + } + } else { + match extract_dir { + Some(dir) => crate::embeddedcli::install_at(dir), + None => crate::embeddedcli::path(), + } }; if let Some(path) = bundled { + if use_runtime_wrapper { + validate_runtime_pair(&path)?; + } return Ok(path); } } @@ -61,16 +72,21 @@ pub(crate) fn copilot_binary_with_extract_dir( #[cfg(not(feature = "bundled-cli"))] { let _ = extract_dir; - if let Some(path) = extracted_cli_path() { - return Ok(path); + if let Some(program) = extracted_program(use_runtime_wrapper) { + return Ok(program); } } + let binary_name = if use_runtime_wrapper { + runtime_binary_name() + } else { + cli_binary_name() + }; Err(ErrorKind::BinaryNotFound { - name: "copilot".into(), + name: binary_name.into(), hint: Some( "the Copilot CLI is not bundled in this build of github-copilot-sdk and \ - COPILOT_CLI_PATH is not set. Either keep the default `bundled-cli` cargo \ + no applicable path override is set. Either keep the default `bundled-cli` cargo \ feature enabled, set COPILOT_CLI_PATH, or supply an explicit path via \ `CliProgram::Path(...)` on `ClientOptions::program`." .into(), @@ -79,7 +95,7 @@ pub(crate) fn copilot_binary_with_extract_dir( .into()) } -/// Path to the CLI extracted into the per-user cache by `build.rs` when +/// Path to the program extracted into the per-user cache by `build.rs` when /// `bundled-cli` is disabled. Returns `None` if the cached file is missing /// (e.g. the user deleted the cache after building, or built with /// `COPILOT_SKIP_CLI_DOWNLOAD`). @@ -93,14 +109,8 @@ pub(crate) fn copilot_binary_with_extract_dir( /// `$HOME` / `$LOCALAPPDATA` into the artifact, breaks sccache across /// machines, and prevents copying `target/` between hosts. #[cfg(all(not(feature = "bundled-cli"), has_extracted_cli))] -fn extracted_cli_path() -> Option { +fn extracted_program(use_runtime_wrapper: bool) -> Option { let version = env!("COPILOT_SDK_CLI_VERSION"); - let binary = if cfg!(windows) { - "copilot.exe" - } else { - "copilot" - }; - let dir = match env::var_os("COPILOT_CLI_EXTRACT_DIR") { Some(custom) => PathBuf::from(custom), None => dirs::cache_dir() @@ -110,8 +120,16 @@ fn extracted_cli_path() -> Option { .join(sanitize_version(version)), }; - let path = dir.join(binary); - if path.is_file() { + let path = dir.join(if use_runtime_wrapper { + runtime_binary_name() + } else { + cli_binary_name() + }); + if use_runtime_wrapper { + if validate_runtime_pair(&path).is_ok() { + return Some(path); + } + } else if path.is_file() { return Some(path); } warn!( @@ -125,10 +143,83 @@ fn extracted_cli_path() -> Option { /// build opted out via `COPILOT_SKIP_CLI_DOWNLOAD`. In both cases there's /// no binary to look up, so the resolver returns `None` immediately. #[cfg(all(not(feature = "bundled-cli"), not(has_extracted_cli)))] -fn extracted_cli_path() -> Option { +fn extracted_program(_use_runtime_wrapper: bool) -> Option { None } +fn validate_runtime_pair(wrapper: &Path) -> Result<(), Error> { + let wrapper_valid = wrapper + .metadata() + .map(|metadata| metadata.is_file() && metadata.len() > 0) + .unwrap_or(false); + let runtime_node = wrapper + .parent() + .map(|parent| parent.join("runtime.node")) + .unwrap_or_else(|| PathBuf::from("runtime.node")); + let runtime_valid = runtime_node + .metadata() + .map(|metadata| metadata.is_file() && metadata.len() > 0) + .unwrap_or(false); + if wrapper_valid && runtime_valid { + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + + let metadata = wrapper.metadata().map_err(|e| { + Error::with_message( + ErrorKind::InvalidConfig, + format!( + "failed to inspect Copilot runtime wrapper permissions at '{}': {e}", + wrapper.display() + ), + ) + })?; + if metadata.permissions().mode() & 0o111 == 0 { + let mut permissions = metadata.permissions(); + permissions.set_mode(permissions.mode() | 0o111); + std::fs::set_permissions(wrapper, permissions).map_err(|e| { + Error::with_message( + ErrorKind::InvalidConfig, + format!( + "failed to make Copilot runtime wrapper executable at '{}': {e}", + wrapper.display() + ), + ) + })?; + } + } + return Ok(()); + } + let detail = format!( + "The runtime wrapper and its adjacent runtime.node must both be non-empty files; checked '{}' and '{}'", + wrapper.display(), + runtime_node.display() + ); + Err(Error::with_message( + ErrorKind::BinaryNotFound { + name: runtime_binary_name().into(), + hint: Some(detail.clone()), + }, + detail, + )) +} + +fn cli_binary_name() -> &'static str { + if cfg!(windows) { + "copilot.exe" + } else { + "copilot" + } +} + +fn runtime_binary_name() -> &'static str { + if cfg!(windows) { + "copilot-runtime.exe" + } else { + "copilot-runtime" + } +} + /// Replace characters outside `[a-zA-Z0-9._-]` with `_`. Kept in sync /// with `build.rs::sanitize_version` and `embeddedcli::sanitize_version` /// so all three resolve to the same cache directory for any given @@ -143,3 +234,29 @@ fn sanitize_version(version: &str) -> String { }) .collect() } + +#[cfg(test)] +mod tests { + use std::fs; + + use tempfile::tempdir; + + use super::validate_runtime_pair; + + #[test] + fn runtime_pair_requires_adjacent_nonempty_runtime_node() { + let dir = tempdir().expect("temp dir"); + let wrapper = dir.path().join(if cfg!(windows) { + "copilot-runtime.exe" + } else { + "copilot-runtime" + }); + fs::write(&wrapper, b"wrapper").expect("write wrapper"); + + let error = validate_runtime_pair(&wrapper).expect_err("runtime.node is required"); + assert!(error.to_string().contains("runtime.node")); + + fs::write(dir.path().join("runtime.node"), b"runtime").expect("write runtime.node"); + validate_runtime_pair(&wrapper).expect("complete pair is valid"); + } +} diff --git a/rust/src/router.rs b/rust/src/router.rs index adc1923824..4815d0e1c5 100644 --- a/rust/src/router.rs +++ b/rust/src/router.rs @@ -85,8 +85,12 @@ impl SessionRouter { &self, notification_tx: &broadcast::Sender, request_rx: &Mutex>>, + extension_launch_provider: Arc< + crate::extension_launch_provider::ExtensionLaunchProviderDispatcher, + >, llm_inference: Option>, github_telemetry: Option, + github_token_registry: Arc, ) { let mut started = self.started.lock(); if *started { @@ -181,6 +185,14 @@ impl SessionRouter { let sessions = self.sessions.clone(); tokio::spawn(async move { while let Some(request) = rx.recv().await { + if request.method == crate::extension_launch_provider::RESOLVE_METHOD { + extension_launch_provider.dispatch(request).await; + continue; + } + if request.method == "gitHubToken.getToken" { + github_token_registry.dispatch(request).await; + continue; + } // Client-global `llmInference.*` requests carry no routable // session and are handled by the inference dispatcher. if request.method.starts_with("llmInference.") { diff --git a/rust/src/session.rs b/rust/src/session.rs index d505541a50..509485e39e 100644 --- a/rust/src/session.rs +++ b/rust/src/session.rs @@ -1,23 +1,25 @@ use std::collections::HashMap; +use std::panic::AssertUnwindSafe; use std::path::{Path, PathBuf}; use std::sync::Arc; use std::time::{Duration, Instant}; +use futures_util::FutureExt; use parking_lot::Mutex as ParkingLotMutex; use serde_json::Value; use tokio::sync::oneshot; use tokio::task::JoinHandle; use tokio_util::sync::CancellationToken; -use tracing::{Instrument, warn}; +use tracing::{Instrument, error, 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, - SessionCanvasClosedData, SessionErrorData, SessionEventType, + SessionCanvasClosedData, SessionErrorData, SessionEventType, SessionIdleData, SessionMode, }; use crate::handler::{ AutoModeSwitchHandler, AutoModeSwitchResponse, ElicitationHandler, ExitPlanModeHandler, @@ -66,6 +68,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>>, @@ -183,6 +192,8 @@ pub struct Session { open_canvases: Arc>>, /// Broadcast channel for runtime event subscribers — see [`Session::subscribe`]. event_tx: tokio::sync::broadcast::Sender, + github_token_registration: + ParkingLotMutex>, } impl Session { @@ -314,10 +325,15 @@ impl Session { /// Stop the internal event loop. Called automatically on [`destroy`](Self::destroy). /// /// Cooperative: signals shutdown via the session's [`CancellationToken`] - /// and awaits the loop's natural exit rather than aborting the task. - /// Any in-flight handler (permission callback, tool call, elicitation - /// response) completes before the loop exits, so the CLI never sees a - /// half-handled request. See RFD-400 review finding #3. + /// and awaits the loop's natural exit rather than aborting the task, so + /// the loop always stops between iterations instead of at an arbitrary + /// await point. See RFD-400 review finding #3. + /// + /// Inbound requests are dispatched to their own spawned tasks, which this + /// call does not await. A handler (permission callback, tool call, + /// elicitation response) still running at teardown may therefore outlive + /// the loop, and its response can be lost if the connection closes first. + /// Await your own handler work before calling this if it must complete. pub async fn stop_event_loop(&self) { self.shutdown.cancel(); let handle = self.event_loop.lock().take(); @@ -533,13 +549,20 @@ impl Session { pub async fn set_model(&self, model: &str, opts: Option) -> Result<(), Error> { let opts = opts.unwrap_or_default(); let request = ModelSwitchToRequest { + compaction_decision: None, + context_tier: opts.context_tier, + defer_if_model_change_queued: None, + model_capabilities: opts.model_capabilities, + model_change_scope: None, model_id: model.to_string(), + picker_persistence: None, reasoning_effort: opts.reasoning_effort, reasoning_summary: opts.reasoning_summary, + repo_scope: None, + require_available: None, + run_compaction_preflight: None, + source: None, 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(()) @@ -570,6 +593,7 @@ impl Session { .await?; self.stop_event_loop().await; self.client.unregister_session(&self.id); + self.github_token_registration.lock().take(); Ok(()) } @@ -636,17 +660,18 @@ impl Drop for Session { fn drop(&mut self) { // Cooperative shutdown: cancel the event loop's token to signal // exit between iterations. The loop will see the cancellation on - // its next select poll and break cleanly without interrupting an - // in-flight handler. We do NOT abort the JoinHandle — that would - // land at any await point in the loop body, potentially leaving - // the CLI with an unanswered request id. RFD-400 review finding - // #3. + // its next select poll and break cleanly. We do NOT abort the + // JoinHandle — that would land at any await point in the loop body, + // potentially leaving the CLI with an unanswered request id. + // RFD-400 review finding #3. Requests already dispatched to their + // own tasks are not tracked here and may outlive the session. // // The handle itself is left in `event_loop` to be reaped by the // tokio runtime when it next polls; we intentionally don't await // it here because Drop is sync. self.shutdown.cancel(); self.client.unregister_session(&self.id); + self.github_token_registration.lock().take(); } } @@ -889,6 +914,7 @@ impl Client { let opt_custom_agents_local_only = config.custom_agents_local_only; let opt_coauthor_enabled = config.coauthor_enabled; let opt_manage_schedule_enabled = config.manage_schedule_enabled; + let opt_included_builtin_skills = config.included_builtin_skills.take(); let (mut wire, mut runtime) = config.into_wire(local_session_id.clone())?; wire.enable_github_telemetry_forwarding = self.inner.on_github_telemetry.is_some().then_some(true); @@ -899,7 +925,10 @@ impl Client { ); let handlers = SessionHandlers { permission: permission_handler, - managed_settings_enabled: wire.enable_managed_settings == Some(true), + 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(), @@ -916,6 +945,13 @@ impl Client { let canvas_handler = runtime.canvas_handler.take(); let session_fs_provider = runtime.session_fs_provider.take(); let bearer_token_providers = std::mem::take(&mut runtime.bearer_token_providers); + let github_token_registration = runtime + .github_token_provider + .take() + .map(|provider| self.register_github_token_provider(provider)); + wire.github_token_provider_registration_id = github_token_registration + .as_ref() + .map(|registration| registration.id().to_string()); let has_mcp_auth_handler = handlers.mcp_auth.is_some(); if self.inner.session_fs_configured && session_fs_provider.is_none() { return Err(ErrorKind::Session(SessionErrorKind::SessionFsProviderRequired).into()); @@ -1073,6 +1109,7 @@ impl Client { capabilities, open_canvases, event_tx, + github_token_registration: ParkingLotMutex::new(github_token_registration), }; apply_mode_post_create_patch( &session, @@ -1081,8 +1118,14 @@ impl Client { opt_custom_agents_local_only, opt_coauthor_enabled, opt_manage_schedule_enabled, + opt_included_builtin_skills, ) .await?; + if let Some(registration) = session.github_token_registration.lock().as_ref() { + registration.claim(session.id.clone()); + } else { + self.retire_github_token_provider(&session.id); + } Ok(session) } @@ -1159,6 +1202,7 @@ impl Client { let opt_custom_agents_local_only = config.custom_agents_local_only; let opt_coauthor_enabled = config.coauthor_enabled; let opt_manage_schedule_enabled = config.manage_schedule_enabled; + let opt_included_builtin_skills = config.included_builtin_skills.take(); let (mut wire, mut runtime) = config.into_wire()?; wire.enable_github_telemetry_forwarding = self.inner.on_github_telemetry.is_some().then_some(true); @@ -1169,7 +1213,10 @@ impl Client { ); let handlers = SessionHandlers { permission: permission_handler, - managed_settings_enabled: wire.enable_managed_settings == Some(true), + 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(), @@ -1186,6 +1233,13 @@ impl Client { let canvas_handler = runtime.canvas_handler.take(); let session_fs_provider = runtime.session_fs_provider.take(); let bearer_token_providers = std::mem::take(&mut runtime.bearer_token_providers); + let github_token_registration = runtime + .github_token_provider + .take() + .map(|provider| self.register_github_token_provider(provider)); + wire.github_token_provider_registration_id = github_token_registration + .as_ref() + .map(|registration| registration.id().to_string()); let has_mcp_auth_handler = handlers.mcp_auth.is_some(); if self.inner.session_fs_configured && session_fs_provider.is_none() { return Err(ErrorKind::Session(SessionErrorKind::SessionFsProviderRequired).into()); @@ -1330,6 +1384,7 @@ impl Client { capabilities, open_canvases, event_tx, + github_token_registration: ParkingLotMutex::new(github_token_registration), }; apply_mode_post_create_patch( &session, @@ -1338,8 +1393,14 @@ impl Client { opt_custom_agents_local_only, opt_coauthor_enabled, opt_manage_schedule_enabled, + opt_included_builtin_skills, ) .await?; + if let Some(registration) = session.github_token_registration.lock().as_ref() { + registration.claim(session.id.clone()); + } else { + self.retire_github_token_provider(&session.id); + } Ok(session) } } @@ -1353,7 +1414,42 @@ async fn apply_mode_post_create_patch( opt_custom_agents_local_only: Option, opt_coauthor_enabled: Option, opt_manage_schedule_enabled: Option, + opt_included_builtin_skills: Option>, ) -> Result<(), Error> { + let Some(patch) = build_mode_post_create_patch( + mode, + opt_skip_custom_instructions, + opt_custom_agents_local_only, + opt_coauthor_enabled, + opt_manage_schedule_enabled, + opt_included_builtin_skills, + ) else { + return Ok(()); + }; + if let Err(error) = session.rpc().options().update(patch).await { + let _ = session.disconnect().await; + return Err(error); + } + Ok(()) +} + +/// Builds the `session.options.update` patch applied immediately after a session +/// is created or resumed, or returns `None` when no patch should be sent. +/// +/// Under [`ClientMode::Empty`](crate::ClientMode::Empty) the overridable feature +/// flags fall back to safe defaults (caller values win), while +/// `installed_plugins` is unconditionally empty. `included_builtin_skills` +/// defaults to an empty list, but callers can explicitly allow selected +/// runtime-bundled skills. Under other modes only explicitly-set fields are +/// forwarded. +fn build_mode_post_create_patch( + mode: crate::ClientMode, + opt_skip_custom_instructions: Option, + opt_custom_agents_local_only: Option, + opt_coauthor_enabled: Option, + opt_manage_schedule_enabled: Option, + opt_included_builtin_skills: Option>, +) -> Option { use crate::generated::api_types::SessionUpdateOptionsParams; let mut patch = SessionUpdateOptionsParams::default(); let should_send = if mode == crate::ClientMode::Empty { @@ -1362,6 +1458,7 @@ async fn apply_mode_post_create_patch( patch.coauthor_enabled = Some(opt_coauthor_enabled.unwrap_or(false)); patch.manage_schedule_enabled = Some(opt_manage_schedule_enabled.unwrap_or(false)); patch.installed_plugins = Some(Vec::new()); + patch.included_builtin_skills = Some(opt_included_builtin_skills.unwrap_or_default()); true } else { let mut any = false; @@ -1381,16 +1478,16 @@ async fn apply_mode_post_create_patch( patch.manage_schedule_enabled = Some(v); any = true; } + if let Some(v) = opt_included_builtin_skills { + patch.included_builtin_skills = Some(v); + any = true; + } any }; if !should_send { - return Ok(()); - } - if let Err(error) = session.rpc().options().update(patch).await { - let _ = session.disconnect().await; - return Err(error); + return None; } - Ok(()) + Some(patch) } fn build_command_handler_map(commands: Option<&[CommandDefinition]>) -> Arc { @@ -1477,7 +1574,7 @@ fn spawn_event_loop( _ = shutdown.cancelled() => break, Some(notification) = notifications.recv() => { handle_notification( - &session_id, &client, &handlers, &command_handlers, notification, &idle_waiter, &capabilities, &open_canvases, &event_tx, + &session_id, &client, &handlers, &command_handlers, notification, &idle_waiter, &capabilities, &open_canvases, &event_tx, &shutdown, ).await; } Some(request) = requests.recv() => { @@ -1493,6 +1590,8 @@ fn spawn_event_loop( let canvas_handler = canvas_handler.clone(); let session_fs_provider = session_fs_provider.clone(); let bearer_token_providers = bearer_token_providers.clone(); + let request_id = request.id; + let method = request.method.clone(); tokio::spawn( async move { let ctx = RequestDispatchContext { @@ -1504,7 +1603,19 @@ fn spawn_event_loop( session_fs_provider: session_fs_provider.as_ref(), bearer_token_providers: &bearer_token_providers, }; - handle_request(&session_id, ctx, request).await; + let dispatch = handle_request(&session_id, ctx, request); + if AssertUnwindSafe(dispatch).catch_unwind().await.is_err() { + // Tokio isolates the panic to this task, so without a + // reply the CLI waits out its own timeout on this id. + error!(method = %method, "request handler panicked"); + let _ = send_error_response( + &client, + request_id, + error_codes::INTERNAL_ERROR, + "request handler panicked", + ) + .await; + } } .instrument(span), ); @@ -1560,17 +1671,31 @@ fn permission_request_data( } } -/// Map a [`PermissionResult`] to the `result` payload sent back to the -/// server via `session.permissions.handlePendingPermissionRequest`. +/// 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> { @@ -1597,6 +1722,12 @@ fn tool_failure_result(message: impl Into) -> ToolResult { }) } +fn is_autopilot_continuation_idle(event: &SessionEvent) -> bool { + event + .typed_data::() + .is_some_and(|data| data.mode == Some(SessionMode::Autopilot)) +} + /// Process a notification from the CLI's broadcast channel. #[allow(clippy::too_many_arguments)] async fn handle_notification( @@ -1609,6 +1740,7 @@ async fn handle_notification( capabilities: &Arc>, open_canvases: &Arc>>, event_tx: &tokio::sync::broadcast::Sender, + shutdown: &CancellationToken, ) { let dispatch_start = Instant::now(); let event = notification.event.clone(); @@ -1641,6 +1773,7 @@ async fn handle_notification( } waiter.last_assistant_message = Some(event.clone()); } + SessionEventType::SessionIdle if is_autopilot_continuation_idle(&event) => {} SessionEventType::SessionIdle | SessionEventType::SessionError => { if let Some(waiter) = guard.take() { if event_type == SessionEventType::SessionIdle { @@ -1745,6 +1878,7 @@ async fn handle_notification( }; let client = client.clone(); let sid = session_id.clone(); + let shutdown = shutdown.clone(); let data = permission_request_data( ¬ification.event.data, handlers.managed_settings_enabled, @@ -1766,29 +1900,47 @@ 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). return; }; let rpc_start = Instant::now(); - let _ = client - .call( - "session.permissions.handlePendingPermissionRequest", - Some(serde_json::json!({ - "sessionId": sid, - "requestId": request_id, - "result": result_value, - })), - ) - .await; - tracing::debug!( - elapsed_ms = rpc_start.elapsed().as_millis(), - session_id = %sid, - request_id = %request_id, - "Session::handle_notification response sent successfully" - ); + let method = + rpc_methods::SESSION_PERMISSIONS_HANDLEPENDINGPERMISSIONREQUEST; + tokio::select! { + biased; + response = client.call(method, Some(params)) => { + match response { + Ok(_) => tracing::debug!( + elapsed_ms = rpc_start.elapsed().as_millis(), + session_id = %sid, + request_id = %request_id, + method, + "Session::handle_notification response sent successfully" + ), + Err(error) => warn!( + error = %error, + session_id = %sid, + request_id = %request_id, + method, + "failed to deliver permission decision back to the runtime" + ), + } + } + _ = shutdown.cancelled() => { + warn!( + elapsed_ms = rpc_start.elapsed().as_millis(), + session_id = %sid, + request_id = %request_id, + method, + delivery_outcome = "unknown", + "permission confirmation acknowledgement wait cancelled during session shutdown" + ); + } + } } .instrument(span), ); @@ -2550,31 +2702,232 @@ fn inject_transform_sections_resume( mod tests { use serde_json::json; - use super::{notification_permission_payload, permission_request_data}; + use super::{ + build_mode_post_create_patch, has_managed_settings, is_autopilot_continuation_idle, + permission_request_data, permission_response_params, + }; use crate::handler::PermissionResult; + use crate::types::{ + PermissionDecisionContext, PermissionDecisionOutcome, PermissionDecisionSource, + PermissionDecisionSurface, RequestId, SessionEvent, SessionId, + }; #[test] - fn notification_payload_suppresses_no_result() { - assert!(notification_permission_payload(&PermissionResult::NoResult).is_none()); + fn identifies_only_autopilot_continuation_idles() { + let mut event = SessionEvent { + id: "event-1".to_string(), + timestamp: "2026-01-01T00:00:00Z".to_string(), + parent_id: None, + ephemeral: None, + agent_id: None, + debug_cli_received_at_ms: None, + debug_ws_forwarded_at_ms: None, + event_type: "session.idle".to_string(), + data: json!({ "mode": "autopilot" }), + }; + + assert!(is_autopilot_continuation_idle(&event)); + + event.data = json!({ "mode": "interactive" }); + assert!(!is_autopilot_continuation_idle(&event)); + + event.data = json!({}); + assert!(!is_autopilot_continuation_idle(&event)); } #[test] - fn notification_payload_serializes_decisions() { + fn empty_mode_post_patch_sets_empty_included_builtin_skills() { + let patch = + build_mode_post_create_patch(crate::ClientMode::Empty, None, None, None, None, None) + .expect("empty mode always sends a patch"); assert_eq!( - notification_permission_payload(&PermissionResult::approve_once()), - Some(json!({ "kind": "approve-once" })) + patch.included_builtin_skills, + Some(Vec::new()), + "empty mode must fail closed with an empty includedBuiltinSkills list" ); + assert_eq!(patch.installed_plugins.as_ref().map(|p| p.len()), Some(0)); + // Serializes as an explicit empty array (not omitted). + let value = serde_json::to_value(&patch).expect("serialize patch"); + assert_eq!(value["includedBuiltinSkills"], serde_json::json!([])); + } + + #[test] + fn empty_mode_post_patch_preserves_explicit_builtin_skill_allowlist() { + let patch = build_mode_post_create_patch( + crate::ClientMode::Empty, + Some(false), + Some(false), + Some(true), + Some(true), + Some(vec!["code-review".to_string()]), + ) + .expect("empty mode always sends a patch"); assert_eq!( - notification_permission_payload(&PermissionResult::reject(None)), - Some(json!({ "kind": "reject" })) + patch.included_builtin_skills, + Some(vec!["code-review".to_string()]) + ); + } + + #[test] + fn copilot_cli_mode_does_not_inject_included_builtin_skills() { + // No fields set -> no patch at all. + assert!( + build_mode_post_create_patch( + crate::ClientMode::CopilotCli, + None, + None, + None, + None, + None + ) + .is_none() ); + // A field set -> patch sent, but skills field stays absent. + let patch = build_mode_post_create_patch( + crate::ClientMode::CopilotCli, + Some(true), + None, + None, + None, + None, + ) + .expect("a set field triggers a patch"); + assert_eq!(patch.included_builtin_skills, None); + assert!(patch.installed_plugins.is_none()); + let value = serde_json::to_value(&patch).expect("serialize patch"); + assert!(value.get("includedBuiltinSkills").is_none()); + + let patch = build_mode_post_create_patch( + crate::ClientMode::CopilotCli, + None, + None, + None, + None, + Some(vec!["code-review".to_string()]), + ) + .expect("an explicit allowlist triggers a patch"); assert_eq!( - notification_permission_payload(&PermissionResult::reject(Some("bad".to_string()))), - Some(json!({ "kind": "reject", "feedback": "bad" })) + patch.included_builtin_skills, + Some(vec!["code-review".to_string()]) ); + } + + #[test] + 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, + response_capability: None, + source: PermissionDecisionSource::AssistedApproval, + surface: PermissionDecisionSurface::CopilotApp, + } + } + + #[test] + 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::user_not_available()), - Some(json!({ "kind": "user-not-available" })) + params, + json!({ + "sessionId": "session-1", + "requestId": "permission-1", + "result": { "kind": "approve-once" }, + "decisionContext": { + "outcome": "auto_approved", + "source": "assisted_approval", + "surface": "copilot_app", + }, + }) + ); + // 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, + response_capability: None, + source: PermissionDecisionSource::HumanResponse, + surface: PermissionDecisionSurface::Sdk, + }); + let params = permission_response_params( + &SessionId::from("session-1"), + &RequestId::from("permission-1"), + &result, + ) + .unwrap(); + assert_eq!( + params["decisionContext"], + json!({ + "outcome": "prompted_user", + "source": "human_response", + "surface": "sdk", + }) ); } diff --git a/rust/src/startup_timings.rs b/rust/src/startup_timings.rs index 7938a462b7..7784a6ab3b 100644 --- a/rust/src/startup_timings.rs +++ b/rust/src/startup_timings.rs @@ -38,7 +38,7 @@ use std::time::Duration; #[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 + /// for bundled artifacts, extracting) the Copilot program. `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 diff --git a/rust/src/types.rs b/rust/src/types.rs index 57497cb701..ee3ac3df26 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -21,9 +21,12 @@ pub use crate::copilot_request_handler::{ CopilotWebSocketResponse, WebSocketTransform, forward_http, }; use crate::generated::api_types::{CurrentToolMetadata, OpenCanvasInstance}; +/// Routing tier for the `auto` model with Auto mode V2. +pub use crate::generated::session_events::AutoTier; use crate::generated::session_events::ReasoningSummary; /// Context window tier for models that support tiered context windows. pub use crate::generated::session_events::{ContextTier, SessionLimitsConfig}; +use crate::github_token::GitHubTokenProvider; use crate::handler::{ AutoModeSwitchHandler, ElicitationHandler, ExitPlanModeHandler, McpAuthHandler, PermissionHandler, UserInputHandler, @@ -346,6 +349,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 @@ -470,6 +479,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`]). @@ -512,6 +533,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( @@ -595,12 +617,9 @@ impl std::fmt::Debug for CommandDefinition { impl Serialize for CommandDefinition { fn serialize(&self, serializer: S) -> Result { use serde::ser::SerializeStruct; - let len = if self.description.is_some() { 2 } else { 1 }; - let mut state = serializer.serialize_struct("CommandDefinition", len)?; + let mut state = serializer.serialize_struct("CommandDefinition", 2)?; state.serialize_field("name", &self.name)?; - if let Some(description) = &self.description { - state.serialize_field("description", description)?; - } + state.serialize_field("description", self.description.as_deref().unwrap_or(""))?; state.end() } } @@ -1400,6 +1419,16 @@ impl ProviderConfig { #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct CapiSessionOptions { + /// Routing tier, meaningful only with model `auto` (Auto mode V2). + /// Requires a runtime version that supports `capi.autoTier`. + /// + /// When omitted, the runtime chooses its default on create and preserves + /// the persisted or current tier on resume. An explicit tier overrides the + /// persisted tier on cold resume; the runtime rejects a conflicting tier + /// when resuming a session already resident in memory. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub auto_tier: Option, + /// Whether to use WebSocket transport for CAPI Responses API calls. /// /// When `Some(false)`, the runtime uses HTTP Responses transport even if @@ -1415,6 +1444,12 @@ impl CapiSessionOptions { Self::default() } + /// Set the routing tier for the `auto` model (Auto mode V2). + pub fn with_auto_tier(mut self, auto_tier: AutoTier) -> Self { + self.auto_tier = Some(auto_tier); + self + } + /// Set whether to use WebSocket transport for CAPI Responses API calls. pub fn with_enable_web_socket_responses(mut self, enable: bool) -> Self { self.enable_web_socket_responses = Some(enable); @@ -1744,6 +1779,108 @@ pub struct CopilotExpAssignmentResponse { pub assignment_context: String, } +/// Well-known managed bypass-permissions policies. +pub struct DisableBypassPermissionsModes; + +impl DisableBypassPermissionsModes { + /// Permit automatic bypass but block full allow-all. + pub const ALLOW_AUTO_ONLY: &'static str = "allow-auto-only"; + /// Turn off bypass-permissions mode entirely. + pub const DISABLE: &'static str = "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 bypass-mode restrictions compose to the most +/// restrictive setting. +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub struct ManagedSettingsPermissions { + /// Restricts bypass-permissions mode for the session. See + /// [`DisableBypassPermissionsModes`] for well-known values. Unknown values + /// are forwarded so newer runtime policies fail closed. + #[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: impl Into) -> Self { + self.disable_bypass_permissions_mode = Some(value.into()); + 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 + } +} + +/// Selects the model-facing shape of the built-in `ask_user` tool. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +#[non_exhaustive] +pub enum AskUserVariant { + /// Use the legacy user-input request flow. + #[default] + Legacy, + /// Use the elicitation request flow. + Elicitation, +} + /// Configuration for creating a new session via the `session.create` RPC. /// /// All fields are optional — the CLI applies sensible defaults. @@ -1817,6 +1954,11 @@ pub struct SessionConfig { pub streaming: Option, /// Custom system message configuration. pub system_message: Option, + /// Selects the model-facing shape of the built-in `ask_user` tool. + /// + /// When omitted, the runtime uses [`AskUserVariant::Legacy`]. To use + /// [`AskUserVariant::Elicitation`], also install an [`ElicitationHandler`]. + pub ask_user_variant: Option, /// Client-defined tool declarations to expose to the agent. pub tools: Option>, /// Canvas declarations this connection provides to the runtime. @@ -1849,6 +1991,10 @@ pub struct SessionConfig { /// selected or invoked unless a custom agent with the same name is /// configured. pub excluded_builtin_agents: Option>, + /// Built-in skill names to include in the session. In + /// [`ClientMode::Empty`](crate::ClientMode::Empty), `None` excludes all + /// runtime-bundled skills; `Some` opts the named built-ins back in. + pub included_builtin_skills: Option>, /// MCP server configurations passed through to the CLI. pub mcp_servers: Option>, /// Controls how MCP OAuth tokens are stored for this session. @@ -1928,6 +2074,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). @@ -1977,6 +2127,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 @@ -2000,6 +2153,12 @@ pub struct SessionConfig { /// the GitHub identity used for content exclusion, model routing, and /// quota checks for *this session*. pub github_token: Option, + /// Provider used to acquire rotating GitHub tokens for this session. + /// + /// Mutually exclusive with [`github_token`](Self::github_token). The callback + /// receives the effective host, optional assigned session ID, and acquisition + /// reason; its opaque registration ID is never exposed. + pub github_token_provider: Option>, /// Per-session remote behavior control: /// - `Off` — local only, no remote export (default) /// - `Export` — export session events to GitHub without @@ -2017,6 +2176,12 @@ pub struct SessionConfig { /// each command appears as `/name` for the user to invoke and the /// associated [`CommandHandler`] is called when executed. pub commands: Option>, + /// Feature-flag values resolved by the host for this session. + /// + /// Re-supply these values through [`ResumeSessionConfig::feature_flags`] + /// when resuming after a CLI process restart. Set via + /// [`with_feature_flags`](Self::with_feature_flags). + pub feature_flags: Option>, /// ExP assignment ("flight") data injected by a trusted integrator, in /// the same JSON shape the Copilot CLI fetches from the experimentation /// service (`CopilotExpAssignmentResponse`). When supplied, the runtime @@ -2027,11 +2192,21 @@ pub struct SessionConfig { pub exp_assignments: Option, /// Opt-in: when `Some(true)`, the runtime self-fetches enterprise managed /// settings (bypass-permissions policy) at session bootstrap using the - /// session's [`github_token`](Self::github_token). Requires `github_token` - /// to be set; if omitted, the runtime is expected to reject session creation - /// (fail-closed). When `None`, behaves exactly as before. Set via + /// session's static [`github_token`](Self::github_token) or + /// [`github_token_provider`](Self::github_token_provider). Requires one of + /// those credentials; if both are omitted, the runtime is expected to reject + /// session creation (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. @@ -2047,9 +2222,9 @@ pub struct SessionConfig { /// Optional MCP OAuth request handler. When set, the SDK can satisfy MCP /// server OAuth requests with host-acquired token data or cancellation. pub mcp_auth_handler: Option>, - /// Optional user-input handler. When `None`, - /// `requestUserInput: false` goes on the wire and the `ask_user` - /// tool is disabled. + /// Optional handler for the legacy question-and-answer `ask_user` variant. + /// When `None`, `requestUserInput: false` goes on the wire, so this client + /// cannot handle legacy user-input requests. pub user_input_handler: Option>, /// Optional exit-plan-mode handler. When `None`, /// `requestExitPlanMode: false` goes on the wire. @@ -2104,6 +2279,7 @@ impl std::fmt::Debug for SessionConfig { .field("context_tier", &self.context_tier) .field("streaming", &self.streaming) .field("system_message", &self.system_message) + .field("ask_user_variant", &self.ask_user_variant) .field("tools", &self.tools) .field("canvases", &self.canvases) .field( @@ -2118,6 +2294,7 @@ impl std::fmt::Debug for SessionConfig { .field("available_tools", &self.available_tools) .field("excluded_tools", &self.excluded_tools) .field("excluded_builtin_agents", &self.excluded_builtin_agents) + .field("included_builtin_skills", &self.included_builtin_skills) .field("mcp_servers", &self.mcp_servers) .field("mcp_oauth_token_storage", &self.mcp_oauth_token_storage) .field("embedding_cache_storage", &self.embedding_cache_storage) @@ -2148,6 +2325,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) @@ -2157,6 +2335,10 @@ 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) @@ -2167,6 +2349,10 @@ impl std::fmt::Debug for SessionConfig { "github_token", &self.github_token.as_ref().map(|_| ""), ) + .field( + "github_token_provider", + &self.github_token_provider.as_ref().map(|_| ""), + ) .field("remote_session", &self.remote_session) .field("cloud", &self.cloud) .field( @@ -2174,9 +2360,11 @@ impl std::fmt::Debug for SessionConfig { &self.include_sub_agent_streaming_events, ) .field("commands", &self.commands) + .field("feature_flags", &self.feature_flags) .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(|_| ""), @@ -2233,6 +2421,7 @@ impl Default for SessionConfig { context_tier: None, streaming: None, system_message: None, + ask_user_variant: None, tools: None, canvases: None, canvas_handler: None, @@ -2244,6 +2433,7 @@ impl Default for SessionConfig { available_tools: None, excluded_tools: None, excluded_builtin_agents: None, + included_builtin_skills: None, mcp_servers: None, mcp_oauth_token_storage: None, enable_config_discovery: None, @@ -2263,6 +2453,7 @@ impl Default for SessionConfig { large_output: None, tool_search: None, disabled_skills: None, + disabled_mcp_servers: None, hooks: None, custom_agents: None, default_agent: None, @@ -2274,6 +2465,7 @@ 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, @@ -2281,12 +2473,15 @@ impl Default for SessionConfig { working_directory: None, additional_directories: None, github_token: None, + github_token_provider: None, remote_session: None, cloud: None, include_sub_agent_streaming_events: None, commands: None, + feature_flags: None, exp_assignments: None, enable_managed_settings: None, + managed_settings: None, session_fs_provider: None, permission_handler: None, elicitation_handler: None, @@ -2325,6 +2520,7 @@ pub(crate) struct SessionConfigRuntime { pub canvas_handler: Option>, pub session_fs_provider: Option>, pub bearer_token_providers: HashMap>, + pub github_token_provider: Option>, pub commands: Option>, } @@ -2344,6 +2540,12 @@ impl SessionConfig { mut self, session_id: Option, ) -> Result<(crate::wire::SessionCreateWire, SessionConfigRuntime), crate::Error> { + if self.github_token.is_some() && self.github_token_provider.is_some() { + return Err(crate::Error::with_message( + crate::ErrorKind::InvalidConfig, + "github_token and github_token_provider are mutually exclusive", + )); + } let permission_active = self.permission_handler.is_some() || self.permission_policy.is_some(); let request_user_input = self.user_input_handler.is_some(); @@ -2370,7 +2572,7 @@ impl SessionConfig { cmds.iter() .map(|c| crate::wire::CommandWireDefinition { name: c.name.clone(), - description: c.description.clone(), + description: c.description.clone().unwrap_or_default(), }) .collect() }); @@ -2388,6 +2590,7 @@ impl SessionConfig { context_tier: self.context_tier, streaming: self.streaming, system_message: self.system_message, + ask_user_variant: self.ask_user_variant, tools: self.tools, canvases: wire_canvases, request_canvas_renderer: self.request_canvas_renderer, @@ -2425,6 +2628,7 @@ 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, @@ -2436,6 +2640,7 @@ 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, @@ -2443,14 +2648,17 @@ impl SessionConfig { working_directory: self.working_directory, additional_directories: self.additional_directories, github_token: self.github_token, + github_token_provider_registration_id: None, remote_session: self.remote_session, cloud: self.cloud, include_sub_agent_streaming_events: self.include_sub_agent_streaming_events, enable_github_telemetry_forwarding: None, commands: wire_commands, + feature_flags: self.feature_flags, 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 { @@ -2467,6 +2675,7 @@ impl SessionConfig { canvas_handler, session_fs_provider: self.session_fs_provider, bearer_token_providers, + github_token_provider: self.github_token_provider, commands: self.commands, }; @@ -2494,13 +2703,19 @@ impl SessionConfig { self } - /// Install a [`UserInputHandler`]. Required for the `ask_user` tool - /// to be enabled. + /// Install a [`UserInputHandler`] for the legacy question-and-answer + /// `ask_user` variant. pub fn with_user_input_handler(mut self, handler: Arc) -> Self { self.user_input_handler = Some(handler); self } + /// Select the model-facing shape of the built-in `ask_user` tool. + pub fn with_ask_user_variant(mut self, variant: AskUserVariant) -> Self { + self.ask_user_variant = Some(variant); + self + } + /// Install an [`ExitPlanModeHandler`]. pub fn with_exit_plan_mode_handler(mut self, handler: Arc) -> Self { self.exit_plan_mode_handler = Some(handler); @@ -2817,6 +3032,16 @@ impl SessionConfig { self } + /// Set the runtime-bundled skill allowlist. + pub fn with_included_builtin_skills(mut self, names: I) -> Self + where + I: IntoIterator, + S: Into, + { + self.included_builtin_skills = Some(names.into_iter().map(Into::into).collect()); + self + } + /// Set additional directories to search for custom instruction files. /// Forwarded to the CLI on session create; not the same as /// [`with_skill_directories`](Self::with_skill_directories). @@ -2862,6 +3087,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, @@ -2937,6 +3172,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); @@ -2990,6 +3232,16 @@ impl SessionConfig { self } + /// Install a rotating GitHub token provider for this session. + /// + /// The provider must return a positive remaining lifetime in seconds when + /// its callback completes. Production GitHub tokens typically last eight + /// hours. This option is mutually exclusive with [`with_github_token`](Self::with_github_token). + pub fn with_github_token_provider(mut self, provider: Arc) -> Self { + self.github_token_provider = Some(provider); + self + } + /// Forward sub-agent streaming events to this connection. Defaults /// to true on the CLI when unset. pub fn with_include_sub_agent_streaming_events(mut self, include: bool) -> Self { @@ -3042,6 +3294,12 @@ impl SessionConfig { self } + /// Set feature-flag values resolved by the host for this session. + pub fn with_feature_flags(mut self, feature_flags: HashMap) -> Self { + self.feature_flags = Some(feature_flags); + self + } + /// Inject ExP assignment ("flight") data for this session, in the same /// JSON shape the Copilot CLI fetches from the experimentation service /// (`CopilotExpAssignmentResponse`). The runtime feeds it into the same @@ -3057,13 +3315,23 @@ impl SessionConfig { /// Opt the runtime into self-fetching enterprise managed settings /// (bypass-permissions policy) at session bootstrap using the session's - /// [`github_token`](Self::github_token). Requires `github_token` to be set; - /// if omitted, the runtime is expected to reject session creation - /// (fail-closed). + /// static [`github_token`](Self::github_token) or + /// [`github_token_provider`](Self::github_token_provider). Requires one of + /// those credentials; if both are omitted, the runtime is expected to reject + /// session creation (fail-closed). pub fn with_enable_managed_settings(mut self, enabled: bool) -> Self { 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_*` @@ -3095,6 +3363,11 @@ pub struct ResumeSessionConfig { /// Re-supply the system message so the agent retains workspace context /// across CLI process restarts. pub system_message: Option, + /// Selects the model-facing shape of the built-in `ask_user` tool on a cold resume. + /// + /// When omitted, the runtime uses [`AskUserVariant::Legacy`]. To use + /// [`AskUserVariant::Elicitation`], also install an [`ElicitationHandler`]. + pub ask_user_variant: Option, /// Client-defined tool declarations to re-supply on resume. pub tools: Option>, /// Canvas declarations this connection provides to the runtime. @@ -3127,6 +3400,10 @@ pub struct ResumeSessionConfig { /// selected or invoked unless a custom agent with the same name is /// configured. pub excluded_builtin_agents: Option>, + /// Built-in skill names to include in the resumed session. In + /// [`ClientMode::Empty`](crate::ClientMode::Empty), `None` excludes all + /// runtime-bundled skills; `Some` opts the named built-ins back in. + pub included_builtin_skills: Option>, /// Re-supply MCP servers so they remain available after app restart. pub mcp_servers: Option>, /// Controls how MCP OAuth tokens are stored for this session. @@ -3176,6 +3453,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. @@ -3216,6 +3496,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. @@ -3232,6 +3516,9 @@ pub struct ResumeSessionConfig { /// Per-session GitHub token on resume. See /// [`SessionConfig::github_token`]. pub github_token: Option, + /// Rotating GitHub token provider on resume. See + /// [`SessionConfig::github_token_provider`]. + pub github_token_provider: Option>, /// Per-session remote behavior control on resume. See /// [`SessionConfig::remote_session`]. pub remote_session: Option, @@ -3241,6 +3528,10 @@ pub struct ResumeSessionConfig { /// [`SessionConfig::commands`] — commands are not persisted server-side, /// so the resume payload re-supplies the registration. pub commands: Option>, + /// Feature-flag values resolved by the host to apply on resume. + /// + /// See [`SessionConfig::feature_flags`]. + pub feature_flags: Option>, /// ExP assignment ("flight") data injected on resume. See /// [`SessionConfig::exp_assignments`]. Re-supply on resume so the runtime /// re-applies the assignments after a CLI process restart. Set via @@ -3253,6 +3544,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). @@ -3318,6 +3615,7 @@ impl std::fmt::Debug for ResumeSessionConfig { .field("context_tier", &self.context_tier) .field("streaming", &self.streaming) .field("system_message", &self.system_message) + .field("ask_user_variant", &self.ask_user_variant) .field("tools", &self.tools) .field("canvases", &self.canvases) .field( @@ -3333,6 +3631,7 @@ impl std::fmt::Debug for ResumeSessionConfig { .field("available_tools", &self.available_tools) .field("excluded_tools", &self.excluded_tools) .field("excluded_builtin_agents", &self.excluded_builtin_agents) + .field("included_builtin_skills", &self.included_builtin_skills) .field("mcp_servers", &self.mcp_servers) .field("mcp_oauth_token_storage", &self.mcp_oauth_token_storage) .field("embedding_cache_storage", &self.embedding_cache_storage) @@ -3363,6 +3662,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) @@ -3372,6 +3672,10 @@ 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) @@ -3382,15 +3686,21 @@ impl std::fmt::Debug for ResumeSessionConfig { "github_token", &self.github_token.as_ref().map(|_| ""), ) + .field( + "github_token_provider", + &self.github_token_provider.as_ref().map(|_| ""), + ) .field("remote_session", &self.remote_session) .field( "include_sub_agent_streaming_events", &self.include_sub_agent_streaming_events, ) .field("commands", &self.commands) + .field("feature_flags", &self.feature_flags) .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(|_| ""), @@ -3440,6 +3750,12 @@ impl ResumeSessionConfig { pub(crate) fn into_wire( mut self, ) -> Result<(crate::wire::SessionResumeWire, SessionConfigRuntime), crate::Error> { + if self.github_token.is_some() && self.github_token_provider.is_some() { + return Err(crate::Error::with_message( + crate::ErrorKind::InvalidConfig, + "github_token and github_token_provider are mutually exclusive", + )); + } let permission_active = self.permission_handler.is_some() || self.permission_policy.is_some(); let request_user_input = self.user_input_handler.is_some(); @@ -3466,7 +3782,7 @@ impl ResumeSessionConfig { cmds.iter() .map(|c| crate::wire::CommandWireDefinition { name: c.name.clone(), - description: c.description.clone(), + description: c.description.clone().unwrap_or_default(), }) .collect() }); @@ -3484,6 +3800,7 @@ impl ResumeSessionConfig { context_tier: self.context_tier, streaming: self.streaming, system_message: self.system_message, + ask_user_variant: self.ask_user_variant, tools: self.tools, canvases: wire_canvases, open_canvases: self.open_canvases, @@ -3522,6 +3839,7 @@ 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, @@ -3533,6 +3851,7 @@ 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, @@ -3540,13 +3859,16 @@ impl ResumeSessionConfig { working_directory: self.working_directory, additional_directories: self.additional_directories, github_token: self.github_token, + github_token_provider_registration_id: None, remote_session: self.remote_session, include_sub_agent_streaming_events: self.include_sub_agent_streaming_events, enable_github_telemetry_forwarding: None, commands: wire_commands, + feature_flags: self.feature_flags, 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, }; @@ -3565,6 +3887,7 @@ impl ResumeSessionConfig { canvas_handler, session_fs_provider: self.session_fs_provider, bearer_token_providers, + github_token_provider: self.github_token_provider, commands: self.commands, }; @@ -3585,6 +3908,7 @@ impl ResumeSessionConfig { context_tier: None, streaming: None, system_message: None, + ask_user_variant: None, tools: None, canvases: None, canvas_handler: None, @@ -3597,6 +3921,7 @@ impl ResumeSessionConfig { available_tools: None, excluded_tools: None, excluded_builtin_agents: None, + included_builtin_skills: None, mcp_servers: None, mcp_oauth_token_storage: None, enable_config_discovery: None, @@ -3616,6 +3941,7 @@ impl ResumeSessionConfig { large_output: None, tool_search: None, disabled_skills: None, + disabled_mcp_servers: None, hooks: None, custom_agents: None, default_agent: None, @@ -3627,6 +3953,7 @@ impl ResumeSessionConfig { models: None, enable_session_telemetry: None, enable_citations: None, + enable_file_change_tracking: None, session_limits: None, model_capabilities: None, memory: None, @@ -3634,11 +3961,14 @@ impl ResumeSessionConfig { working_directory: None, additional_directories: None, github_token: None, + github_token_provider: None, remote_session: None, include_sub_agent_streaming_events: None, commands: None, + feature_flags: None, exp_assignments: None, enable_managed_settings: None, + managed_settings: None, session_fs_provider: None, suppress_resume_event: None, continue_pending_work: None, @@ -3683,6 +4013,12 @@ impl ResumeSessionConfig { self } + /// Select the model-facing shape of the built-in `ask_user` tool on resume. + pub fn with_ask_user_variant(mut self, variant: AskUserVariant) -> Self { + self.ask_user_variant = Some(variant); + self + } + /// Install an [`ExitPlanModeHandler`] for the resumed session. pub fn with_exit_plan_mode_handler(mut self, handler: Arc) -> Self { self.exit_plan_mode_handler = Some(handler); @@ -3987,6 +4323,16 @@ impl ResumeSessionConfig { self } + /// Set the runtime-bundled skill allowlist on resume. + pub fn with_included_builtin_skills(mut self, names: I) -> Self + where + I: IntoIterator, + S: Into, + { + self.included_builtin_skills = Some(names.into_iter().map(Into::into).collect()); + self + } + /// Set additional directories to search for custom instruction files /// on resume. Forwarded to the CLI; not the same as /// [`with_skill_directories`](Self::with_skill_directories). @@ -4032,6 +4378,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, @@ -4105,6 +4461,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); @@ -4156,6 +4519,16 @@ impl ResumeSessionConfig { self } + /// Install a rotating GitHub token provider for the resumed session. + /// + /// The provider must return a positive remaining lifetime in seconds when + /// its callback completes. Production GitHub tokens typically last eight + /// hours. Mutually exclusive with [`with_github_token`](Self::with_github_token). + pub fn with_github_token_provider(mut self, provider: Arc) -> Self { + self.github_token_provider = Some(provider); + self + } + /// Forward sub-agent streaming events to this connection on resume. pub fn with_include_sub_agent_streaming_events(mut self, include: bool) -> Self { self.include_sub_agent_streaming_events = Some(include); @@ -4218,6 +4591,12 @@ impl ResumeSessionConfig { self } + /// Re-supply feature-flag values resolved by the host on resume. + pub fn with_feature_flags(mut self, feature_flags: HashMap) -> Self { + self.feature_flags = Some(feature_flags); + self + } + /// Inject ExP assignment ("flight") data on resume. See /// [`SessionConfig::with_exp_assignments`]. Re-supply the assignments on /// resume so the runtime re-applies them after a CLI process restart. @@ -4233,6 +4612,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. @@ -5557,7 +5944,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, PermissionResponseCapability, }; /// Permission categories the CLI may request approval for. @@ -5663,17 +6052,25 @@ mod tests { use super::{ AgentMode, Attachment, AttachmentLineRange, AttachmentSelectionPosition, - AttachmentSelectionRange, AzureProviderOptions, CapiSessionOptions, ConnectionState, - CopilotExpAssignmentResponse, CustomAgentConfig, DeliveryMode, ExpConfigEntry, - ExpFlagValue, ExtensionInfo, GitHubMcpToolConfig, GitHubReferenceType, + AttachmentSelectionRange, AutoTier, AzureProviderOptions, CapiSessionOptions, + ConnectionState, CopilotExpAssignmentResponse, CustomAgentConfig, DeliveryMode, + ExpConfigEntry, ExpFlagValue, ExtensionInfo, GitHubMcpToolConfig, GitHubReferenceType, InfiniteSessionConfig, LargeToolOutputConfig, McpServerConfig, McpStdioServerConfig, - MemoryConfiguration, NamedProviderConfig, ProviderConfig, ProviderModelConfig, - ReasoningSummary, ResumeSessionConfig, SessionConfig, SessionEvent, SessionId, - SystemMessageConfig, Tool, ToolBinaryResult, ToolResult, ToolResultExpanded, + MemoryConfiguration, NamedProviderConfig, PermissionResponseCapability, ProviderConfig, + ProviderModelConfig, ReasoningSummary, ResumeSessionConfig, SessionConfig, SessionEvent, + SessionId, SystemMessageConfig, Tool, ToolBinaryResult, ToolResult, ToolResultExpanded, ToolResultResponse, ensure_attachment_display_names, }; use crate::generated::session_events::TypedSessionEvent; + #[test] + fn permission_response_capability_is_publicly_exported() { + assert_eq!( + serde_json::to_value(PermissionResponseCapability::Interactive).unwrap(), + json!("interactive") + ); + } + #[test] fn tool_builder_composes() { let tool = Tool::new("greet") @@ -5928,6 +6325,8 @@ mod tests { assert!(!wire.request_auto_mode_switch); assert!(!wire.hooks); assert!(!wire.request_mcp_apps); + let json = serde_json::to_value(&wire).unwrap(); + assert!(json.get("askUserVariant").is_none()); } #[test] @@ -5944,6 +6343,8 @@ mod tests { assert!(!wire.request_auto_mode_switch); assert!(!wire.hooks); assert!(!wire.request_mcp_apps); + let json = serde_json::to_value(&wire).unwrap(); + assert!(json.get("askUserVariant").is_none()); } #[test] @@ -6096,6 +6497,46 @@ mod tests { assert!(empty_json.get("memory").is_none()); } + #[test] + fn feature_flags_serialize_on_create_and_resume() { + let feature_flags = HashMap::from([ + ("BACKGROUND_TASK_NOTIFICATION_PAYLOADS".to_string(), true), + ("DISABLED_TEST_FLAG".to_string(), false), + ]); + let expected = serde_json::json!({ + "BACKGROUND_TASK_NOTIFICATION_PAYLOADS": true, + "DISABLED_TEST_FLAG": false, + }); + + let create_config = SessionConfig::default().with_feature_flags(feature_flags.clone()); + assert_eq!(create_config.feature_flags.as_ref(), Some(&feature_flags)); + let (create_wire, _) = create_config + .into_wire(Some(SessionId::from("feature-flags-create"))) + .expect("no duplicate handlers"); + let create_json = serde_json::to_value(&create_wire).unwrap(); + assert_eq!(create_json["featureFlags"], expected); + + let (resume_wire, _) = ResumeSessionConfig::new(SessionId::from("feature-flags-resume")) + .with_feature_flags(feature_flags) + .into_wire() + .expect("no duplicate handlers"); + let resume_json = serde_json::to_value(&resume_wire).unwrap(); + assert_eq!(resume_json["featureFlags"], expected); + + let (unset_create_wire, _) = SessionConfig::default() + .into_wire(Some(SessionId::from("feature-flags-create-unset"))) + .expect("no duplicate handlers"); + let unset_create_json = serde_json::to_value(&unset_create_wire).unwrap(); + assert!(unset_create_json.get("featureFlags").is_none()); + + let (unset_resume_wire, _) = + ResumeSessionConfig::new(SessionId::from("feature-flags-resume-unset")) + .into_wire() + .expect("no duplicate handlers"); + let unset_resume_json = serde_json::to_value(&unset_resume_wire).unwrap(); + assert!(unset_resume_json.get("featureFlags").is_none()); + } + fn sample_exp_assignments(context: &str) -> CopilotExpAssignmentResponse { CopilotExpAssignmentResponse { features: vec!["copilot_exp_flag".to_string()], @@ -6342,6 +6783,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) @@ -6356,6 +6801,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"); @@ -6365,6 +6814,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()); } @@ -6414,6 +6864,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) @@ -6424,6 +6875,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"); @@ -6433,9 +6888,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; @@ -6457,6 +6941,7 @@ 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")) @@ -6495,6 +6980,10 @@ 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"))); @@ -6533,6 +7022,7 @@ 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")) @@ -6571,6 +7061,10 @@ 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"))); @@ -6846,6 +7340,56 @@ mod tests { let unset = CapiSessionOptions::new(); let wire_unset = serde_json::to_value(&unset).unwrap(); assert!(wire_unset.get("enableWebSocketResponses").is_none()); + assert!(wire_unset.get("autoTier").is_none()); + assert_eq!(wire_unset, json!({})); + } + + #[test] + fn capi_auto_tier_canonical_values_round_trip_and_forward() { + for (tier, value) in [ + (AutoTier::Efficiency, "efficiency"), + (AutoTier::Balance, "balance"), + (AutoTier::Intelligence, "intelligence"), + ] { + let exported: crate::AutoTier = tier.clone(); + let capi = CapiSessionOptions::new().with_auto_tier(exported); + assert_eq!(capi.auto_tier, Some(tier)); + assert_eq!( + serde_json::to_value(&capi).unwrap(), + json!({"autoTier": value}) + ); + assert_eq!( + serde_json::from_value::(json!({"autoTier": value})).unwrap(), + capi + ); + + let capi = capi.with_enable_web_socket_responses(false); + let expected = json!({"autoTier": value, "enableWebSocketResponses": false}); + let (create, _) = SessionConfig::default() + .with_model("auto") + .with_capi(capi.clone()) + .into_wire(Some(SessionId::from("capi-create"))) + .unwrap(); + assert_eq!(serde_json::to_value(create).unwrap()["capi"], expected); + + let (resume, _) = ResumeSessionConfig::new(SessionId::from("capi-resume")) + .with_capi(capi) + .into_wire() + .unwrap(); + assert_eq!(serde_json::to_value(resume).unwrap()["capi"], expected); + } + } + + #[test] + fn capi_auto_tier_accepts_unknown_values_for_forward_compatibility() { + for value in ["balanced", "Balance", "unknown"] { + assert_eq!( + serde_json::from_value::(json!(value)).unwrap(), + AutoTier::Unknown + ); + } + let capi: CapiSessionOptions = serde_json::from_value(json!({})).unwrap(); + assert_eq!(capi.auto_tier, None); } #[test] @@ -7336,7 +7880,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(_), + .. + } )); } @@ -7346,7 +7893,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(_), + .. + } )); } @@ -7364,11 +7914,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(_), + .. + } )); } @@ -7384,11 +7940,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(_), + .. + } )); } @@ -7400,7 +7962,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(_), + .. + } )); } @@ -7419,11 +7984,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(_), + .. + } )); } @@ -7435,7 +8006,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(_), + .. + } )); } @@ -7451,11 +8025,17 @@ 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(_), + .. + } )); } @@ -7516,3 +8096,50 @@ mod permission_builder_tests { 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 261350e972..75e17f4e9c 100644 --- a/rust/src/wire.rs +++ b/rust/src/wire.rs @@ -13,6 +13,7 @@ //! configs hold trait-object handlers, the wire structs hold only the //! plain data the runtime needs. +use std::collections::HashMap; use std::path::PathBuf; use indexmap::IndexMap; @@ -24,11 +25,11 @@ use crate::generated::api_types::{ }; use crate::generated::session_events::ReasoningSummary; use crate::types::{ - CanvasProviderIdentity, CapiSessionOptions, CloudSessionOptions, CustomAgentConfig, - DefaultAgentConfig, ExtensionInfo, GitHubMcpToolConfig, InfiniteSessionConfig, - LargeToolOutputConfig, McpServerConfig, MemoryConfiguration, NamedProviderConfig, - ProviderConfig, ProviderModelConfig, SessionId, SessionLimitsConfig, SystemMessageConfig, Tool, - ToolSearchConfig, + AskUserVariant, CanvasProviderIdentity, CapiSessionOptions, CloudSessionOptions, + CustomAgentConfig, 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 @@ -38,8 +39,9 @@ use crate::types::{ #[serde(rename_all = "camelCase")] pub(crate) struct CommandWireDefinition { pub name: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, + /// Always sent. The runtime requires a string, so a command declared + /// without a description is serialized as `""` rather than omitted. + pub description: String, } /// The exact JSON shape sent on the `session.create` JSON-RPC request. @@ -63,6 +65,8 @@ pub(crate) struct SessionCreateWire { #[serde(skip_serializing_if = "Option::is_none")] pub system_message: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub ask_user_variant: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub tools: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub canvases: Option>, @@ -130,6 +134,8 @@ 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, @@ -152,6 +158,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, @@ -165,6 +173,11 @@ pub(crate) struct SessionCreateWire { pub additional_directories: Option>, #[serde(rename = "gitHubToken", skip_serializing_if = "Option::is_none")] pub github_token: Option, + #[serde( + rename = "gitHubTokenProviderRegistrationId", + skip_serializing_if = "Option::is_none" + )] + pub github_token_provider_registration_id: Option, #[serde(skip_serializing_if = "Option::is_none")] pub remote_session: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -179,11 +192,15 @@ pub(crate) struct SessionCreateWire { #[serde(skip_serializing_if = "Option::is_none")] pub commands: Option>, #[serde(skip_serializing_if = "Option::is_none")] + pub feature_flags: Option>, + #[serde(skip_serializing_if = "Option::is_none")] 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. @@ -206,6 +223,8 @@ pub(crate) struct SessionResumeWire { #[serde(skip_serializing_if = "Option::is_none")] pub system_message: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub ask_user_variant: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub tools: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub canvases: Option>, @@ -274,6 +293,8 @@ 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, @@ -296,6 +317,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, @@ -309,6 +332,11 @@ pub(crate) struct SessionResumeWire { pub additional_directories: Option>, #[serde(rename = "gitHubToken", skip_serializing_if = "Option::is_none")] pub github_token: Option, + #[serde( + rename = "gitHubTokenProviderRegistrationId", + skip_serializing_if = "Option::is_none" + )] + pub github_token_provider_registration_id: Option, #[serde(skip_serializing_if = "Option::is_none")] pub remote_session: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -326,9 +354,13 @@ pub(crate) struct SessionResumeWire { #[serde(skip_serializing_if = "Option::is_none")] pub continue_pending_work: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub feature_flags: Option>, + #[serde(skip_serializing_if = "Option::is_none")] 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 9b86b1367a..8ed40e7c76 100644 --- a/rust/tests/api_types_test.rs +++ b/rust/tests/api_types_test.rs @@ -3,11 +3,53 @@ #![allow(clippy::unwrap_used)] +use github_copilot_sdk::AutoTier; use github_copilot_sdk::rpc::{ Extension, ExtensionList, ExtensionSource, ExtensionStatus, ExtensionsDisableRequest, ExtensionsEnableRequest, FleetStartRequest, FleetStartResult, TasksStartAgentRequest, }; -use github_copilot_sdk::session_events::{PermissionRequest, PermissionRequestedData}; +use github_copilot_sdk::session_events::{ + PermissionRequest, PermissionRequestedData, SessionEventData, TypedSessionEvent, +}; + +#[test] +fn session_events_deserialize_auto_tier() { + for event_type in ["session.start", "session.resume"] { + for (tier, wire_tier) in [ + (Some(AutoTier::Efficiency), Some("efficiency")), + (Some(AutoTier::Balance), Some("balance")), + (Some(AutoTier::Intelligence), Some("intelligence")), + (None, None), + ] { + let mut wire = serde_json::json!({ + "id": "11111111-1111-1111-1111-111111111111", + "timestamp": "2026-08-28T00:00:00Z", + "parentId": null, + "type": event_type, + "data": { + "sessionId": "test-session", "version": 1, + "producer": "copilot", "copilotVersion": "1.0.82-1", + "startTime": "2026-08-28T00:00:00Z", + "resumeTime": "2026-08-28T00:00:00Z", "eventCount": 1 + } + }); + if let Some(wire_tier) = wire_tier { + wire["data"]["autoTier"] = serde_json::json!(wire_tier); + } + let event: TypedSessionEvent = serde_json::from_value(wire).unwrap(); + let actual: Option = match event.payload { + SessionEventData::SessionStart(data) if event_type == "session.start" => { + data.auto_tier + } + SessionEventData::SessionResume(data) if event_type == "session.resume" => { + data.auto_tier + } + _ => panic!("expected {event_type}"), + }; + assert_eq!(actual, tier); + } + } +} #[test] fn extension_running_has_expected_status_and_source() { diff --git a/rust/tests/builtin_plugin_directories_test.rs b/rust/tests/builtin_plugin_directories_test.rs new file mode 100644 index 0000000000..f1310f9b04 --- /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/cli_resolution_test.rs b/rust/tests/cli_resolution_test.rs index 9e4927e676..847ac7a4d0 100644 --- a/rust/tests/cli_resolution_test.rs +++ b/rust/tests/cli_resolution_test.rs @@ -10,7 +10,10 @@ use std::path::PathBuf; use github_copilot_sdk::{ CliProgram, Client, ClientOptions, ErrorKind, HAS_BUNDLED_CLI, install_bundled_cli, + install_bundled_runtime, }; +#[cfg(all(feature = "bundled-cli", has_bundled_cli))] +use github_copilot_sdk::{SessionConfig, Transport}; use serial_test::serial; fn unset_env(key: &str) { @@ -95,7 +98,7 @@ async fn stale_env_override_falls_through() { } } -/// With `bundled-cli` off, `build.rs` extracts the binary into the +/// With `bundled-cli` off, `build.rs` extracts the runtime wrapper into the /// per-user cache and the runtime resolver recomputes its location from /// `COPILOT_SDK_CLI_VERSION` + the OS-derived binary name. This test /// mirrors that convention and asserts the file is on disk where the @@ -105,9 +108,9 @@ async fn stale_env_override_falls_through() { fn extracted_binary_present_at_conventional_path() { let version = env!("COPILOT_SDK_CLI_VERSION"); let binary = if cfg!(windows) { - "copilot.exe" + "copilot-runtime.exe" } else { - "copilot" + "copilot-runtime" }; let sanitized = sanitize_version_for_test(version); let path = dirs::cache_dir() @@ -158,21 +161,19 @@ async fn unbundled_resolver_finds_extracted_binary() { /// With `bundled-cli` off, `COPILOT_CLI_EXTRACT_DIR` set at runtime /// redirects the resolver to look directly under the named directory /// (no per-version subdir, matching the build-time write semantics). -/// We place a fake `copilot[.exe]` there and assert the resolver picks -/// it up — failing here means the build-time / runtime convention has -/// drifted. #[cfg(all(not(feature = "bundled-cli"), has_extracted_cli))] #[tokio::test(flavor = "current_thread")] #[serial(copilot_cli_path)] async fn extract_dir_runtime_override_is_honored() { let tmp = tempfile::tempdir().expect("create tempdir"); let binary = if cfg!(windows) { - "copilot.exe" + "copilot-runtime.exe" } else { - "copilot" + "copilot-runtime" }; let fake = tmp.path().join(binary); - std::fs::write(&fake, b"").expect("write fake binary"); + std::fs::write(&fake, b"runtime").expect("write fake binary"); + std::fs::write(tmp.path().join("runtime.node"), b"runtime").expect("write runtime.node"); unset_env("COPILOT_CLI_PATH"); set_env( @@ -198,16 +199,17 @@ async fn extract_dir_runtime_override_is_honored() { /// Build-time version pins, when present, must match the selected bundling /// implementation's checksum format. -/// When absent, build.rs falls through to `../nodejs/package-lock.json` — +/// When absent, build.rs falls through to `../nodejs/package.json` and +/// the release's `SHA256SUMS.txt` — /// both are accepted, this test only checks the pin file's format if it's /// there. #[test] fn pin_file_when_present_is_well_formed() { let manifest_dir = env!("CARGO_MANIFEST_DIR"); - let (filename, value_prefix) = if cfg!(feature = "bundled-in-process") { - ("cli-version-in-process.txt", Some("sha512-")) + let (filename, expected_package_count) = if cfg!(feature = "bundled-in-process") { + ("cli-version-in-process.txt", 8) } else { - ("cli-version.txt", None) + ("cli-version.txt", 6) }; let pin = PathBuf::from(manifest_dir).join(filename); if !pin.is_file() { @@ -229,27 +231,20 @@ fn pin_file_when_present_is_well_formed() { if key.trim() == "version" { saw_version = true; } else { - if let Some(prefix) = value_prefix { - assert!( - value.trim().starts_with(prefix), - "invalid npm integrity for key {key:?}" - ); - } else { - assert_eq!( - value.trim().len(), - 64, - "invalid SHA-256 hash for key {key:?}" - ); - assert!( - value.trim().bytes().all(|byte| byte.is_ascii_hexdigit()), - "invalid SHA-256 hash for key {key:?}" - ); - } + assert_eq!( + value.trim().len(), + 64, + "invalid SHA-256 hash for key {key:?}" + ); + assert!( + value.trim().bytes().all(|byte| byte.is_ascii_hexdigit()), + "invalid SHA-256 hash for key {key:?}" + ); package_count += 1; } } assert!(saw_version, "{filename} missing `version=` line"); - assert_eq!(package_count, 6); + assert_eq!(package_count, expected_package_count); } /// With `bundled-cli` on AND a supported target, `install_bundled_cli` @@ -265,6 +260,14 @@ fn install_bundled_cli_returns_extracted_path() { "install_bundled_cli returned a path that is not a file: {}", first.display() ); + assert_eq!( + first.file_name().and_then(|name| name.to_str()), + Some(if cfg!(windows) { + "copilot.exe" + } else { + "copilot" + }) + ); let second = install_bundled_cli().expect("second call should also succeed"); assert_eq!( @@ -293,30 +296,6 @@ fn install_bundled_cli_returns_extracted_path() { } } -/// `install_bundled_cli` returns the same path the runtime resolver -/// hands to `Client::start` for `CliProgram::Resolve` with no -/// `COPILOT_CLI_PATH` override. Observed indirectly: the binary the -/// public API points at must exist, and `Client::start` must not -/// report `BinaryNotFound` under the same env conditions. -#[cfg(all(feature = "bundled-cli", has_bundled_cli))] -#[tokio::test(flavor = "current_thread")] -#[serial(copilot_cli_path)] -async fn install_bundled_cli_matches_resolver() { - unset_env("COPILOT_CLI_PATH"); - unset_env("COPILOT_CLI_EXTRACT_DIR"); - - let direct = install_bundled_cli().expect("bundled CLI should install"); - assert!(direct.is_file()); - - let opts = ClientOptions::default().with_program(CliProgram::Resolve); - if let Err(e) = Client::start(opts).await { - assert!( - !matches!(e.kind(), ErrorKind::BinaryNotFound { .. }), - "resolver returned BinaryNotFound while install_bundled_cli succeeded: {e}" - ); - } -} - /// With `bundled-cli` off (or the target unsupported), the public API /// reports no bundled CLI and does not fall back to the /// build-time-extracted dev-cache path that `CliProgram::Resolve` uses. @@ -329,3 +308,97 @@ fn install_bundled_cli_is_none_without_embed() { "install_bundled_cli must not fall back to the dev-cache path" ); } + +#[cfg(all(feature = "bundled-cli", has_bundled_cli))] +#[test] +fn install_bundled_runtime_returns_wrapper_bundle() { + let first = install_bundled_runtime().expect("bundled runtime should install"); + assert_eq!( + first.file_name().and_then(|name| name.to_str()), + Some(if cfg!(windows) { + "copilot-runtime.exe" + } else { + "copilot-runtime" + }) + ); + let runtime_node = first + .parent() + .expect("install directory") + .join("runtime.node"); + assert!( + runtime_node.is_file(), + "runtime.node was not installed: {}", + runtime_node.display() + ); + let second = install_bundled_runtime().expect("second call should also succeed"); + assert_eq!(first, second); +} + +#[cfg(all(feature = "bundled-cli", has_bundled_cli))] +#[tokio::test(flavor = "current_thread")] +#[serial(copilot_cli_path)] +async fn bundled_runtime_clean_extract_starts_without_cli_host() { + let temp = tempfile::tempdir().expect("create tempdir"); + let extract_dir = temp.path().join("runtime"); + let empty_path = temp.path().join("empty-path"); + let working_dir = temp.path().join("work"); + std::fs::create_dir(&empty_path).expect("create empty PATH directory"); + std::fs::create_dir(&working_dir).expect("create working directory"); + assert!(!extract_dir.exists()); + + let options = ClientOptions::new() + .with_bundled_cli_extract_dir(&extract_dir) + .with_cwd(&working_dir) + .with_env([("PATH", empty_path.as_os_str())]) + .with_env_remove([ + "COPILOT_RUNTIME_HOST_COMMAND", + "COPILOT_CLI_PATH", + "COPILOT_RUNTIME_PROVIDER_LIB", + ]) + .with_transport(Transport::Stdio) + .with_use_logged_in_user(false); + let client = Client::start(options) + .await + .expect("start bundled runtime from clean extraction"); + let response = client + .ping(Some("hostless runtime")) + .await + .expect("ping bundled runtime"); + assert_eq!(response.message, "pong: hostless runtime"); + + let session = client + .create_session(SessionConfig::default()) + .await + .expect("create session"); + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop bundled runtime"); + + assert!(extract_dir.join("runtime.node").is_file()); + assert!( + extract_dir + .join(if cfg!(windows) { + "copilot-runtime.exe" + } else { + "copilot-runtime" + }) + .is_file() + ); + assert!( + !extract_dir + .join(if cfg!(windows) { + "copilot.exe" + } else { + "copilot" + }) + .exists() + ); +} + +#[cfg(not(all(feature = "bundled-cli", has_bundled_cli)))] +#[test] +fn install_bundled_runtime_is_none_without_embed() { + assert!( + install_bundled_runtime().is_none(), + "install_bundled_runtime must not fall back to the dev-cache path" + ); +} diff --git a/rust/tests/e2e.rs b/rust/tests/e2e.rs index 3a698abd18..03723dfb1b 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/canvas.rs b/rust/tests/e2e/canvas.rs index 2418e9e5a6..a2873eed23 100644 --- a/rust/tests/e2e/canvas.rs +++ b/rust/tests/e2e/canvas.rs @@ -64,7 +64,6 @@ fn canvas_session_config( ctx.approve_all_session_config() .with_request_canvas_renderer(true) - .with_request_extensions(true) .with_extension_info(ExtensionInfo::new("rust-sdk-tests", "canvas-provider")) .with_canvases([decl]) .with_canvas_handler(handler) diff --git a/rust/tests/e2e/client.rs b/rust/tests/e2e/client.rs index 0ac4c9d457..3abedce1a8 100644 --- a/rust/tests/e2e/client.rs +++ b/rust/tests/e2e/client.rs @@ -79,6 +79,11 @@ async fn should_get_status() { #[tokio::test] async fn should_get_authenticated_status() { + // TODO(cli-1.0.81-2): CLI 1.0.81-2 stopped honoring client-level GitHub tokens over the + // in-process (FFI) host, which resolves auth from the ambient environment instead. + if super::support::skip_inprocess("client-level GitHub tokens are not supported in-process") { + return; + } with_e2e_context("client", "should_get_authenticated_status", |ctx| { Box::pin(async move { ctx.set_default_copilot_user(); @@ -99,6 +104,11 @@ async fn should_get_authenticated_status() { #[tokio::test] async fn should_list_models_when_authenticated() { + // TODO(cli-1.0.81-2): CLI 1.0.81-2 stopped honoring client-level GitHub tokens over the + // in-process (FFI) host, which resolves auth from the ambient environment instead. + if super::support::skip_inprocess("client-level GitHub tokens are not supported in-process") { + return; + } with_e2e_context("client", "should_list_models_when_authenticated", |ctx| { Box::pin(async move { ctx.set_default_copilot_user(); @@ -110,7 +120,7 @@ async fn should_list_models_when_authenticated() { let models = client.list_models().await.expect("list models"); assert!( - models.iter().any(|model| model.id == "claude-sonnet-4.5"), + models.iter().any(|model| model.id == "claude-sonnet-5"), "expected default replay model in {models:?}" ); diff --git a/rust/tests/e2e/client_lifecycle.rs b/rust/tests/e2e/client_lifecycle.rs index 75646b4860..92bfa6ff6d 100644 --- a/rust/tests/e2e/client_lifecycle.rs +++ b/rust/tests/e2e/client_lifecycle.rs @@ -1,3 +1,5 @@ +#[cfg(windows)] +use github_copilot_sdk::CliProgram; use github_copilot_sdk::SessionLifecycleEventType; use serde_json::json; @@ -135,6 +137,151 @@ async fn dispose_disconnects_client_and_disposes_rpc_surface_drop() { .await; } +// This test represents github/app#2303: the SDK-hosting GitHub Copilot app +// process exits abruptly, so Client cleanup never runs. The helper starts a +// real CLI client, is terminated through `TerminateProcess`, and relies only +// on Job Object kill-on-close behavior to terminate the CLI. +#[cfg(windows)] +#[tokio::test] +async fn abrupt_host_termination_still_kills_cli_via_job_object() { + with_e2e_context( + "client_lifecycle", + "abrupt_host_termination_still_kills_cli_via_job_object", + |ctx| { + Box::pin(async move { + let options = ctx.client_options(); + let program = match &options.program { + CliProgram::Path(path) => path + .to_str() + .expect("CLI program path is valid UTF-8") + .to_owned(), + CliProgram::Resolve => { + panic!("E2E client options should resolve to an explicit CLI path") + } + }; + let prefix_args: Vec = options + .prefix_args + .iter() + .map(|arg| arg.to_str().expect("prefix arg is valid UTF-8").to_owned()) + .collect(); + let env_pairs: Vec<(String, String)> = options + .env + .iter() + .map(|(k, v)| { + ( + k.to_str().expect("env key is valid UTF-8").to_owned(), + v.to_str().expect("env value is valid UTF-8").to_owned(), + ) + }) + .collect(); + let cwd = options + .working_directory + .to_str() + .expect("cwd is valid UTF-8") + .to_owned(); + let pid_file = ctx.work_dir().join("host-crash-fixture-cli.pid"); + + let mut host = + std::process::Command::new(env!("CARGO_BIN_EXE_copilot-host-crash-fixture")) + .env("HOST_CRASH_FIXTURE_PROGRAM", &program) + .env( + "HOST_CRASH_FIXTURE_PREFIX_ARGS_JSON", + serde_json::to_string(&prefix_args).expect("serialize prefix args"), + ) + .env("HOST_CRASH_FIXTURE_CWD", &cwd) + .env( + "HOST_CRASH_FIXTURE_ENV_JSON", + serde_json::to_string(&env_pairs).expect("serialize env pairs"), + ) + .env("HOST_CRASH_FIXTURE_PID_FILE", &pid_file) + .spawn() + .expect("spawn host-crash fixture process"); + + let cli_pid = wait_for_pid_file_windows(&pid_file).await; + assert!( + process_alive_windows(cli_pid), + "CLI should be alive before its host process is terminated" + ); + + // `Child::kill` maps to `TerminateProcess`, which runs none + // of the target process's cleanup code. + host.kill().expect("terminate host-crash fixture process"); + host.wait().expect("reap host-crash fixture process"); + + let cli_exited = wait_for_process_exit_windows(cli_pid).await; + if !cli_exited { + kill_process_windows(cli_pid); + } + assert!( + cli_exited, + "CLI survived its abruptly terminated host process; Job Object \ + kill-on-close did not terminate it" + ); + }) + }, + ) + .await; +} + +#[cfg(windows)] +async fn wait_for_pid_file_windows(path: &std::path::Path) -> u32 { + super::support::wait_for_condition("host-crash fixture CLI pid file", || async { + path.exists() + }) + .await; + std::fs::read_to_string(path) + .expect("read host-crash fixture CLI pid") + .trim() + .parse() + .expect("parse host-crash fixture CLI pid") +} + +#[cfg(windows)] +async fn wait_for_process_exit_windows(pid: u32) -> bool { + let deadline = std::time::Instant::now() + std::time::Duration::from_secs(15); + while process_alive_windows(pid) { + if std::time::Instant::now() >= deadline { + return false; + } + tokio::time::sleep(std::time::Duration::from_millis(25)).await; + } + true +} + +#[cfg(windows)] +fn process_alive_windows(pid: u32) -> bool { + use windows_sys::Win32::Foundation::{CloseHandle, WAIT_TIMEOUT}; + use windows_sys::Win32::System::Threading::{ + OpenProcess, PROCESS_SYNCHRONIZE, WaitForSingleObject, + }; + + // SAFETY: the process handle is closed before returning. + unsafe { + let process = OpenProcess(PROCESS_SYNCHRONIZE, 0, pid); + if process.is_null() { + return false; + } + let alive = WaitForSingleObject(process, 0) == WAIT_TIMEOUT; + CloseHandle(process); + alive + } +} + +#[cfg(windows)] +fn kill_process_windows(pid: u32) { + use windows_sys::Win32::Foundation::CloseHandle; + use windows_sys::Win32::System::Threading::{OpenProcess, PROCESS_TERMINATE, TerminateProcess}; + + // SAFETY: the pid came from this test's controlled fixture-spawned CLI. + unsafe { + let process = OpenProcess(PROCESS_TERMINATE, 0, pid); + if !process.is_null() { + TerminateProcess(process, 1); + CloseHandle(process); + } + } +} + #[tokio::test] async fn should_receive_session_updated_lifecycle_event_for_non_ephemeral_activity() { with_e2e_context( diff --git a/rust/tests/e2e/client_options.rs b/rust/tests/e2e/client_options.rs index fc1ceebb83..fa8e6f68f7 100644 --- a/rust/tests/e2e/client_options.rs +++ b/rust/tests/e2e/client_options.rs @@ -27,7 +27,7 @@ async fn should_forward_advanced_session_creation_options_to_the_cli() { SessionConfig::default() .with_session_id("advanced-session-id") .with_client_name("rust-sdk-e2e-client") - .with_model("claude-sonnet-4.5") + .with_model("claude-sonnet-5") .with_reasoning_effort("low") .with_reasoning_summary(ReasoningSummary::None) .with_context_tier("long_context") @@ -90,7 +90,7 @@ async fn should_forward_advanced_session_creation_options_to_the_cli() { [ ("sessionId", json!("advanced-session-id")), ("clientName", json!("rust-sdk-e2e-client")), - ("model", json!("claude-sonnet-4.5")), + ("model", json!("claude-sonnet-5")), ("reasoningEffort", json!("low")), ("reasoningSummary", json!("none")), ("contextTier", json!("long_context")), diff --git a/rust/tests/e2e/commands.rs b/rust/tests/e2e/commands.rs index d110d3b352..78c8e07616 100644 --- a/rust/tests/e2e/commands.rs +++ b/rust/tests/e2e/commands.rs @@ -105,6 +105,7 @@ async fn session_commands_invoke_known_builtin_returns_expected_result() { .invoke(CommandsInvokeRequest { name: "context".to_string(), input: None, + ..Default::default() }) .await .expect("invoke context"); @@ -119,6 +120,7 @@ async fn session_commands_invoke_known_builtin_returns_expected_result() { assert!(!prompt.prompt.trim().is_empty()); } SlashCommandInvocationResult::Completed(_) => {} + unexpected => panic!("unexpected command result: {unexpected:?}"), } session.disconnect().await.expect("disconnect session"); @@ -208,6 +210,7 @@ async fn session_commands_enqueue_and_respond_to_queued_command() { .commands() .enqueue(EnqueueCommandParams { command: "/help".to_string(), + display_text: None, }) .await .expect("enqueue command"); diff --git a/rust/tests/e2e/copilot_request_handler.rs b/rust/tests/e2e/copilot_request_handler.rs index 46b4e510cd..478845f48e 100644 --- a/rust/tests/e2e/copilot_request_handler.rs +++ b/rust/tests/e2e/copilot_request_handler.rs @@ -101,8 +101,8 @@ fn sse(event_type: &str, data: &Value) -> String { fn model_catalog(supported_endpoints: Option<&[&str]>) -> String { let mut model = json!({ - "id": "claude-sonnet-4.5", - "name": "Claude Sonnet 4.5", + "id": "claude-sonnet-5", + "name": "Claude Sonnet 5", "object": "model", "vendor": "Anthropic", "version": "1", @@ -110,7 +110,7 @@ fn model_catalog(supported_endpoints: Option<&[&str]>) -> String { "model_picker_enabled": true, "capabilities": { "type": "chat", - "family": "claude-sonnet-4.5", + "family": "claude-sonnet-5", "tokenizer": "o200k_base", "limits": { "max_context_window_tokens": 200000, @@ -232,7 +232,7 @@ fn synth_inference_response(url: &str, body: &[u8], text: &str) -> CopilotHttpRe "id": "chatcmpl-stub-1", "object": "chat.completion.chunk", "created": 1, - "model": "claude-sonnet-4.5", + "model": "claude-sonnet-5", }) }; let mut c1 = base(); @@ -257,7 +257,7 @@ fn synth_inference_response(url: &str, body: &[u8], text: &str) -> CopilotHttpRe "id": "chatcmpl-stub-1", "object": "chat.completion", "created": 1, - "model": "claude-sonnet-4.5", + "model": "claude-sonnet-5", "choices": [{ "index": 0, "message": { "role": "assistant", "content": text }, @@ -668,14 +668,14 @@ async fn threads_session_id_into_inference() { let before = handler.inference_records().len(); let byok_config = SessionConfig::default() .with_permission_handler(Arc::new(ApproveAllHandler)) - .with_model("claude-sonnet-4.5") + .with_model("claude-sonnet-5") .with_provider( ProviderConfig::new("https://byok.invalid/v1") .with_provider_type("openai") .with_wire_api("responses") .with_api_key("byok-secret") - .with_model_id("claude-sonnet-4.5") - .with_wire_model("claude-sonnet-4.5"), + .with_model_id("claude-sonnet-5") + .with_wire_model("claude-sonnet-5"), ); let byok_session = client .create_session(byok_config) diff --git a/rust/tests/e2e/github_telemetry.rs b/rust/tests/e2e/github_telemetry.rs index 2047ee34ff..02af0e2ae8 100644 --- a/rust/tests/e2e/github_telemetry.rs +++ b/rust/tests/e2e/github_telemetry.rs @@ -9,6 +9,11 @@ use super::support::{DEFAULT_TEST_TOKEN, with_e2e_context_no_snapshot}; #[tokio::test] async fn should_forward_github_telemetry_on_session_create() { + // TODO(cli-1.0.81-2): CLI 1.0.81-2 does not forward GitHub telemetry notifications over + // the in-process (FFI) host, mirroring the existing telemetry-configuration limitation. + if super::support::skip_inprocess("GitHub telemetry forwarding is not honored in-process") { + return; + } with_e2e_context_no_snapshot(|ctx| { Box::pin(async move { ctx.set_default_copilot_user(); diff --git a/rust/tests/e2e/mode_handlers.rs b/rust/tests/e2e/mode_handlers.rs index 7ab6fe5bfa..b796096d06 100644 --- a/rust/tests/e2e/mode_handlers.rs +++ b/rust/tests/e2e/mode_handlers.rs @@ -118,6 +118,7 @@ async fn should_invoke_exit_plan_mode_handler_when_model_uses_tool() { .mode() .set(ModeSetRequest { mode: SessionMode::Plan, + ..Default::default() }) .await .expect("set plan mode"); diff --git a/rust/tests/e2e/permissions.rs b/rust/tests/e2e/permissions.rs index 8f594841fd..65b37928db 100644 --- a/rust/tests/e2e/permissions.rs +++ b/rust/tests/e2e/permissions.rs @@ -50,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(_), + .. + } )); } diff --git a/rust/tests/e2e/provider_endpoint.rs b/rust/tests/e2e/provider_endpoint.rs index 3953aad669..6aeff7b1a7 100644 --- a/rust/tests/e2e/provider_endpoint.rs +++ b/rust/tests/e2e/provider_endpoint.rs @@ -91,6 +91,11 @@ async fn byok_provider_endpoint_returns_configured_endpoint() { #[tokio::test] #[allow(deprecated)] async fn capi_provider_endpoint_returns_resolved_credentials() { + // TODO(cli-1.0.81-2): CLI 1.0.81-2 stopped honoring client-level GitHub tokens over the + // in-process (FFI) host, which resolves auth from the ambient environment instead. + if super::support::skip_inprocess("client-level GitHub tokens are not supported in-process") { + return; + } with_e2e_context( "provider-endpoint", "capi_provider_endpoint_returns_resolved_credentials", diff --git a/rust/tests/e2e/rewind.rs b/rust/tests/e2e/rewind.rs new file mode 100644 index 0000000000..485c389ece --- /dev/null +++ b/rust/tests/e2e/rewind.rs @@ -0,0 +1,158 @@ +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 ORIGINAL_FILE_CONTENT: &str = "Original rewind content"; +const PREPARED_FILE_CONTENT: &str = "Prepared rewind content"; +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); + std::fs::write(&file_path, ORIGINAL_FILE_CONTENT).expect("write original file"); + let client = ctx.start_client().await; + let session = client + .create_session( + ctx.approve_all_session_config() + .with_model("claude-sonnet-5") + .with_enable_file_change_tracking(true), + ) + .await + .expect("create session"); + + let ready = session + .send_and_wait(format!( + "Use the edit tool to replace the exact contents of {FILE_NAME} from \ + {ORIGINAL_FILE_CONTENT} to {PREPARED_FILE_CONTENT}. After the tool \ + succeeds, reply with exactly SDK_REWIND_READY." + )) + .await + .expect("send readiness turn") + .expect("readiness response"); + assert_eq!(assistant_message_content(&ready), "SDK_REWIND_READY"); + assert_eq!( + std::fs::read_to_string(&file_path).expect("read prepared file"), + PREPARED_FILE_CONTENT + ); + + let response = session + .send_and_wait(format!( + "Use the edit tool to replace the exact contents of {FILE_NAME} from \ + {PREPARED_FILE_CONTENT} to {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(), 2); + let rewind_point = &rewind_points.points[1]; + assert!(rewind_point.turn_changed_files); + 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_eq!( + std::fs::read_to_string(&file_path).expect("read restored file"), + PREPARED_FILE_CONTENT + ); + + 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(30); + loop { + let result = session + .rpc() + .history() + .list_rewind_points() + .await + .expect("list rewind points"); + if result.unavailable_reason.is_none() + && result.points.len() == 2 + && result.points[1].turn_changed_files + && result.points[1].can_restore_files + && result.points[1].file_count == 1 + { + return result; + } + assert!( + tokio::time::Instant::now() < deadline, + "timed out waiting for a restorable rewind point: {result:?}" + ); + 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 d7537f3141..56b9198ab7 100644 --- a/rust/tests/e2e/rpc_additional_edge_cases.rs +++ b/rust/tests/e2e/rpc_additional_edge_cases.rs @@ -277,6 +277,7 @@ async fn mode_set_to_same_value_multiple_times_stays_stable() { .mode() .set(ModeSetRequest { mode: SessionMode::Plan, + ..Default::default() }) .await .expect("set mode"); diff --git a/rust/tests/e2e/rpc_event_side_effects.rs b/rust/tests/e2e/rpc_event_side_effects.rs index e8d7b29b23..06adc3f65c 100644 --- a/rust/tests/e2e/rpc_event_side_effects.rs +++ b/rust/tests/e2e/rpc_event_side_effects.rs @@ -40,6 +40,7 @@ async fn should_emit_mode_changed_event_when_mode_set() { .mode() .set(ModeSetRequest { mode: SessionMode::Plan, + ..Default::default() }) .await .expect("set mode"); diff --git a/rust/tests/e2e/rpc_mcp_and_skills.rs b/rust/tests/e2e/rpc_mcp_and_skills.rs index d5a295e073..8cf3efd998 100644 --- a/rust/tests/e2e/rpc_mcp_and_skills.rs +++ b/rust/tests/e2e/rpc_mcp_and_skills.rs @@ -9,9 +9,9 @@ use github_copilot_sdk::rpc::{ McpAppsSetHostContextRequest, McpCancelSamplingExecutionParams, McpDisableRequest, McpEnableRequest, McpExecuteSamplingParams, McpExecuteSamplingRequest, McpOauthLoginRequest, McpResourcesReadRequest, McpSamplingExecutionAction, McpSetEnvValueModeDetails, - McpSetEnvValueModeParams, PermissionsAllowAllMode, PermissionsSetAllowAllRequest, - SkillsDisableRequest, SkillsEnableRequest, + McpSetEnvValueModeParams, PermissionsSetModeRequest, SkillsDisableRequest, SkillsEnableRequest, }; +use github_copilot_sdk::session_events::PermissionMode; use github_copilot_sdk::{IndexMap, McpServerConfig, McpStdioServerConfig}; #[tokio::test] @@ -361,10 +361,9 @@ async fn should_list_extensions() { session .rpc() .permissions() - .set_allow_all(PermissionsSetAllowAllRequest { - enabled: None, - mode: Some(PermissionsAllowAllMode::On), - model: None, + .set_mode(PermissionsSetModeRequest { + assisted_approval_model: None, + mode: PermissionMode::AllowAll, source: None, }) .await @@ -715,10 +714,9 @@ async fn should_report_error_when_extensions_are_not_available() { session .rpc() .permissions() - .set_allow_all(PermissionsSetAllowAllRequest { - enabled: None, - mode: Some(PermissionsAllowAllMode::On), - model: None, + .set_mode(PermissionsSetModeRequest { + assisted_approval_model: None, + mode: PermissionMode::AllowAll, source: None, }) .await diff --git a/rust/tests/e2e/rpc_mcp_lifecycle.rs b/rust/tests/e2e/rpc_mcp_lifecycle.rs index 9e135f1e97..8e5b74c05b 100644 --- a/rust/tests/e2e/rpc_mcp_lifecycle.rs +++ b/rust/tests/e2e/rpc_mcp_lifecycle.rs @@ -138,6 +138,10 @@ async fn should_stop_running_mcp_server() { .await; } +// TODO(cli-1.0.81-2): CLI 1.0.81-2 no longer installs an MCP config from the inline start +// payload, so `session.mcp.startServer` reports "has no installed config to start". +// Re-enable once the runtime fix ships. +#[ignore = "blocked on CLI 1.0.81-2 MCP installed-config regression"] #[tokio::test] async fn should_start_and_restart_mcp_server() { super::support::with_shared_e2e_context( @@ -205,6 +209,7 @@ async fn should_start_and_restart_mcp_server() { // internal methods generically; it never exercised a supported wire API. #[tokio::test] +#[ignore = "blocked on CLI 1.0.81-6 missing session.mcp.reloadWithConfig handler"] async fn should_reload_mcp_servers_with_config() { super::support::with_shared_e2e_context( &E2E, @@ -248,6 +253,7 @@ async fn should_reload_mcp_servers_with_config() { } #[tokio::test] +#[ignore = "blocked on CLI 1.0.81-6 missing session.mcp.configureGitHub handler"] async fn should_configure_github_mcp_server() { super::support::with_shared_e2e_context( &E2E, diff --git a/rust/tests/e2e/rpc_queue.rs b/rust/tests/e2e/rpc_queue.rs index 6f4f881659..d5f1228302 100644 --- a/rust/tests/e2e/rpc_queue.rs +++ b/rust/tests/e2e/rpc_queue.rs @@ -153,6 +153,7 @@ async fn pendingitems_reports_queued_command_and_remove_and_clear_update_queue() .commands() .enqueue(EnqueueCommandParams { command: first_command, + display_text: None, }) .await .expect("enqueue command"); @@ -167,6 +168,7 @@ async fn pendingitems_reports_queued_command_and_remove_and_clear_update_queue() .commands() .enqueue(EnqueueCommandParams { command: second_command.clone(), + display_text: None, }) .await .expect("enqueue second command"); @@ -187,6 +189,7 @@ async fn pendingitems_reports_queued_command_and_remove_and_clear_update_queue() .commands() .enqueue(EnqueueCommandParams { command: third_command.clone(), + display_text: None, }) .await .expect("enqueue third command"); diff --git a/rust/tests/e2e/rpc_server.rs b/rust/tests/e2e/rpc_server.rs index caa846ba04..2e80ae1d70 100644 --- a/rust/tests/e2e/rpc_server.rs +++ b/rust/tests/e2e/rpc_server.rs @@ -48,6 +48,11 @@ async fn should_call_rpc_ping_with_typed_params_and_result() { #[tokio::test] async fn should_call_rpc_models_list_with_typed_result() { + // TODO(cli-1.0.81-2): CLI 1.0.81-2 stopped honoring client-level GitHub tokens over the + // in-process (FFI) host, which resolves auth from the ambient environment instead. + if super::support::skip_inprocess("client-level GitHub tokens are not supported in-process") { + return; + } with_e2e_context( "rpc_server", "should_call_rpc_models_list_with_typed_result", @@ -65,7 +70,7 @@ async fn should_call_rpc_models_list_with_typed_result() { result .models .iter() - .any(|model| model.id == "claude-sonnet-4.5") + .any(|model| model.id == "claude-sonnet-5") ); assert!(result.models.iter().all(|model| !model.name.is_empty())); client.stop().await.expect("stop client"); @@ -77,6 +82,11 @@ async fn should_call_rpc_models_list_with_typed_result() { #[tokio::test] async fn should_call_rpc_account_get_quota_when_authenticated() { + // TODO(cli-1.0.81-2): CLI 1.0.81-2 stopped honoring client-level GitHub tokens over the + // in-process (FFI) host, which resolves auth from the ambient environment instead. + if super::support::skip_inprocess("client-level GitHub tokens are not supported in-process") { + return; + } with_e2e_context( "rpc_server", "should_call_rpc_account_get_quota_when_authenticated", diff --git a/rust/tests/e2e/rpc_server_misc.rs b/rust/tests/e2e/rpc_server_misc.rs index 47ae4ecbd2..e53cce2e59 100644 --- a/rust/tests/e2e/rpc_server_misc.rs +++ b/rust/tests/e2e/rpc_server_misc.rs @@ -1,6 +1,6 @@ use github_copilot_sdk::Client; use github_copilot_sdk::rpc::{ - AccountLoginRequest, AccountLogoutRequest, AgentRegistrySpawnRequest, + AccountLoginRequest, AccountLogoutRequest, AgentRegistrySpawnRequest, AuthInfo, SendAttachmentsToMessageParams, SessionsOpenStatus, UserSettingsSetRequest, }; use serde_json::{Map, Value, json}; @@ -150,7 +150,7 @@ async fn should_login_list_getcurrentauth_and_logout_account() { .account() .login(AccountLoginRequest { host: "https://github.com".to_string(), - login: "rust-account-user".to_string(), + login: Some("rust-account-user".to_string()), token: "rust-account-token".to_string(), }) .await @@ -164,8 +164,11 @@ async fn should_login_list_getcurrentauth_and_logout_account() { .await .expect("get current auth after login"); let auth_info = current.auth_info.expect("auth info after login"); - assert_eq!(auth_info["login"], json!("rust-account-user")); - assert_eq!(auth_info["host"], json!("https://github.com")); + let AuthInfo::User(user_auth_info) = &auth_info else { + panic!("expected user auth info, got {auth_info:?}"); + }; + assert_eq!(user_auth_info.login, "rust-account-user"); + assert_eq!(user_auth_info.host, "https://github.com"); let users = client .rpc() @@ -173,10 +176,12 @@ async fn should_login_list_getcurrentauth_and_logout_account() { .get_all_users() .await .expect("get all users"); - if let Some(user) = users - .iter() - .find(|user| user.auth_info["login"] == json!("rust-account-user")) - { + if let Some(user) = users.iter().find(|user| { + matches!( + &user.auth_info, + AuthInfo::User(auth_info) if auth_info.login == "rust-account-user" + ) + }) { user.token .as_deref() .filter(|token| *token == "rust-account-token") @@ -188,7 +193,10 @@ async fn should_login_list_getcurrentauth_and_logout_account() { let logout = client .rpc() .account() - .logout(AccountLogoutRequest { auth_info }) + .logout(AccountLogoutRequest { + auth_info: Some(auth_info), + ..Default::default() + }) .await .expect("account logout"); assert!(!logout.has_more_users); diff --git a/rust/tests/e2e/rpc_session_state.rs b/rust/tests/e2e/rpc_session_state.rs index c705d231c1..e6d6a2b546 100644 --- a/rust/tests/e2e/rpc_session_state.rs +++ b/rust/tests/e2e/rpc_session_state.rs @@ -1,5 +1,7 @@ use std::collections::HashMap; +use std::sync::Arc; +use async_trait::async_trait; use github_copilot_sdk::rpc::{ AuthInfoType, HistoryTruncateRequest, LspInitializeRequest, MetadataContextInfoRequest, MetadataRecomputeContextTokensRequest, MetadataRecordContextChangeRequest, @@ -7,19 +9,25 @@ use github_copilot_sdk::rpc::{ ModelSetReasoningEffortRequest, ModelSwitchToRequest, NameSetAutoRequest, NameSetRequest, PermissionsResetSessionApprovalsRequest, PermissionsSetApproveAllRequest, PlanUpdateRequest, SessionSetCredentialsParams, SessionUpdateOptionsParams, SessionWorkingDirectoryContext, - SessionWorkingDirectoryContextHostType, SessionsForkRequest, ShutdownRequest, - TelemetrySetFeatureOverridesRequest, WorkspacesCreateFileRequest, WorkspacesReadFileRequest, + SessionWorkingDirectoryContextHostType, SessionsForkRequest, SettableAuthInfo, ShutdownRequest, + TelemetrySetFeatureOverridesRequest, UserAuthInfo, WorkspacesCreateFileRequest, + WorkspacesReadFileRequest, }; use github_copilot_sdk::session_events::{ SessionContextChangedData, SessionEventType, SessionMode, SessionShutdownData, SessionTitleChangedData, SessionWorkspaceFileChangedData, ShutdownType, WorkspaceFileChangedOperation, }; +use github_copilot_sdk::tool::ToolHandler; +use github_copilot_sdk::{Error, Tool, ToolInvocation, ToolResult}; use serde_json::json; +use tokio::sync::{Mutex, mpsc, oneshot}; -use super::support::{assistant_message_content, wait_for_condition, wait_for_event}; +use super::support::{ + assistant_message_content, recv_with_timeout, wait_for_condition, wait_for_event, +}; -const MODEL_ID: &str = "claude-sonnet-4.5"; +const MODEL_ID: &str = "claude-sonnet-5"; #[tokio::test] async fn should_call_session_rpc_model_getcurrent() { @@ -129,6 +137,7 @@ async fn should_get_and_set_session_mode() { .mode() .set(ModeSetRequest { mode: SessionMode::Plan, + ..Default::default() }) .await .expect("set plan mode"); @@ -207,7 +216,10 @@ async fn should_set_and_get_each_session_mode_value() { session .rpc() .mode() - .set(ModeSetRequest { mode: mode.clone() }) + .set(ModeSetRequest { + mode: mode.clone(), + ..Default::default() + }) .await .expect("set mode"); assert_eq!(session.rpc().mode().get().await.expect("get mode"), mode); @@ -757,18 +769,18 @@ async fn should_update_options_and_initialize_session_services() { .await .expect("create session"); + let mut update_options = SessionUpdateOptionsParams::default(); + update_options.ask_user_disabled = Some(true); + update_options.available_tools = Some(vec!["view".to_string()]); + update_options.client_name = Some("rust-rpc-e2e".to_string()); + update_options.enable_streaming = Some(true); + update_options.model = Some(MODEL_ID.to_string()); + update_options.working_directory = Some(ctx.work_dir().display().to_string()); + let options = session .rpc() .options() - .update(SessionUpdateOptionsParams { - ask_user_disabled: Some(true), - available_tools: Some(vec!["view".to_string()]), - client_name: Some("rust-rpc-e2e".to_string()), - enable_streaming: Some(true), - model: Some(MODEL_ID.to_string()), - working_directory: Some(ctx.work_dir().display().to_string()), - ..SessionUpdateOptionsParams::default() - }) + .update(update_options) .await .expect("update options"); assert!(options.success); @@ -888,10 +900,10 @@ async fn should_set_auth_credentials() { .rpc() .git_hub_auth() .set_credentials(SessionSetCredentialsParams { - credentials: Some(json!({ - "type": "user", - "host": "github.com", - "login": "rpc-session-user" + credentials: Some(SettableAuthInfo::User(UserAuthInfo { + host: "github.com".to_string(), + login: "rpc-session-user".to_string(), + ..Default::default() })), }) .await @@ -1107,8 +1119,28 @@ async fn should_report_processing_and_context_metadata() { Box::pin(async move { ctx.set_default_copilot_user(); let client = ctx.start_client().await; + let (entered_tx, mut entered_rx) = mpsc::unbounded_channel(); + let (release_tx, release_rx) = oneshot::channel(); let session = client - .create_session(ctx.approve_all_session_config().with_model(MODEL_ID)) + .create_session( + ctx.approve_all_session_config() + .with_model(MODEL_ID) + .with_tools(vec![ + Tool::new("processing_barrier") + .with_description( + "Blocks until the processing state has been observed", + ) + .with_parameters(json!({ + "type": "object", + "properties": {}, + "additionalProperties": false + })) + .with_handler(Arc::new(ProcessingBarrierTool { + entered_tx, + release_rx: Mutex::new(Some(release_rx)), + })), + ]), + ) .await .expect("create session"); @@ -1122,19 +1154,20 @@ async fn should_report_processing_and_context_metadata() { .processing ); session - .send("Reply with exactly: RUST_CONTEXT_INFO") + .send("Use processing_barrier, then reply with exactly: RUST_CONTEXT_INFO") .await .expect("send"); - wait_for_condition("session processing started", || async { + recv_with_timeout(&mut entered_rx, "processing barrier entry").await; + assert!( session .rpc() .metadata() .is_processing() .await - .expect("processing poll") + .expect("processing during tool call") .processing - }) - .await; + ); + release_tx.send(()).expect("release processing barrier"); wait_for_condition("session processing completed", || async { !session .rpc() @@ -1176,6 +1209,26 @@ async fn should_report_processing_and_context_metadata() { .await; } +struct ProcessingBarrierTool { + entered_tx: mpsc::UnboundedSender<()>, + release_rx: Mutex>>, +} + +#[async_trait] +impl ToolHandler for ProcessingBarrierTool { + async fn call(&self, _invocation: ToolInvocation) -> Result { + let _ = self.entered_tx.send(()); + self.release_rx + .lock() + .await + .take() + .expect("processing barrier called once") + .await + .expect("processing barrier released"); + Ok(ToolResult::Text("PROCESSING_OBSERVED".to_string())) + } +} + fn expect_err_contains(result: Result, expected: &str) { let err = match result { Ok(_) => panic!("expected error containing {expected:?}"), diff --git a/rust/tests/e2e/rpc_session_state_extras.rs b/rust/tests/e2e/rpc_session_state_extras.rs index f43359f0b1..e764c53933 100644 --- a/rust/tests/e2e/rpc_session_state_extras.rs +++ b/rust/tests/e2e/rpc_session_state_extras.rs @@ -3,15 +3,16 @@ use std::collections::HashMap; use github_copilot_sdk::Client; use github_copilot_sdk::rpc::{ CompletionsRequestRequest, MetadataContextHeaviestMessagesRequest, ModelSwitchToRequest, - NamedProviderConfig, PermissionsSetAllowAllRequest, ProviderAddRequest, ProviderConfigType, + NamedProviderConfig, PermissionsSetModeRequest, ProviderAddRequest, ProviderConfigType, ProviderConfigWireApi, ProviderModelConfig, SessionVisibilityStatus, SubagentSettingsEntry, SubagentSettingsEntryContextTier, UpdateSubagentSettingsRequest, UpdateSubagentSettingsRequestSubagents, VisibilitySetRequest, }; +use github_copilot_sdk::session_events::PermissionMode; use super::support::{assistant_message_content, with_e2e_context}; -const MODEL_ID: &str = "claude-sonnet-4.5"; +const MODEL_ID: &str = "claude-sonnet-5"; #[tokio::test] async fn should_list_models_for_session() { @@ -103,55 +104,55 @@ async fn should_get_and_set_allowall_permissions() { let initial = session .rpc() .permissions() - .get_allow_all() + .get_mode() .await - .expect("get initial allow-all"); - assert!(!initial.enabled); + .expect("get initial permission mode"); + assert_eq!(initial.mode, PermissionMode::Manual); let enable = session .rpc() .permissions() - .set_allow_all(PermissionsSetAllowAllRequest { - enabled: Some(true), - mode: None, - model: None, + .set_mode(PermissionsSetModeRequest { + assisted_approval_model: None, + mode: PermissionMode::AllowAll, source: None, }) .await - .expect("enable allow-all"); + .expect("set allow-all mode"); assert!(enable.success); - assert!(enable.enabled); - assert!( + assert_eq!(enable.mode, PermissionMode::AllowAll); + assert_eq!( session .rpc() .permissions() - .get_allow_all() + .get_mode() .await - .expect("get enabled allow-all") - .enabled + .expect("get allow-all mode") + .mode, + PermissionMode::AllowAll ); let disable = session .rpc() .permissions() - .set_allow_all(PermissionsSetAllowAllRequest { - enabled: Some(false), - mode: None, - model: None, + .set_mode(PermissionsSetModeRequest { + assisted_approval_model: None, + mode: PermissionMode::Manual, source: None, }) .await - .expect("disable allow-all"); + .expect("set manual mode"); assert!(disable.success); - assert!(!disable.enabled); - assert!( - !session + assert_eq!(disable.mode, PermissionMode::Manual); + assert_eq!( + session .rpc() .permissions() - .get_allow_all() + .get_mode() .await - .expect("get disabled allow-all") - .enabled + .expect("get manual mode") + .mode, + PermissionMode::Manual ); session.disconnect().await.expect("disconnect session"); @@ -304,7 +305,7 @@ async fn should_add_byok_provider_and_model_at_runtime() { id: "small".to_string(), max_context_window_tokens: None, max_output_tokens: None, - max_prompt_tokens: Some(4096.0), + max_prompt_tokens: Some(4096), model_id: None, name: Some("Rust Added Model".to_string()), provider: "rust-e2e-provider".to_string(), @@ -320,13 +321,8 @@ async fn should_add_byok_provider_and_model_at_runtime() { .rpc() .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, - reasoning_summary: None, - verbosity: None, + ..Default::default() }) .await .expect("switch to added model"); @@ -499,6 +495,7 @@ async fn should_update_and_clear_live_subagent_settings() { ), effort_level: Some("low".to_string()), model: Some("gpt-5-mini".to_string()), + model_policy: None, }, )])), disabled_subagents: Some(vec!["legacy-agent".to_string()]), diff --git a/rust/tests/e2e/rpc_tasks_and_handlers.rs b/rust/tests/e2e/rpc_tasks_and_handlers.rs index b3010ab78a..540238ddd0 100644 --- a/rust/tests/e2e/rpc_tasks_and_handlers.rs +++ b/rust/tests/e2e/rpc_tasks_and_handlers.rs @@ -273,6 +273,7 @@ async fn should_return_expected_results_for_missing_pending_handler_requestids() result: UIElicitationResponse { action: UIElicitationResponseAction::Cancel, content: Default::default(), + meta: None, }, }) .await @@ -404,6 +405,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, }) diff --git a/rust/tests/e2e/rpc_ui_ephemeral_query.rs b/rust/tests/e2e/rpc_ui_ephemeral_query.rs index 2fa421cc6d..5bb09882b0 100644 --- a/rust/tests/e2e/rpc_ui_ephemeral_query.rs +++ b/rust/tests/e2e/rpc_ui_ephemeral_query.rs @@ -1,5 +1,9 @@ use github_copilot_sdk::rpc::UIEphemeralQueryRequest; +// TODO(cli-1.0.81-2): CLI 1.0.81-5 still fails session.ui.ephemeralQuery against the +// recorded snapshot on macOS ("Failed to get response from the AI model"). Re-enable +// once the runtime fix ships. +#[ignore = "blocked on CLI 1.0.81-5 session.ui.ephemeralQuery regression on macOS"] #[tokio::test] async fn should_answer_ephemeral_query() { super::support::with_shared_e2e_context( diff --git a/rust/tests/e2e/session.rs b/rust/tests/e2e/session.rs index e2ca76c478..04c99422ea 100644 --- a/rust/tests/e2e/session.rs +++ b/rust/tests/e2e/session.rs @@ -37,7 +37,7 @@ async fn shouldcreateanddisconnectsessions() { let session = client .create_session( ctx.approve_all_session_config() - .with_model("claude-sonnet-4.5"), + .with_model("claude-sonnet-5"), ) .await .expect("create session"); @@ -501,8 +501,7 @@ async fn should_abort_a_session() { #[tokio::test] async fn should_resume_a_session_using_the_same_client() { - super::support::with_shared_e2e_context( - &E2E, + super::support::with_dedicated_e2e_context( "session", "should_resume_a_session_using_the_same_client", |ctx| { @@ -1752,4 +1751,4 @@ fn secret_number_tool() -> Tool { .with_handler(Arc::new(SecretNumberTool)) } static E2E: super::support::SharedE2eGroup = - super::support::SharedE2eGroup::standard("session", 30); + super::support::SharedE2eGroup::standard("session", 29); diff --git a/rust/tests/e2e/session_config.rs b/rust/tests/e2e/session_config.rs index c3f6b57aea..2c844a8415 100644 --- a/rust/tests/e2e/session_config.rs +++ b/rust/tests/e2e/session_config.rs @@ -362,7 +362,7 @@ fn anthropic_message_stream_body(text: &str) -> String { "id": "msg_stub_1", "type": "message", "role": "assistant", - "model": "claude-sonnet-4.5", + "model": "claude-sonnet-5", "content": [], "stop_reason": null, "stop_sequence": null, @@ -414,8 +414,8 @@ fn synth_non_inference_response(url: &str) -> CopilotHttpResponse { json_headers(), json!({ "data": [{ - "id": "claude-sonnet-4.5", - "name": "Claude Sonnet 4.5", + "id": "claude-sonnet-5", + "name": "Claude Sonnet 5", "object": "model", "vendor": "Anthropic", "version": "1", @@ -423,7 +423,7 @@ fn synth_non_inference_response(url: &str) -> CopilotHttpResponse { "model_picker_enabled": true, "capabilities": { "type": "chat", - "family": "claude-sonnet-4.5", + "family": "claude-sonnet-5", "tokenizer": "o200k_base", "limits": { "max_context_window_tokens": 200000, @@ -459,7 +459,7 @@ fn synth_inference_response(url: &str, body: &[u8]) -> CopilotHttpResponse { "id": "msg_stub_1", "type": "message", "role": "assistant", - "model": "claude-sonnet-4.5", + "model": "claude-sonnet-5", "content": [{ "type": "text", "text": SYNTHETIC_TEXT }], "stop_reason": "end_turn", "stop_sequence": null, @@ -474,7 +474,7 @@ fn synth_inference_response(url: &str, body: &[u8]) -> CopilotHttpResponse { "id": "chatcmpl-stub-1", "object": "chat.completion", "created": 1, - "model": "claude-sonnet-4.5", + "model": "claude-sonnet-5", "choices": [{ "index": 0, "message": { "role": "assistant", "content": SYNTHETIC_TEXT }, @@ -489,8 +489,8 @@ fn anthropic_provider() -> ProviderConfig { ProviderConfig::new("https://anthropic-citations.invalid/v1") .with_provider_type("anthropic") .with_api_key("test-provider-key") - .with_model_id("claude-sonnet-4.5") - .with_wire_model("claude-sonnet-4.5") + .with_model_id("claude-sonnet-5") + .with_wire_model("claude-sonnet-5") } fn pdf_attachment() -> Attachment { @@ -532,7 +532,7 @@ async fn should_enable_citations_for_anthropic_file_attachments_on_create() { .create_session( SessionConfig::default() .with_permission_handler(Arc::new(ApproveAllHandler)) - .with_model("claude-sonnet-4.5") + .with_model("claude-sonnet-5") .with_enable_citations(true) .with_provider(anthropic_provider()), ) @@ -592,7 +592,7 @@ async fn should_enable_citations_for_anthropic_file_attachments_on_resume() { .resume_session( ResumeSessionConfig::new(session1.id().clone()) .with_permission_handler(Arc::new(ApproveAllHandler)) - .with_model("claude-sonnet-4.5") + .with_model("claude-sonnet-5") .with_enable_citations(true) .with_provider(anthropic_provider()), ) diff --git a/rust/tests/e2e/session_fs_sqlite.rs b/rust/tests/e2e/session_fs_sqlite.rs index 8ba712bb45..919563c691 100644 --- a/rust/tests/e2e/session_fs_sqlite.rs +++ b/rust/tests/e2e/session_fs_sqlite.rs @@ -407,6 +407,9 @@ fn sqlite_session_config( .with_session_fs_provider(provider) } +// TODO(cli-1.0.81-2): CLI 1.0.81-2 hangs servicing the nested SQL tool request, so this test +// times out. Re-enable once the runtime reentrancy fix ships. +#[ignore = "blocked on CLI 1.0.81-2 nested tool-request servicing regression"] #[tokio::test] async fn should_route_sql_queries_through_the_sessionfs_sqlite_handler() { super::support::with_shared_e2e_context( @@ -478,6 +481,9 @@ async fn should_route_sql_queries_through_the_sessionfs_sqlite_handler() { .await; } +// TODO(cli-1.0.81-2): CLI 1.0.81-2 hangs servicing the nested SQL tool request, so this test +// times out. Re-enable once the runtime reentrancy fix ships. +#[ignore = "blocked on CLI 1.0.81-2 nested tool-request servicing regression"] #[tokio::test] async fn should_allow_subagents_to_use_sql_tool_via_inherited_sessionfs() { super::support::with_shared_e2e_context( diff --git a/rust/tests/e2e/support.rs b/rust/tests/e2e/support.rs index d65b049f9b..0b7bbae7a7 100644 --- a/rust/tests/e2e/support.rs +++ b/rust/tests/e2e/support.rs @@ -31,6 +31,7 @@ static SHARED_E2E_RUNTIME: LazyLock = LazyLock::new(|| .expect("create shared E2E runtime") }); const SHARED_E2E_CLEANUP_TIMEOUT: Duration = Duration::from_secs(10); +const PROXY_STARTUP_TIMEOUT: Duration = Duration::from_secs(30); pub const DEFAULT_TEST_TOKEN: &str = "rust-e2e-token"; @@ -510,12 +511,8 @@ impl E2eContext { .expect("start E2E client") } - /// Start a client that hosts the runtime in-process over FFI - /// ([`Transport::InProcess`]). Unlike the stdio harness, the CLI - /// entrypoint is passed as the program directly (the FFI host builds the - /// `node --embedded-host` argv itself and loads the sibling - /// runtime cdylib), so a `.js` entrypoint is not split into node + - /// prefix_args here. + /// Start a client that hosts the bundled runtime directly in-process over + /// FFI ([`Transport::InProcess`]). #[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); @@ -677,7 +674,7 @@ 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())?; + clear_directory_contents(self.context.work_dir()).await?; self.context.configure(category, snapshot_name)?; self.context.set_default_copilot_user(); Ok(()) @@ -685,7 +682,7 @@ impl SharedE2eState { async fn cleanup_after_test(&mut self) -> std::io::Result<()> { self.cleanup_sessions().await?; - clear_directory_contents(self.context.work_dir()) + clear_directory_contents(self.context.work_dir()).await } async fn cleanup_sessions(&self) -> std::io::Result<()> { @@ -785,19 +782,36 @@ fn is_filtered_test_run() -> bool { }) } -fn clear_directory_contents(directory: &Path) -> std::io::Result<()> { +async 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)?; + let is_directory = entry.file_type()?.is_dir(); + + for attempt in 1..=20 { + let result = if is_directory { + std::fs::remove_dir_all(&path) + } else { + std::fs::remove_file(&path) + }; + + match result { + Ok(()) => break, + Err(error) if error.kind() == std::io::ErrorKind::NotFound => break, + Err(error) if is_transient_windows_file_lock(&error) && attempt < 20 => { + tokio::time::sleep(Duration::from_millis(50)).await; + } + Err(error) => return Err(error), + } } } Ok(()) } +fn is_transient_windows_file_lock(error: &std::io::Error) -> bool { + cfg!(windows) && matches!(error.raw_os_error(), Some(5 | 32 | 145)) +} + impl Drop for E2eContext { fn drop(&mut self) { if let Some(mut proxy) = self.proxy.take() { @@ -1070,7 +1084,8 @@ impl InProcessEnvGuard { pairs.push(("COPILOT_SDK_AUTH_TOKEN".into(), "".into())); pairs.push(( "COPILOT_CLI_PATH".into(), - ctx.cli_path.clone().into_os_string(), + std::env::var_os("COPILOT_CLI_PATH") + .unwrap_or_else(|| ctx.cli_path.clone().into_os_string()), )); // Some tests opt into gated runtime APIs via per-client `options.env`, which the // in-process transport does not pass to the shared native runtime (see issue #1934). @@ -1177,29 +1192,23 @@ fn cli_path(repo_root: &Path) -> std::io::Result { } } - // The `@github/copilot` package is a thin loader; the runnable `index.js` - // ships in a platform-specific `@github/copilot--` package, - // exactly one of which is installed. Resolve whichever one is present. - let github_dir = repo_root - .join("nodejs") - .join("node_modules") - .join("@github"); - if let Ok(entries) = std::fs::read_dir(&github_dir) { - for entry in entries.flatten() { - if entry.file_name().to_string_lossy().starts_with("copilot-") { - let candidate = entry.path().join("index.js"); - if candidate.exists() { - return Ok(candidate); - } - } + let npm = if cfg!(windows) { "npm.cmd" } else { "npm" }; + let output = std::process::Command::new(npm) + .args(["run", "--silent", "prepare:runtime", "--", "--print-path"]) + .current_dir(repo_root.join("nodejs")) + .output()?; + if output.status.success() { + let path = PathBuf::from(String::from_utf8_lossy(&output.stdout).trim()); + if path.is_file() { + return Ok(path); } } Err(std::io::Error::new( std::io::ErrorKind::NotFound, format!( - "CLI not found under {}; run npm install in nodejs first", - github_dir.display() + "failed to prepare the pinned Copilot CLI: {}", + String::from_utf8_lossy(&output.stderr).trim() ), )) } @@ -1277,7 +1286,7 @@ impl CapiProxy { } }); let re = regex::Regex::new(r"Listening: (http://[^\s]+)\s+(\{.*\})$").unwrap(); - let deadline = Instant::now() + SHARED_E2E_CLEANUP_TIMEOUT; + let deadline = Instant::now() + PROXY_STARTUP_TIMEOUT; while let Some(remaining) = deadline.checked_duration_since(Instant::now()) { let line = match line_rx.recv_timeout(remaining) { Ok(Ok(line)) => line, @@ -1344,7 +1353,7 @@ impl CapiProxy { kill_and_wait_child(&mut child); Err(std::io::Error::other(format!( - "timed out after {SHARED_E2E_CLEANUP_TIMEOUT:?} waiting for proxy startup" + "timed out after {PROXY_STARTUP_TIMEOUT:?} waiting for proxy startup" ))) } @@ -1385,8 +1394,17 @@ impl CapiProxy { "/stop" }; let result = self.post_json(path, ""); - if let Some(mut child) = self.child.take() { - wait_for_child_exit(&mut child)?; + if let Some(mut child) = self.child.take() + && let Err(error) = wait_for_child_exit(&mut child) + { + if result.is_err() { + return Err(error); + } + // The proxy acknowledges /stop before its asynchronous server shutdown. + // npm/tsx can occasionally leave its wrapper alive after the proxy has + // accepted the request; wait_for_child_exit has reaped it, so do not turn + // an otherwise successful test into a teardown failure. + eprintln!("force-killed E2E proxy after successful stop request: {error}"); } result } diff --git a/rust/tests/e2e/telemetry.rs b/rust/tests/e2e/telemetry.rs index f6905427ae..386cc2f560 100644 --- a/rust/tests/e2e/telemetry.rs +++ b/rust/tests/e2e/telemetry.rs @@ -83,11 +83,6 @@ async fn should_export_file_telemetry_for_sdk_interactions() { == Some(source_name) })); - let trace_ids: std::collections::HashSet<_> = spans - .iter() - .filter_map(|span| string_property(span, "traceId")) - .collect(); - assert_eq!(trace_ids.len(), 1); assert!(spans.iter().all(|span| status_code(span) != Some(2))); let invoke_agent = find_span(&spans, "invoke_agent"); @@ -97,6 +92,8 @@ async fn should_export_file_telemetry_for_sdk_interactions() { ); let invoke_agent_span_id = string_property(invoke_agent, "spanId").expect("invoke_agent span id"); + let invoke_agent_trace_id = + string_property(invoke_agent, "traceId").expect("invoke_agent trace id"); assert!(is_root_span(invoke_agent)); let chat_spans: Vec<_> = spans @@ -109,6 +106,7 @@ async fn should_export_file_telemetry_for_sdk_interactions() { assert!(!chat_spans.is_empty()); assert!(chat_spans.iter().all(|span| { string_property(span, "parentSpanId") == Some(invoke_agent_span_id) + && string_property(span, "traceId") == Some(invoke_agent_trace_id) })); assert!(chat_spans.iter().any(|span| string_attribute( span, @@ -126,6 +124,10 @@ async fn should_export_file_telemetry_for_sdk_interactions() { string_property(tool_span, "parentSpanId"), Some(invoke_agent_span_id) ); + assert_eq!( + string_property(tool_span, "traceId"), + Some(invoke_agent_trace_id) + ); assert_eq!( string_attribute(tool_span, "gen_ai.tool.name").as_deref(), Some(tool_name) diff --git a/rust/tests/extension_launch_provider_runtime_test.rs b/rust/tests/extension_launch_provider_runtime_test.rs new file mode 100644 index 0000000000..f08d6af7f0 --- /dev/null +++ b/rust/tests/extension_launch_provider_runtime_test.rs @@ -0,0 +1,321 @@ +//! Real-boundary coverage for hostless extension discovery and lifecycle. +//! +//! Router tests can prove provider dispatch without exercising runtime session +//! setup, while an empty extension-list smoke test never requires the runtime +//! to install extension services. This test requires both boundaries and is +//! ignored until a real wrapper/runtime.node pair is supplied explicitly. + +#![cfg(unix)] +#![allow(clippy::unwrap_used)] + +use std::collections::HashMap; +use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; +use std::sync::Arc; +use std::time::{Duration, Instant}; + +use async_trait::async_trait; +use github_copilot_sdk::extension_launch_provider::{ + ExtensionLaunchProfile, ExtensionLaunchProvider, ExtensionLaunchProviderResolveRequest, + ExtensionLaunchProviderResolveResult, +}; +use github_copilot_sdk::handler::ApproveAllHandler; +use github_copilot_sdk::rpc::{ + ExtensionSource, ExtensionStatus, ExtensionsDisableRequest, ExtensionsEnableRequest, + ToolResult, ToolResultType, ToolsExecuteRequest, +}; +use github_copilot_sdk::{CliProgram, Client, ClientOptions, SessionConfig, Transport}; +use serde_json::{Value, json}; +use tokio::sync::mpsc; +use tokio::time::{sleep, timeout}; + +const TEST_TIMEOUT: Duration = Duration::from_secs(15); +const RUNTIME_PATH_ENV: &str = "COPILOT_RUNTIME_E2E_PATH"; + +struct RecordingProvider { + executable: PathBuf, + state_path: PathBuf, + requests: mpsc::UnboundedSender, +} + +#[async_trait] +impl ExtensionLaunchProvider for RecordingProvider { + async fn resolve( + &self, + request: ExtensionLaunchProviderResolveRequest, + ) -> github_copilot_sdk::Result { + self.requests.send(request.clone()).unwrap(); + Ok(ExtensionLaunchProviderResolveResult { + launch: Some(ExtensionLaunchProfile { + executable: self.executable.to_string_lossy().into_owned(), + args: Vec::new(), + env: HashMap::from([ + ("EXTENSION_PATH".to_string(), request.module_path.clone()), + ( + "FIXTURE_STATE_PATH".to_string(), + self.state_path.to_string_lossy().into_owned(), + ), + ("COPILOT_AUTO_UPDATE".to_string(), "false".to_string()), + ( + "COPILOT_SDK_PATH".to_string(), + "provider-must-not-win".to_string(), + ), + ( + "SESSION_ID".to_string(), + "provider-must-not-win".to_string(), + ), + ( + "COPILOT_EXTENSION_PARENT_PID".to_string(), + "provider-must-not-win".to_string(), + ), + ]), + }), + }) + } +} + +#[tokio::test(flavor = "multi_thread")] +#[ignore = "requires COPILOT_RUNTIME_E2E_PATH to name a real wrapper/runtime.node pair"] +async fn real_wrapper_installs_and_runs_hostless_extensions() { + let runtime_path = std::env::var_os(RUNTIME_PATH_ENV) + .map(PathBuf::from) + .expect("COPILOT_RUNTIME_E2E_PATH must name a copilot-runtime executable"); + assert_runtime_pair(&runtime_path); + + let home = tempfile::tempdir().unwrap(); + let workspace = tempfile::tempdir().unwrap(); + let extension_dir = workspace + .path() + .join(".github") + .join("extensions") + .join("lifecycle"); + std::fs::create_dir_all(&extension_dir).unwrap(); + let module_path = extension_dir.join("extension.mjs"); + std::fs::write(&module_path, "export default {};\n").unwrap(); + let module_path = std::fs::canonicalize(module_path).unwrap(); + let sdk_path = home.path().join("extension-sdk"); + std::fs::create_dir_all(&sdk_path).unwrap(); + let sdk_path = std::fs::canonicalize(sdk_path).unwrap(); + let state_path = home.path().join("extension-state.jsonl"); + let (request_tx, mut request_rx) = mpsc::unbounded_channel(); + + let options = ClientOptions::new() + .with_program(CliProgram::Path(runtime_path)) + .with_transport(Transport::Stdio) + .with_cwd(workspace.path()) + .with_base_directory(home.path()) + .with_use_logged_in_user(false) + .with_env_remove(["COPILOT_CLI_DIST_DIR"]) + .with_extension_launch_provider(RecordingProvider { + executable: PathBuf::from(env!("CARGO_BIN_EXE_copilot-extension-test-fixture")), + state_path: state_path.clone(), + requests: request_tx, + }); + let client = Client::start(options).await.unwrap(); + let session = client + .create_session( + SessionConfig::default() + .with_request_extensions(true) + .with_extension_sdk_path(sdk_path.to_string_lossy()) + .with_permission_handler(Arc::new(ApproveAllHandler)), + ) + .await + .unwrap(); + + let first_request = recv_request(&mut request_rx).await; + assert_request(&first_request, &module_path); + let first_start = wait_for_starts(&state_path, 1).await.remove(0); + assert_runtime_owned_environment(&first_start, session.id(), &sdk_path, &module_path); + let first_pid = json_pid(&first_start); + let wrapper_pid = json_parent_pid(&first_start); + assert_eq!(direct_child_pids(wrapper_pid), vec![first_pid]); + + let listed = session.rpc().extensions().list().await.unwrap(); + assert_eq!(listed.extensions.len(), 1); + assert_eq!(listed.extensions[0].id, "project:lifecycle"); + assert_eq!(listed.extensions[0].status, ExtensionStatus::Running); + + let result = session + .rpc() + .tools() + .execute(ToolsExecuteRequest { + arguments: json!({ "text": "sdk-boundary" }), + name: "fixture_echo".to_string(), + tool_call_id: Some("fixture-call".to_string()), + }) + .await + .unwrap(); + match result { + ToolResult::String(value) => assert_eq!(value, "echoed"), + ToolResult::ToolResultExpanded(result) => { + assert_eq!(result.text_result_for_llm, "echoed"); + assert_eq!(result.result_type, ToolResultType::Success); + } + } + wait_for_invocations(&state_path, 1).await; + + session + .rpc() + .extensions() + .disable(ExtensionsDisableRequest { + id: "project:lifecycle".to_string(), + }) + .await + .unwrap(); + wait_for_process_exit(first_pid).await; + let disabled = session.rpc().extensions().list().await.unwrap(); + assert_eq!(disabled.extensions[0].status, ExtensionStatus::Disabled); + + session + .rpc() + .extensions() + .enable(ExtensionsEnableRequest { + id: "project:lifecycle".to_string(), + }) + .await + .unwrap(); + let second_request = recv_request(&mut request_rx).await; + assert_request(&second_request, &module_path); + let starts = wait_for_starts(&state_path, 2).await; + let second_pid = json_pid(&starts[1]); + assert_ne!(second_pid, first_pid); + assert!(process_exists(second_pid)); + assert_eq!(json_parent_pid(&starts[1]), wrapper_pid); + assert_eq!(direct_child_pids(wrapper_pid), vec![second_pid]); + let enabled = session.rpc().extensions().list().await.unwrap(); + assert_eq!(enabled.extensions[0].status, ExtensionStatus::Running); + + client.stop().await.unwrap(); + wait_for_process_exit(second_pid).await; + wait_for_process_exit(wrapper_pid).await; + assert!(request_rx.try_recv().is_err()); +} + +fn assert_runtime_pair(runtime_path: &Path) { + assert!(runtime_path.is_file(), "missing {}", runtime_path.display()); + let runtime_node = runtime_path.parent().unwrap().join("runtime.node"); + assert!(runtime_node.is_file(), "missing {}", runtime_node.display()); +} + +async fn recv_request( + requests: &mut mpsc::UnboundedReceiver, +) -> ExtensionLaunchProviderResolveRequest { + timeout(TEST_TIMEOUT, requests.recv()) + .await + .expect( + "runtime never invoked extensionLaunchProvider.resolve; hostless extension services \ + may not be installed", + ) + .expect("provider request channel closed") +} + +fn assert_request(request: &ExtensionLaunchProviderResolveRequest, module_path: &Path) { + assert_eq!(request.id, "project:lifecycle"); + assert_eq!(request.name, "lifecycle"); + assert_eq!(request.source, ExtensionSource::Project); + assert_eq!(Path::new(&request.module_path), module_path); +} + +fn read_state(path: &Path) -> Vec { + let Ok(contents) = std::fs::read_to_string(path) else { + return Vec::new(); + }; + contents + .lines() + .map(|line| serde_json::from_str(line).unwrap()) + .collect() +} + +async fn wait_for_starts(path: &Path, count: usize) -> Vec { + wait_for_state(path, "start", count).await +} + +async fn wait_for_invocations(path: &Path, count: usize) -> Vec { + wait_for_state(path, "invoke", count).await +} + +async fn wait_for_state(path: &Path, kind: &str, count: usize) -> Vec { + let deadline = Instant::now() + TEST_TIMEOUT; + loop { + let matching: Vec<_> = read_state(path) + .into_iter() + .filter(|entry| entry["kind"] == kind) + .collect(); + if matching.len() >= count { + return matching; + } + assert!( + Instant::now() < deadline, + "timed out waiting for {count} {kind} entries; state: {:?}", + read_state(path) + ); + sleep(Duration::from_millis(25)).await; + } +} + +fn assert_runtime_owned_environment( + start: &Value, + session_id: &str, + sdk_path: &Path, + module_path: &Path, +) { + assert_eq!(start["sessionId"], session_id); + assert_eq!( + start["sdkPath"].as_str(), + Some(sdk_path.to_string_lossy().as_ref()) + ); + assert_eq!( + start["extensionPath"].as_str(), + Some(module_path.to_string_lossy().as_ref()) + ); + assert_eq!(start["autoUpdate"], "false"); + assert!(start["cliDistDir"].is_null()); + let parent_pid = start["parentPid"].as_str().unwrap(); + assert_ne!(parent_pid, "provider-must-not-win"); + assert!(parent_pid.parse::().is_ok()); +} + +fn json_pid(value: &Value) -> u32 { + value["pid"].as_u64().unwrap() as u32 +} + +fn json_parent_pid(value: &Value) -> u32 { + value["parentPid"].as_str().unwrap().parse().unwrap() +} + +fn direct_child_pids(parent_pid: u32) -> Vec { + let output = Command::new("ps") + .args(["-axo", "pid=,ppid="]) + .output() + .expect("list processes"); + assert!(output.status.success(), "ps failed: {output:?}"); + let mut children: Vec<_> = String::from_utf8(output.stdout) + .unwrap() + .lines() + .filter_map(|line| { + let mut columns = line.split_whitespace(); + let pid = columns.next()?.parse::().ok()?; + let ppid = columns.next()?.parse::().ok()?; + (ppid == parent_pid).then_some(pid) + }) + .collect(); + children.sort_unstable(); + children +} + +fn process_exists(pid: u32) -> bool { + Command::new("kill") + .args(["-0", &pid.to_string()]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .is_ok_and(|status| status.success()) +} + +async fn wait_for_process_exit(pid: u32) { + let deadline = Instant::now() + TEST_TIMEOUT; + while process_exists(pid) { + assert!(Instant::now() < deadline, "process {pid} did not exit"); + sleep(Duration::from_millis(25)).await; + } +} diff --git a/rust/tests/extension_launch_provider_test.rs b/rust/tests/extension_launch_provider_test.rs new file mode 100644 index 0000000000..b1a9592133 --- /dev/null +++ b/rust/tests/extension_launch_provider_test.rs @@ -0,0 +1,493 @@ +#![allow(clippy::unwrap_used)] + +use std::collections::HashMap; +use std::sync::Arc; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::time::Duration; + +use async_trait::async_trait; +use github_copilot_sdk::extension_launch_provider::{ + ExtensionLaunchProfile, ExtensionLaunchProvider, ExtensionLaunchProviderResolveRequest, + ExtensionLaunchProviderResolveResult, +}; +use github_copilot_sdk::rpc::ExtensionSource; +use github_copilot_sdk::{CliProgram, Client, ClientOptions, Error, ErrorKind, Transport}; +use serde_json::{Value, json}; +use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, duplex}; +use tokio::net::{TcpListener, TcpStream}; +use tokio::sync::{Notify, mpsc}; +use tokio::time::timeout; + +const TEST_TIMEOUT: Duration = Duration::from_secs(2); + +async fn write_framed(writer: &mut (impl AsyncWrite + Unpin), value: &Value) { + let body = serde_json::to_vec(value).unwrap(); + let header = format!("Content-Length: {}\r\n\r\n", body.len()); + writer.write_all(header.as_bytes()).await.unwrap(); + writer.write_all(&body).await.unwrap(); + writer.flush().await.unwrap(); +} + +async fn read_framed(reader: &mut (impl AsyncRead + Unpin)) -> Option { + let mut header = String::new(); + loop { + let mut byte = [0u8; 1]; + match reader.read_exact(&mut byte).await { + Ok(_) => {} + Err(error) if error.kind() == std::io::ErrorKind::UnexpectedEof => return None, + Err(error) => panic!("failed to read frame header: {error}"), + } + 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(); + Some(serde_json::from_slice(&body).unwrap()) +} + +fn resolve_params() -> Value { + json!({ + "id": "project:legacy-extension", + "modulePath": "/extensions/legacy/index.js", + "name": "Legacy extension", + "source": "project" + }) +} + +fn app_launch_result(executable: &str) -> ExtensionLaunchProviderResolveResult { + ExtensionLaunchProviderResolveResult { + launch: Some(ExtensionLaunchProfile { + executable: executable.to_string(), + args: vec!["/app/preloads/extension_bootstrap.mjs".to_string()], + env: HashMap::from([ + ("COPILOT_AUTO_UPDATE".to_string(), "false".to_string()), + ( + "EXTENSION_PATH".to_string(), + "/extensions/legacy/index.js".to_string(), + ), + ]), + }), + } +} + +struct AppProvider { + executable: String, + observed: Option>, + release: Option>, + calls: Arc, +} + +#[async_trait] +impl ExtensionLaunchProvider for AppProvider { + async fn resolve( + &self, + request: ExtensionLaunchProviderResolveRequest, + ) -> github_copilot_sdk::Result { + self.calls.fetch_add(1, Ordering::SeqCst); + if let Some(observed) = &self.observed { + observed.send(request).unwrap(); + } + if let Some(release) = &self.release { + release.notified().await; + } + Ok(app_launch_result(&self.executable)) + } +} + +struct FailingProvider; + +#[async_trait] +impl ExtensionLaunchProvider for FailingProvider { + async fn resolve( + &self, + _request: ExtensionLaunchProviderResolveRequest, + ) -> github_copilot_sdk::Result { + Err(Error::with_message( + ErrorKind::InvalidConfig, + "extension profile lookup failed", + )) + } +} + +#[test] +fn launch_profile_request_and_result_serialize_exactly() { + let request: ExtensionLaunchProviderResolveRequest = + serde_json::from_value(resolve_params()).unwrap(); + assert_eq!(request.id, "project:legacy-extension"); + assert_eq!(request.module_path, "/extensions/legacy/index.js"); + assert_eq!(request.name, "Legacy extension"); + assert_eq!(request.source, ExtensionSource::Project); + assert_eq!(serde_json::to_value(request).unwrap(), resolve_params()); + + let result = app_launch_result("/app/copilot"); + assert_eq!( + serde_json::to_value(&result).unwrap(), + json!({ + "launch": { + "executable": "/app/copilot", + "args": ["/app/preloads/extension_bootstrap.mjs"], + "env": { + "COPILOT_AUTO_UPDATE": "false", + "EXTENSION_PATH": "/extensions/legacy/index.js" + } + } + }) + ); + + let round_trip: ExtensionLaunchProviderResolveResult = + serde_json::from_value(serde_json::to_value(result).unwrap()).unwrap(); + let launch = round_trip.launch.unwrap(); + assert_eq!(launch.executable, "/app/copilot"); + assert_eq!(launch.args, vec!["/app/preloads/extension_bootstrap.mjs"]); + assert_eq!( + launch.env, + HashMap::from([ + ("COPILOT_AUTO_UPDATE".to_string(), "false".to_string()), + ( + "EXTENSION_PATH".to_string(), + "/extensions/legacy/index.js".to_string() + ) + ]) + ); +} + +#[tokio::test] +async fn configured_async_provider_dispatches_without_a_session() { + let (client_write, mut server_read) = duplex(8192); + let (mut server_write, client_read) = duplex(8192); + let temp = tempfile::tempdir().unwrap(); + let (observed_tx, mut observed_rx) = mpsc::unbounded_channel(); + let release = Arc::new(Notify::new()); + let calls = Arc::new(AtomicUsize::new(0)); + let client = Client::from_streams_with_extension_launch_provider( + client_read, + client_write, + temp.path().to_path_buf(), + Arc::new(AppProvider { + executable: "/app/copilot".to_string(), + observed: Some(observed_tx), + release: Some(release.clone()), + calls: calls.clone(), + }), + ) + .unwrap(); + client.start_router_for_test(); + + write_framed( + &mut server_write, + &json!({ + "jsonrpc": "2.0", + "id": 41, + "method": "extensionLaunchProvider.resolve", + "params": resolve_params() + }), + ) + .await; + + let observed = timeout(TEST_TIMEOUT, observed_rx.recv()) + .await + .unwrap() + .unwrap(); + assert_eq!(observed.id, "project:legacy-extension"); + assert!( + timeout(Duration::from_millis(25), read_framed(&mut server_read)) + .await + .is_err() + ); + + release.notify_one(); + let response = timeout(TEST_TIMEOUT, read_framed(&mut server_read)) + .await + .unwrap() + .unwrap(); + assert_eq!(response["id"], 41); + assert_eq!( + response["result"], + serde_json::to_value(app_launch_result("/app/copilot")).unwrap() + ); + assert_eq!(calls.load(Ordering::SeqCst), 1); +} + +#[tokio::test] +async fn missing_provider_returns_client_global_handler_error() { + let (client_write, mut server_read) = duplex(8192); + let (mut server_write, client_read) = duplex(8192); + let temp = tempfile::tempdir().unwrap(); + let client = + Client::from_streams(client_read, client_write, temp.path().to_path_buf()).unwrap(); + client.start_router_for_test(); + + write_framed( + &mut server_write, + &json!({ + "jsonrpc": "2.0", + "id": 42, + "method": "extensionLaunchProvider.resolve", + "params": resolve_params() + }), + ) + .await; + + let response = timeout(TEST_TIMEOUT, read_framed(&mut server_read)) + .await + .unwrap() + .unwrap(); + assert_eq!(response["id"], 42); + assert_eq!(response["error"]["code"], -32603); + assert_eq!( + response["error"]["message"], + "No extensionLaunchProvider client-global handler registered" + ); +} + +#[tokio::test] +async fn provider_error_is_returned_as_json_rpc_error() { + let (client_write, mut server_read) = duplex(8192); + let (mut server_write, client_read) = duplex(8192); + let temp = tempfile::tempdir().unwrap(); + let client = Client::from_streams_with_extension_launch_provider( + client_read, + client_write, + temp.path().to_path_buf(), + Arc::new(FailingProvider), + ) + .unwrap(); + client.start_router_for_test(); + + write_framed( + &mut server_write, + &json!({ + "jsonrpc": "2.0", + "id": 43, + "method": "extensionLaunchProvider.resolve", + "params": resolve_params() + }), + ) + .await; + + let response = timeout(TEST_TIMEOUT, read_framed(&mut server_read)) + .await + .unwrap() + .unwrap(); + assert_eq!(response["id"], 43); + assert_eq!(response["error"]["code"], -32603); + assert!( + response["error"]["message"] + .as_str() + .unwrap() + .contains("extension profile lookup failed") + ); +} + +async fn respond_to_connect( + reader: &mut (impl AsyncRead + Unpin), + writer: &mut (impl AsyncWrite + Unpin), +) { + let request = read_framed(reader).await.unwrap(); + assert_eq!(request["method"], "connect"); + write_framed( + writer, + &json!({ + "jsonrpc": "2.0", + "id": request["id"], + "result": { + "ok": true, + "protocolVersion": 3, + "version": "test" + } + }), + ) + .await; +} + +async fn read_registration(reader: &mut (impl AsyncRead + Unpin)) -> Value { + let request = read_framed(reader).await.unwrap(); + assert_eq!(request["method"], "registerExtensionLaunchProvider"); + assert_eq!(request["params"], json!({})); + request +} + +fn external_options(port: u16, provider: AppProvider) -> ClientOptions { + ClientOptions::new() + .with_program(CliProgram::Path("unused-for-external-transport".into())) + .with_transport(Transport::External { + host: "127.0.0.1".to_string(), + port, + connection_token: None, + }) + .with_extension_launch_provider(provider) +} + +#[tokio::test] +async fn registration_precedes_session_work_and_routes_during_start() { + let listener = TcpListener::bind(("127.0.0.1", 0)).await.unwrap(); + let port = listener.local_addr().unwrap().port(); + let calls = Arc::new(AtomicUsize::new(0)); + let server = tokio::spawn(async move { + let (stream, _) = listener.accept().await.unwrap(); + let (mut reader, mut writer) = stream.into_split(); + respond_to_connect(&mut reader, &mut writer).await; + let registration = read_registration(&mut reader).await; + + write_framed( + &mut writer, + &json!({ + "jsonrpc": "2.0", + "id": 501, + "method": "extensionLaunchProvider.resolve", + "params": resolve_params() + }), + ) + .await; + let callback_response = read_framed(&mut reader).await.unwrap(); + assert_eq!(callback_response["id"], 501); + assert_eq!( + callback_response["result"], + serde_json::to_value(app_launch_result("/app/copilot")).unwrap() + ); + + write_framed( + &mut writer, + &json!({ + "jsonrpc": "2.0", + "id": registration["id"], + "result": null + }), + ) + .await; + assert!( + timeout(Duration::from_millis(50), read_framed(&mut reader)) + .await + .is_err() + ); + }); + + let client = Client::start(external_options( + port, + AppProvider { + executable: "/app/copilot".to_string(), + observed: None, + release: None, + calls: calls.clone(), + }, + )) + .await + .unwrap(); + + timeout(TEST_TIMEOUT, server).await.unwrap().unwrap(); + assert_eq!(calls.load(Ordering::SeqCst), 1); + client.stop().await.unwrap(); +} + +async fn serve_registered_client(stream: TcpStream, request_id: u64, expected_executable: &str) { + let (mut reader, mut writer) = stream.into_split(); + respond_to_connect(&mut reader, &mut writer).await; + let registration = read_registration(&mut reader).await; + write_framed( + &mut writer, + &json!({ + "jsonrpc": "2.0", + "id": registration["id"], + "result": null + }), + ) + .await; + + write_framed( + &mut writer, + &json!({ + "jsonrpc": "2.0", + "id": request_id, + "method": "extensionLaunchProvider.resolve", + "params": resolve_params() + }), + ) + .await; + let response = read_framed(&mut reader).await.unwrap(); + assert_eq!(response["id"], request_id); + assert_eq!( + response["result"]["launch"]["executable"], + expected_executable + ); + assert_eq!(read_framed(&mut reader).await, None); +} + +#[tokio::test] +async fn shutdown_and_restart_do_not_reuse_or_duplicate_providers() { + let listener = TcpListener::bind(("127.0.0.1", 0)).await.unwrap(); + let port = listener.local_addr().unwrap().port(); + let first_calls = Arc::new(AtomicUsize::new(0)); + let second_calls = Arc::new(AtomicUsize::new(0)); + let (served_tx, mut served_rx) = mpsc::unbounded_channel(); + + let server = tokio::spawn(async move { + let (first, _) = listener.accept().await.unwrap(); + serve_registered_client(first, 601, "/app/first-copilot").await; + served_tx.send(()).unwrap(); + + let (second, _) = listener.accept().await.unwrap(); + serve_registered_client(second, 602, "/app/second-copilot").await; + served_tx.send(()).unwrap(); + }); + + let first = Client::start(external_options( + port, + AppProvider { + executable: "/app/first-copilot".to_string(), + observed: None, + release: None, + calls: first_calls.clone(), + }, + )) + .await + .unwrap(); + timeout(TEST_TIMEOUT, async { + while first_calls.load(Ordering::SeqCst) == 0 { + tokio::task::yield_now().await; + } + }) + .await + .unwrap(); + first.stop().await.unwrap(); + timeout(TEST_TIMEOUT, served_rx.recv()) + .await + .unwrap() + .unwrap(); + + let second = Client::start(external_options( + port, + AppProvider { + executable: "/app/second-copilot".to_string(), + observed: None, + release: None, + calls: second_calls.clone(), + }, + )) + .await + .unwrap(); + timeout(TEST_TIMEOUT, async { + while second_calls.load(Ordering::SeqCst) == 0 { + tokio::task::yield_now().await; + } + }) + .await + .unwrap(); + second.stop().await.unwrap(); + timeout(TEST_TIMEOUT, served_rx.recv()) + .await + .unwrap() + .unwrap(); + timeout(TEST_TIMEOUT, server).await.unwrap().unwrap(); + + assert_eq!(first_calls.load(Ordering::SeqCst), 1); + assert_eq!(second_calls.load(Ordering::SeqCst), 1); +} diff --git a/rust/tests/fixtures/extension_fixture.rs b/rust/tests/fixtures/extension_fixture.rs new file mode 100644 index 0000000000..787c82503a --- /dev/null +++ b/rust/tests/fixtures/extension_fixture.rs @@ -0,0 +1,127 @@ +use std::fs::OpenOptions; +use std::io::{BufRead, BufReader, Write}; + +use serde_json::{Value, json}; + +fn main() { + let session_id = std::env::var("SESSION_ID").expect("SESSION_ID"); + record_state(json!({ + "kind": "start", + "pid": std::process::id(), + "sessionId": session_id, + "sdkPath": std::env::var("COPILOT_SDK_PATH").ok(), + "parentPid": std::env::var("COPILOT_EXTENSION_PARENT_PID").ok(), + "extensionPath": std::env::var("EXTENSION_PATH").ok(), + "autoUpdate": std::env::var("COPILOT_AUTO_UPDATE").ok(), + "cliDistDir": std::env::var("COPILOT_CLI_DIST_DIR").ok(), + })); + + let stdin = std::io::stdin(); + let mut reader = BufReader::new(stdin.lock()); + let stdout = std::io::stdout(); + let mut writer = stdout.lock(); + write_message( + &mut writer, + &json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "session.resume", + "params": { + "sessionId": session_id, + "tools": [{ + "name": "fixture_echo", + "description": "Returns a deterministic fixture result", + "parameters": { + "type": "object", + "properties": { "text": { "type": "string" } }, + "required": ["text"] + } + }], + "requestPermission": false, + "enableConfigDiscovery": false, + "disableResume": true, + "streaming": true + } + }), + ); + + let mut next_id = 2_u64; + while let Some(message) = read_message(&mut reader) { + if message.get("id") == Some(&json!(1)) { + assert!( + message.get("error").is_none(), + "extension resume failed: {message}" + ); + continue; + } + if message.pointer("/params/event/type") != Some(&json!("external_tool.requested")) { + continue; + } + let Some(request_id) = message + .pointer("/params/event/data/requestId") + .and_then(Value::as_str) + else { + continue; + }; + record_state(json!({ + "kind": "invoke", + "pid": std::process::id(), + "requestId": request_id, + })); + write_message( + &mut writer, + &json!({ + "jsonrpc": "2.0", + "id": next_id, + "method": "session.tools.handlePendingToolCall", + "params": { + "sessionId": session_id, + "requestId": request_id, + "result": "echoed" + } + }), + ); + next_id += 1; + } +} + +fn record_state(value: Value) { + let path = std::env::var("FIXTURE_STATE_PATH").expect("FIXTURE_STATE_PATH"); + let mut file = OpenOptions::new() + .create(true) + .append(true) + .open(path) + .expect("open fixture state"); + writeln!(file, "{value}").expect("write fixture state"); +} + +fn write_message(writer: &mut impl Write, value: &Value) { + let body = serde_json::to_vec(value).expect("serialize fixture message"); + write!(writer, "Content-Length: {}\r\n\r\n", body.len()).expect("write fixture header"); + writer.write_all(&body).expect("write fixture body"); + writer.flush().expect("flush fixture message"); +} + +fn read_message(reader: &mut impl BufRead) -> Option { + let mut content_length = None; + loop { + let mut line = String::new(); + if reader.read_line(&mut line).expect("read fixture header") == 0 { + return None; + } + if line == "\r\n" { + break; + } + if let Some(value) = line.strip_prefix("Content-Length:") { + content_length = Some( + value + .trim() + .parse::() + .expect("parse fixture content length"), + ); + } + } + let mut body = vec![0; content_length.expect("fixture content length")]; + reader.read_exact(&mut body).expect("read fixture body"); + Some(serde_json::from_slice(&body).expect("parse fixture message")) +} diff --git a/rust/tests/fixtures/host_crash_fixture.rs b/rust/tests/fixtures/host_crash_fixture.rs new file mode 100644 index 0000000000..c688cf3e87 --- /dev/null +++ b/rust/tests/fixtures/host_crash_fixture.rs @@ -0,0 +1,60 @@ +//! Test-only binary that hosts a single [`github_copilot_sdk::Client`] and then +//! blocks forever, so an external test can terminate *this* process abruptly +//! (simulating an SDK-embedding app process crashing) without ever running any +//! of this process's own cleanup code (`Client::stop`, `force_stop`, or +//! `Drop`). +//! +//! Configuration is passed entirely through environment variables so the +//! caller doesn't need this crate's non-`pub` types: +//! - `HOST_CRASH_FIXTURE_PROGRAM`: CLI program path. +//! - `HOST_CRASH_FIXTURE_PREFIX_ARGS_JSON`: JSON array of prefix args. +//! - `HOST_CRASH_FIXTURE_CWD`: working directory for the spawned CLI. +//! - `HOST_CRASH_FIXTURE_ENV_JSON`: JSON array of `[key, value]` pairs to set +//! on the spawned CLI's environment. +//! - `HOST_CRASH_FIXTURE_PID_FILE`: path this process writes the CLI child's +//! OS process id to, once the client finishes starting. + +use std::path::PathBuf; + +use github_copilot_sdk::{CliProgram, Client, ClientOptions, Transport}; + +#[tokio::main(flavor = "current_thread")] +async fn main() { + let program = std::env::var("HOST_CRASH_FIXTURE_PROGRAM").expect("HOST_CRASH_FIXTURE_PROGRAM"); + let prefix_args: Vec = serde_json::from_str( + &std::env::var("HOST_CRASH_FIXTURE_PREFIX_ARGS_JSON") + .expect("HOST_CRASH_FIXTURE_PREFIX_ARGS_JSON"), + ) + .expect("parse HOST_CRASH_FIXTURE_PREFIX_ARGS_JSON"); + let cwd = std::env::var("HOST_CRASH_FIXTURE_CWD").expect("HOST_CRASH_FIXTURE_CWD"); + let env_pairs: Vec<(String, String)> = serde_json::from_str( + &std::env::var("HOST_CRASH_FIXTURE_ENV_JSON").expect("HOST_CRASH_FIXTURE_ENV_JSON"), + ) + .expect("parse HOST_CRASH_FIXTURE_ENV_JSON"); + let pid_file = PathBuf::from( + std::env::var("HOST_CRASH_FIXTURE_PID_FILE").expect("HOST_CRASH_FIXTURE_PID_FILE"), + ); + + let options = ClientOptions::new() + .with_program(CliProgram::Path(PathBuf::from(program))) + .with_prefix_args(prefix_args) + .with_cwd(PathBuf::from(cwd)) + .with_env(env_pairs) + .with_use_logged_in_user(false) + .with_transport(Transport::Stdio); + + let client = Client::start(options).await.expect("start CLI client"); + let pid = client.pid().expect("client reports spawned CLI pid"); + std::fs::write(&pid_file, pid.to_string()).expect("write pid file"); + + // Deliberately leak the client so nothing in this process — including its + // `Drop` impls — ever runs cleanup code. The external test process + // terminates this process abruptly (e.g. `TerminateProcess` on Windows) + // to simulate an SDK-embedding host crashing, and asserts that the CLI + // still dies via the OS containment primitive alone. + std::mem::forget(client); + + loop { + std::thread::sleep(std::time::Duration::from_secs(3600)); + } +} diff --git a/rust/tests/jsonrpc_test.rs b/rust/tests/jsonrpc_test.rs index 7f7d432130..1735067c3e 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/protocol_version_test.rs b/rust/tests/protocol_version_test.rs index 9d613d8d76..cd8563f87b 100644 --- a/rust/tests/protocol_version_test.rs +++ b/rust/tests/protocol_version_test.rs @@ -127,6 +127,7 @@ async fn connect_handshake_supplies_protocol_version() { assert_eq!(req["method"], "connect"); // Token is None for the from_streams entry point (no transport spawn). assert!(req["params"].get("token").is_none()); + assert!(req["params"].get("clientInfo").is_none()); let response = serde_json::json!({ "jsonrpc": "2.0", "id": req["id"], @@ -238,3 +239,153 @@ async fn connect_handshake_forwards_auto_generated_token() { .unwrap() .unwrap(); } + +/// Positive coverage for application-identity forwarding on the `connect` +/// handshake. A client constructed with a [`ClientInfo`] MUST serialize it +/// (camelCase) into the outbound `connect` request's `clientInfo` param so +/// the runtime attributes this connection's telemetry to the application. +#[tokio::test] +async fn connect_handshake_forwards_client_info() { + let (client_write, server_read) = duplex(8192); + let (server_write, client_read) = duplex(8192); + let client = Client::from_streams_with_client_info( + client_read, + client_write, + std::env::temp_dir(), + Some( + github_copilot_sdk::ClientInfo::new() + .with_application_name("acme-developer-portal") + .with_application_version("2.4.0") + .with_integration_name("copilot-assistant") + .with_integration_version("1.5.0"), + ), + ) + .unwrap(); + + let mut server_read = server_read; + let mut server_write = server_write; + + let verify_handle = tokio::spawn({ + let client = client.clone(); + async move { client.verify_protocol_version().await } + }); + + let req = read_framed(&mut server_read).await; + assert_eq!(req["method"], "connect"); + let client_info = &req["params"]["clientInfo"]; + assert_eq!(client_info["editorName"], "acme-developer-portal"); + assert_eq!(client_info["editorVersion"], "2.4.0"); + assert_eq!(client_info["extensionName"], "copilot-assistant"); + assert_eq!(client_info["extensionVersion"], "1.5.0"); + + let response = serde_json::json!({ + "jsonrpc": "2.0", + "id": req["id"], + "result": { "ok": true, "protocolVersion": 3, "version": "test-1.0.0" }, + }); + write_framed(&mut server_write, &serde_json::to_vec(&response).unwrap()).await; + + tokio::time::timeout(std::time::Duration::from_secs(2), verify_handle) + .await + .unwrap() + .unwrap() + .unwrap(); +} + +/// A [`ClientInfo`] with only some fields set must omit the empty ones from +/// the wire, and a fully-empty one must drop `clientInfo` entirely so the +/// runtime keeps its default attribution. +#[tokio::test] +async fn connect_handshake_omits_empty_client_info_fields() { + let (client_write, server_read) = duplex(8192); + let (server_write, client_read) = duplex(8192); + let client = Client::from_streams_with_client_info( + client_read, + client_write, + std::env::temp_dir(), + Some( + github_copilot_sdk::ClientInfo::new() + .with_application_name("example-app") + .with_application_version(""), + ), + ) + .unwrap(); + + let mut server_read = server_read; + let mut server_write = server_write; + + let verify_handle = tokio::spawn({ + let client = client.clone(); + async move { client.verify_protocol_version().await } + }); + + let req = read_framed(&mut server_read).await; + assert_eq!(req["method"], "connect"); + let client_info = &req["params"]["clientInfo"]; + assert_eq!(client_info["editorName"], "example-app"); + assert!(client_info.get("editorVersion").is_none()); + assert!(client_info.get("extensionName").is_none()); + assert!(client_info.get("extensionVersion").is_none()); + + let response = serde_json::json!({ + "jsonrpc": "2.0", + "id": req["id"], + "result": { "ok": true, "protocolVersion": 3, "version": "test-1.0.0" }, + }); + write_framed(&mut server_write, &serde_json::to_vec(&response).unwrap()).await; + + tokio::time::timeout(std::time::Duration::from_secs(2), verify_handle) + .await + .unwrap() + .unwrap() + .unwrap(); +} + +/// A [`ClientInfo`] whose every field is empty must drop `clientInfo` from the +/// handshake entirely so the runtime keeps its default attribution. +#[tokio::test] +async fn connect_handshake_omits_all_empty_client_info() { + let (client_write, server_read) = duplex(8192); + let (server_write, client_read) = duplex(8192); + let client = Client::from_streams_with_client_info( + client_read, + client_write, + std::env::temp_dir(), + Some( + github_copilot_sdk::ClientInfo::new() + .with_application_name("") + .with_application_version("") + .with_integration_name("") + .with_integration_version(""), + ), + ) + .unwrap(); + + let mut server_read = server_read; + let mut server_write = server_write; + + let verify_handle = tokio::spawn({ + let client = client.clone(); + async move { client.verify_protocol_version().await } + }); + + let req = read_framed(&mut server_read).await; + assert_eq!(req["method"], "connect"); + assert!( + req["params"].get("clientInfo").is_none(), + "an all-empty clientInfo must be omitted from the handshake" + ); + + let response = serde_json::json!({ + "jsonrpc": "2.0", + "id": req["id"], + "result": { "ok": true, "protocolVersion": 3, "version": "test-1.0.0" }, + }); + write_framed(&mut server_write, &serde_json::to_vec(&response).unwrap()).await; + + tokio::time::timeout(std::time::Duration::from_secs(2), verify_handle) + .await + .unwrap() + .unwrap() + .unwrap(); +} diff --git a/rust/tests/session_test.rs b/rust/tests/session_test.rs index 41cae3e950..778e13e71f 100644 --- a/rust/tests/session_test.rs +++ b/rust/tests/session_test.rs @@ -1,41 +1,229 @@ #![allow(clippy::unwrap_used)] +use std::collections::HashMap; +use std::fmt; use std::path::Path; use std::sync::Arc; -use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering}; use std::time::Duration; use async_trait::async_trait; use github_copilot_sdk::canvas::{CanvasDeclaration, CanvasHandler, CanvasResult}; +use github_copilot_sdk::github_token::{ + GitHubToken, GitHubTokenProviderArgs, GitHubTokenProviderResult, GitHubTokenRequestReason, +}; 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, + AskUserVariant, CanvasProviderIdentity, CloudSessionOptions, CloudSessionRepository, + CommandContext, CommandDefinition, CommandHandler, DeliveryMode, DisableBypassPermissionsModes, + 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; use tokio::io::{AsyncWrite, AsyncWriteExt, duplex}; +use tokio::sync::Notify; use tokio::time::timeout; +use tracing::field::{Field, Visit}; +use tracing::span::{Attributes, Id, Record}; +use tracing::{Event, Metadata, Subscriber}; const TIMEOUT: Duration = Duration::from_secs(2); +const PERMISSION_CONFIRMATION_METHOD: &str = "session.permissions.handlePendingPermissionRequest"; + +#[derive(Clone, Debug)] +struct CapturedTraceEvent { + fields: HashMap, +} + +impl CapturedTraceEvent { + fn message_contains(&self, expected: &str) -> bool { + self.fields + .get("message") + .is_some_and(|message| message.contains(expected)) + } + + fn field_is(&self, name: &str, expected: &str) -> bool { + self.fields.get(name).is_some_and(|value| value == expected) + } +} + +#[derive(Clone, Default)] +struct TraceCapture { + events: Arc>>, +} + +impl TraceCapture { + fn permission_outcome(&self, request_id: &str) -> Option { + self.events + .lock() + .unwrap() + .iter() + .find(|event| { + event.field_is("request_id", request_id) + && (event.message_contains( + "Session::handle_notification response sent successfully", + ) || event.message_contains( + "failed to deliver permission decision back to the runtime", + ) || event + .message_contains("permission confirmation acknowledgement wait cancelled")) + }) + .cloned() + } + + async fn wait_for_permission_outcome(&self, request_id: &str) -> CapturedTraceEvent { + timeout(TIMEOUT, async { + loop { + if let Some(event) = self.permission_outcome(request_id) { + return event; + } + tokio::task::yield_now().await; + } + }) + .await + .expect("timed out waiting for permission confirmation diagnostic") + } +} + +#[derive(Default)] +struct TraceFieldVisitor { + fields: HashMap, +} + +impl Visit for TraceFieldVisitor { + fn record_bool(&mut self, field: &Field, value: bool) { + self.fields + .insert(field.name().to_string(), value.to_string()); + } + + fn record_i64(&mut self, field: &Field, value: i64) { + self.fields + .insert(field.name().to_string(), value.to_string()); + } + + fn record_u64(&mut self, field: &Field, value: u64) { + self.fields + .insert(field.name().to_string(), value.to_string()); + } + + fn record_str(&mut self, field: &Field, value: &str) { + self.fields + .insert(field.name().to_string(), value.to_string()); + } + + fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) { + self.fields + .insert(field.name().to_string(), format!("{value:?}")); + } +} + +struct CaptureSubscriber { + capture: TraceCapture, + next_span_id: AtomicU64, +} + +impl CaptureSubscriber { + fn new(capture: TraceCapture) -> Self { + Self { + capture, + next_span_id: AtomicU64::new(1), + } + } +} + +impl Subscriber for CaptureSubscriber { + fn enabled(&self, _metadata: &Metadata<'_>) -> bool { + true + } + + fn new_span(&self, _span: &Attributes<'_>) -> Id { + Id::from_u64(self.next_span_id.fetch_add(1, Ordering::Relaxed)) + } + + fn record(&self, _span: &Id, _values: &Record<'_>) {} + + fn record_follows_from(&self, _span: &Id, _follows: &Id) {} + + fn event(&self, event: &Event<'_>) { + let mut visitor = TraceFieldVisitor::default(); + event.record(&mut visitor); + self.capture + .events + .lock() + .unwrap() + .push(CapturedTraceEvent { + fields: visitor.fields, + }); + } + + fn enter(&self, _span: &Id) {} + + fn exit(&self, _span: &Id) {} +} + +fn capture_traces() -> (TraceCapture, tracing::dispatcher::DefaultGuard) { + let capture = TraceCapture::default(); + let dispatch = tracing::Dispatch::new(CaptureSubscriber::new(capture.clone())); + let guard = tracing::dispatcher::set_default(&dispatch); + (capture, guard) +} struct TestCanvasHandler; struct CancelMcpAuthHandler; +struct ContextualApproveHandler; + +struct GatedApproveHandler { + entered: Arc, + release: Arc, +} + +#[async_trait] +impl PermissionHandler for GatedApproveHandler { + async fn handle( + &self, + _session_id: SessionId, + _request_id: RequestId, + _data: github_copilot_sdk::PermissionRequestData, + ) -> PermissionResult { + self.entered.notify_one(); + self.release.notified().await; + PermissionResult::approve_once() + } +} + +#[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, + response_capability: None, + source: PermissionDecisionSource::HumanResponse, + surface: PermissionDecisionSurface::CopilotApp, + }) + } +} + #[async_trait] impl McpAuthHandler for CancelMcpAuthHandler { async fn handle( @@ -133,6 +321,16 @@ impl FakeServer { write_framed(&mut self.write, &serde_json::to_vec(&response).unwrap()).await; } + async fn respond_error(&mut self, request: &Value, code: i64, message: &str) { + let id = request["id"].as_u64().unwrap(); + let response = serde_json::json!({ + "jsonrpc": "2.0", + "id": id, + "error": { "code": code, "message": message }, + }); + write_framed(&mut self.write, &serde_json::to_vec(&response).unwrap()).await; + } + async fn send_notification(&mut self, method: &str, params: Value) { let notification = serde_json::json!({ "jsonrpc": "2.0", @@ -233,6 +431,228 @@ where (session, server) } +#[tokio::test] +async fn github_token_provider_uses_global_registration_and_maps_results() { + let (client, server_read, server_write) = make_client(); + let mut server = FakeServer { + read: server_read, + write: server_write, + session_id: "github-token-session".to_string(), + }; + let (args_tx, mut args_rx) = tokio::sync::mpsc::unbounded_channel(); + let provider = Arc::new(move |args: GitHubTokenProviderArgs| { + let args_tx = args_tx.clone(); + async move { + args_tx.send(args.clone()).unwrap(); + match args.host.as_str() { + "github.com" => Ok(GitHubTokenProviderResult::Token(GitHubToken::new( + "secret-token", + 8 * 60 * 60, + ))), + "cancel.example" => Ok(GitHubTokenProviderResult::Cancelled), + _ => Err(github_copilot_sdk::Error::with_message( + ErrorKind::GitHubTokenProvider, + "credential service unavailable", + )), + } + } + }); + let create_handle = tokio::spawn({ + let client = client.clone(); + async move { + client + .create_session( + SessionConfig::default() + .with_session_id("github-token-session") + .with_github_token_provider(provider), + ) + .await + .unwrap() + } + }); + + let create_request = server.read_request().await; + assert_eq!(create_request["method"], "session.create"); + assert!(create_request["params"].get("gitHubToken").is_none()); + let registration_id = create_request["params"]["gitHubTokenProviderRegistrationId"] + .as_str() + .unwrap() + .to_string(); + + server + .send_request( + 900, + "gitHubToken.getToken", + serde_json::json!({ + "registrationId": registration_id, + "host": "github.com", + "sessionId": "github-token-session", + "reason": "initial" + }), + ) + .await; + let token_response = server.read_response().await; + assert_eq!(token_response["result"]["kind"], "token"); + assert_eq!(token_response["result"]["accessToken"], "secret-token"); + assert_eq!(token_response["result"]["expiresIn"], 8 * 60 * 60); + let args = args_rx.recv().await.unwrap(); + assert_eq!(args.host, "github.com"); + assert_eq!( + args.session_id.as_ref().map(SessionId::as_str), + Some("github-token-session") + ); + assert_eq!(args.reason, GitHubTokenRequestReason::Initial); + + server + .send_request( + 901, + "gitHubToken.getToken", + serde_json::json!({ + "registrationId": registration_id, + "host": "cancel.example", + "reason": "refresh" + }), + ) + .await; + let cancelled = server.read_response().await; + assert_eq!(cancelled["result"]["kind"], "cancelled"); + + server + .send_request( + 902, + "gitHubToken.getToken", + serde_json::json!({ + "registrationId": registration_id, + "host": "error.example", + "reason": "refresh" + }), + ) + .await; + let provider_error = server.read_response().await; + assert_eq!(provider_error["error"]["code"], -32603); + assert!( + provider_error["error"]["message"] + .as_str() + .unwrap() + .contains("credential service unavailable") + ); + + server + .respond( + &create_request, + serde_json::json!({"sessionId": "github-token-session"}), + ) + .await; + let session = timeout(TIMEOUT, create_handle).await.unwrap().unwrap(); + + let delete_handle = tokio::spawn({ + let client = client.clone(); + async move { + client + .delete_session(&SessionId::new("github-token-session")) + .await + } + }); + let delete_request = server.read_request().await; + assert_eq!(delete_request["method"], "session.delete"); + server.respond(&delete_request, serde_json::json!({})).await; + timeout(TIMEOUT, delete_handle) + .await + .unwrap() + .unwrap() + .unwrap(); + + server + .send_request( + 903, + "gitHubToken.getToken", + serde_json::json!({ + "registrationId": registration_id, + "host": "github.com", + "reason": "refresh" + }), + ) + .await; + let unknown = server.read_response().await; + assert_eq!(unknown["error"]["code"], -32603); + assert!( + unknown["error"]["message"] + .as_str() + .unwrap() + .contains("unknown GitHub token provider registration") + ); + drop(session); +} + +#[tokio::test] +async fn github_token_provider_is_mutually_exclusive_and_rolls_back_failed_create() { + let provider = Arc::new(|_args: GitHubTokenProviderArgs| async { + Ok(GitHubTokenProviderResult::Cancelled) + }); + let (client, server_read, server_write) = make_client(); + let result = client + .create_session( + SessionConfig::default() + .with_github_token("static") + .with_github_token_provider(provider.clone()), + ) + .await; + let Err(error) = result else { + panic!("mutually exclusive GitHub credentials must be rejected"); + }; + assert!(matches!(error.kind(), ErrorKind::InvalidConfig)); + + let mut server = FakeServer { + read: server_read, + write: server_write, + session_id: "failed-create".to_string(), + }; + let create_handle = tokio::spawn({ + let client = client.clone(); + async move { + client + .create_session( + SessionConfig::default() + .with_session_id("failed-create") + .with_github_token_provider(provider), + ) + .await + } + }); + let create_request = server.read_request().await; + let registration_id = create_request["params"]["gitHubTokenProviderRegistrationId"] + .as_str() + .unwrap() + .to_string(); + let response = serde_json::json!({ + "jsonrpc": "2.0", + "id": create_request["id"], + "error": {"code": -32603, "message": "create failed"} + }); + write_framed(&mut server.write, &serde_json::to_vec(&response).unwrap()).await; + assert!( + timeout(TIMEOUT, create_handle) + .await + .unwrap() + .unwrap() + .is_err() + ); + + server + .send_request( + 904, + "gitHubToken.getToken", + serde_json::json!({ + "registrationId": registration_id, + "host": "github.com", + "reason": "initial" + }), + ) + .await; + let unknown = server.read_response().await; + assert_eq!(unknown["error"]["code"], -32603); +} + fn rand_id() -> u64 { static COUNTER: AtomicUsize = AtomicUsize::new(0); COUNTER.fetch_add(1, Ordering::Relaxed) as u64 @@ -639,6 +1059,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 +1076,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(); @@ -669,6 +1091,60 @@ async fn create_session_sends_new_session_options() { timeout(TIMEOUT, create_handle).await.unwrap().unwrap(); } +#[tokio::test] +async fn create_session_forwards_ask_user_variant() { + let (client, mut server_read, mut server_write) = make_client(); + + let create_handle = tokio::spawn({ + let client = client.clone(); + async move { + client + .create_session( + SessionConfig::default().with_ask_user_variant(AskUserVariant::Elicitation), + ) + .await + .unwrap() + } + }); + + let request = read_framed(&mut server_read).await; + assert_eq!(request["method"], "session.create"); + assert_eq!(request["params"]["askUserVariant"], "elicitation"); + + let session_id = requested_session_id(&request).to_string(); + server_respond_create(&mut server_write, &request, &session_id).await; + timeout(TIMEOUT, create_handle).await.unwrap().unwrap(); +} + +#[tokio::test] +async fn cold_resume_session_forwards_ask_user_variant() { + use github_copilot_sdk::types::ResumeSessionConfig; + + let (client, mut server_read, mut server_write) = make_client(); + + let resume_handle = tokio::spawn({ + let client = client.clone(); + async move { + client + .resume_session( + ResumeSessionConfig::new(SessionId::from("ask-user-variant")) + .with_ask_user_variant(AskUserVariant::Legacy), + ) + .await + .unwrap() + } + }); + + let request = read_framed(&mut server_read).await; + assert_eq!(request["method"], "session.resume"); + assert_eq!(request["params"]["sessionId"], "ask-user-variant"); + assert_eq!(request["params"]["askUserVariant"], "legacy"); + + server_respond_create(&mut server_write, &request, "ask-user-variant").await; + respond_to_reload(&mut server_read, &mut server_write).await; + timeout(TIMEOUT, resume_handle).await.unwrap().unwrap(); +} + #[tokio::test] async fn resume_session_sends_new_session_options() { use github_copilot_sdk::types::ResumeSessionConfig; @@ -683,6 +1159,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 +1177,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; @@ -717,14 +1195,99 @@ async fn create_session_sends_canvas_wire_fields() { client .create_session( SessionConfig::default() - .with_canvases([test_canvas("counter")]) - .with_request_canvas_renderer(true) - .with_request_extensions(true) - .with_extension_info(ExtensionInfo::new("github-app", "counter-provider")) - .with_canvas_provider( - CanvasProviderIdentity::new("app:builtin:window-1") - .with_name("Built-in"), - ), + .with_canvases([test_canvas("counter")]) + .with_request_canvas_renderer(true) + .with_request_extensions(true) + .with_extension_info(ExtensionInfo::new("github-app", "counter-provider")) + .with_canvas_provider( + CanvasProviderIdentity::new("app:builtin:window-1") + .with_name("Built-in"), + ), + ) + .await + .unwrap() + } + }); + + let request = read_framed(&mut server_read).await; + assert_eq!(request["method"], "session.create"); + assert_eq!(request["params"]["canvases"][0]["id"], "counter"); + assert_eq!( + request["params"]["canvases"][0]["displayName"], + "Test Canvas" + ); + assert_eq!(request["params"]["requestCanvasRenderer"], true); + assert_eq!(request["params"]["requestExtensions"], true); + assert_eq!(request["params"]["extensionInfo"]["source"], "github-app"); + assert_eq!( + request["params"]["extensionInfo"]["name"], + "counter-provider" + ); + assert_eq!( + request["params"]["canvasProvider"]["id"], + "app:builtin:window-1" + ); + assert_eq!(request["params"]["canvasProvider"]["name"], "Built-in"); + + 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 }, + }); + write_framed(&mut server_write, &serde_json::to_vec(&response).unwrap()).await; + + timeout(TIMEOUT, create_handle).await.unwrap().unwrap(); +} + +#[test] +fn managed_bypass_permissions_modes_use_wire_values() { + let disabled = ManagedSettingsPermissions::default() + .with_disable_bypass_permissions_mode(DisableBypassPermissionsModes::DISABLE); + assert_eq!( + serde_json::to_value(disabled).unwrap()["disableBypassPermissionsMode"], + "disable" + ); + + let known = ManagedSettingsPermissions::default() + .with_disable_bypass_permissions_mode(DisableBypassPermissionsModes::ALLOW_AUTO_ONLY); + assert_eq!( + serde_json::to_value(known).unwrap()["disableBypassPermissionsMode"], + "allow-auto-only" + ); + + let future = ManagedSettingsPermissions::default() + .with_disable_bypass_permissions_mode("future-fail-closed-mode"); + assert_eq!( + serde_json::to_value(future).unwrap()["disableBypassPermissionsMode"], + "future-fail-closed-mode" + ); +} + +#[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(DisableBypassPermissionsModes::ALLOW_AUTO_ONLY) + .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() @@ -733,26 +1296,45 @@ async fn create_session_sends_canvas_wire_fields() { let request = read_framed(&mut server_read).await; assert_eq!(request["method"], "session.create"); - assert_eq!(request["params"]["canvases"][0]["id"], "counter"); - assert_eq!( - request["params"]["canvases"][0]["displayName"], - "Test Canvas" - ); - assert_eq!(request["params"]["requestCanvasRenderer"], true); - assert_eq!(request["params"]["requestExtensions"], true); - assert_eq!(request["params"]["extensionInfo"]["source"], "github-app"); - assert_eq!( - request["params"]["extensionInfo"]["name"], - "counter-provider" - ); + assert_eq!(request["params"]["enableManagedSettings"], true); + let perms = &request["params"]["managedSettings"]["permissions"]; + assert_eq!(perms["disableBypassPermissionsMode"], "allow-auto-only"); + 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"]["canvasProvider"]["id"], - "app:builtin:window-1" + request["params"]["managedSettings"]["permissions"]["deny"][0], + "shell(rm*)" ); - assert_eq!(request["params"]["canvasProvider"]["name"], "Built-in"); 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, @@ -760,7 +1342,56 @@ async fn create_session_sends_canvas_wire_fields() { }); write_framed(&mut server_write, &serde_json::to_vec(&response).unwrap()).await; - timeout(TIMEOUT, create_handle).await.unwrap().unwrap(); + 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( @@ -2334,7 +2965,8 @@ async fn user_input_requested_notification_does_not_double_dispatch() { } #[tokio::test] -async fn approve_all_handler_approves_permission() { +async fn permission_confirmation_success_behavior_is_unchanged() { + let (capture, _guard) = capture_traces(); let (_session, mut server) = create_session_pair_with_config(|cfg| { cfg.with_permission_handler(Arc::new(ApproveAllHandler)) }) @@ -2358,6 +2990,246 @@ async fn approve_all_handler_approves_permission() { ); assert_eq!(request["params"]["requestId"], "perm-auto"); assert_eq!(request["params"]["result"]["kind"], "approve-once"); + server.respond(&request, serde_json::json!({})).await; + + let outcome = capture.wait_for_permission_outcome("perm-auto").await; + assert!(outcome.message_contains("Session::handle_notification response sent successfully")); + assert!(outcome.field_is("session_id", &server.session_id)); + assert!(outcome.field_is("request_id", "perm-auto")); +} + +#[tokio::test] +async fn permission_confirmation_json_rpc_error_is_observable_and_connection_stays_responsive() { + let (capture, _guard) = capture_traces(); + let (session, mut server) = create_session_pair_with_config(|cfg| { + cfg.with_permission_handler(Arc::new(ApproveAllHandler)) + }) + .await; + let session = Arc::new(session); + + server + .send_event( + "permission.requested", + serde_json::json!({ + "requestId": "perm-rpc-error", + "sessionId": server.session_id, + "permissionRequest": { "kind": "shell" }, + }), + ) + .await; + + let confirmation = timeout(TIMEOUT, server.read_request()).await.unwrap(); + assert_eq!(confirmation["method"], PERMISSION_CONFIRMATION_METHOD); + server + .respond_error(&confirmation, -32603, "permission response rejected") + .await; + + let get_events = tokio::spawn({ + let session = session.clone(); + async move { session.get_events().await } + }); + let follow_up = timeout(TIMEOUT, server.read_request()).await.unwrap(); + assert_eq!(follow_up["method"], "session.getMessages"); + server + .respond(&follow_up, serde_json::json!({ "events": [] })) + .await; + assert!(timeout(TIMEOUT, get_events).await.unwrap().unwrap().is_ok()); + + let outcome = capture.wait_for_permission_outcome("perm-rpc-error").await; + assert!(outcome.message_contains("failed to deliver permission decision back to the runtime")); + assert!(outcome.field_is("session_id", &server.session_id)); + assert!(outcome.field_is("request_id", "perm-rpc-error")); + assert!(outcome.field_is("method", PERMISSION_CONFIRMATION_METHOD)); +} + +#[tokio::test] +async fn permission_confirmation_write_failure_is_observable_and_events_stay_responsive() { + let (capture, _guard) = capture_traces(); + let entered = Arc::new(Notify::new()); + let release = Arc::new(Notify::new()); + let handler = Arc::new(GatedApproveHandler { + entered: entered.clone(), + release: release.clone(), + }); + let (session, mut server) = + create_session_pair_with_config(move |cfg| cfg.with_permission_handler(handler)).await; + let mut subscription = session.subscribe(); + + server + .send_event( + "permission.requested", + serde_json::json!({ + "requestId": "perm-write-error", + "sessionId": server.session_id, + "permissionRequest": { "kind": "shell" }, + }), + ) + .await; + let permission_event = timeout(TIMEOUT, subscription.recv()) + .await + .unwrap() + .unwrap(); + assert_eq!(permission_event.event_type, "permission.requested"); + timeout(TIMEOUT, entered.notified()).await.unwrap(); + + let FakeServer { + read, + mut write, + session_id, + } = server; + drop(read); + release.notify_one(); + + let idle_event = serde_json::json!({ + "jsonrpc": "2.0", + "method": "session.event", + "params": { + "sessionId": session_id, + "event": { + "id": "evt-after-write-error", + "timestamp": "2025-01-01T00:00:00Z", + "type": "session.idle", + "data": {}, + }, + }, + }); + write_framed(&mut write, &serde_json::to_vec(&idle_event).unwrap()).await; + let event = timeout(TIMEOUT, subscription.recv()) + .await + .unwrap() + .unwrap(); + assert_eq!(event.event_type, "session.idle"); + + let outcome = capture + .wait_for_permission_outcome("perm-write-error") + .await; + assert!(outcome.message_contains("failed to deliver permission decision back to the runtime")); + assert!(outcome.field_is("session_id", &session_id)); + assert!(outcome.field_is("request_id", "perm-write-error")); + assert!(outcome.field_is("method", PERMISSION_CONFIRMATION_METHOD)); +} + +#[tokio::test] +async fn permission_confirmation_without_response_does_not_block_events_or_other_rpcs() { + let (capture, _guard) = capture_traces(); + let (session, mut server) = create_session_pair_with_config(|cfg| { + cfg.with_permission_handler(Arc::new(ApproveAllHandler)) + }) + .await; + let session = Arc::new(session); + let mut subscription = session.subscribe(); + + server + .send_event( + "permission.requested", + serde_json::json!({ + "requestId": "perm-no-response", + "sessionId": server.session_id, + "permissionRequest": { "kind": "shell" }, + }), + ) + .await; + let permission_event = timeout(TIMEOUT, subscription.recv()) + .await + .unwrap() + .unwrap(); + assert_eq!(permission_event.event_type, "permission.requested"); + let confirmation = timeout(TIMEOUT, server.read_request()).await.unwrap(); + assert_eq!(confirmation["method"], PERMISSION_CONFIRMATION_METHOD); + + server + .send_event("session.idle", serde_json::json!({})) + .await; + let event = timeout(TIMEOUT, subscription.recv()) + .await + .unwrap() + .unwrap(); + assert_eq!(event.event_type, "session.idle"); + + let get_events = tokio::spawn({ + let session = session.clone(); + async move { session.get_events().await } + }); + let follow_up = timeout(TIMEOUT, server.read_request()).await.unwrap(); + assert_eq!(follow_up["method"], "session.getMessages"); + server + .respond(&follow_up, serde_json::json!({ "events": [] })) + .await; + assert!(timeout(TIMEOUT, get_events).await.unwrap().unwrap().is_ok()); + assert!( + capture.permission_outcome("perm-no-response").is_none(), + "the confirmation task should still be waiting silently for its response" + ); +} + +#[tokio::test] +async fn permission_confirmation_wait_is_cancelled_on_session_teardown() { + let (capture, _guard) = capture_traces(); + let (session, mut server) = create_session_pair_with_config(|cfg| { + cfg.with_permission_handler(Arc::new(ApproveAllHandler)) + }) + .await; + let session_id = server.session_id.clone(); + + server + .send_event( + "permission.requested", + serde_json::json!({ + "requestId": "perm-teardown", + "sessionId": server.session_id, + "permissionRequest": { "kind": "shell" }, + }), + ) + .await; + let confirmation = timeout(TIMEOUT, server.read_request()).await.unwrap(); + assert_eq!(confirmation["method"], PERMISSION_CONFIRMATION_METHOD); + + drop(session); + + let outcome = capture.wait_for_permission_outcome("perm-teardown").await; + assert!(outcome.message_contains("permission confirmation acknowledgement wait cancelled")); + assert!(outcome.field_is("session_id", &session_id)); + assert!(outcome.field_is("request_id", "perm-teardown")); + assert!(outcome.field_is("method", PERMISSION_CONFIRMATION_METHOD)); +} + +#[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] @@ -2672,10 +3544,10 @@ async fn send_and_wait_drop_clears_waiter() { } /// Cancel-safety regression: `Session::stop_event_loop` must NOT abort -/// the event-loop task mid-handler. An in-flight handler (here a slow -/// `userInput.request` callback) must run to completion before the loop -/// exits — the CLI receives the response on the wire before the session -/// tears down. +/// the event-loop task at an arbitrary await point. Requests are +/// dispatched to their own tasks, so a handler that is still running when +/// shutdown is signalled keeps going and its response still reaches the +/// wire rather than being lost mid-protocol. /// /// Closes RFD-400 review finding #3. #[tokio::test] @@ -2719,31 +3591,84 @@ async fn stop_event_loop_completes_in_flight_handler() { // Give the loop a moment to dispatch into the handler. tokio::time::sleep(Duration::from_millis(20)).await; - // Now request shutdown. The loop is parked in handle_request awaiting - // the slow handler. `notify_one()` buffers the signal until the loop - // re-enters its select, which can only happen after the handler - // returns and the response is sent on the wire. + // Now request shutdown while the spawned handler is still sleeping. let stop_handle = tokio::spawn({ let session = session.clone(); async move { session.stop_event_loop().await } }); - // Verify the handler's response lands on the wire BEFORE the loop - // exits — i.e. stop_event_loop did not abort mid-handler. + // The handler task is independent of the loop, so its response still + // lands on the wire instead of being lost to an aborted task. let response = timeout(Duration::from_secs(2), server.read_response()) .await .unwrap(); assert_eq!(response["id"], 900); assert_eq!(response["result"]["answer"], "completed"); - // stop_event_loop completes after the handler returns and the loop - // observes the buffered shutdown signal on its next select iteration. timeout(Duration::from_secs(2), stop_handle) .await .unwrap() .unwrap(); } +/// A panicking request handler must still answer its request id. Tokio +/// isolates the panic to the spawned handler task, so without an explicit +/// reply the caller would wait out its own timeout on a request that can +/// never complete. +#[tokio::test] +async fn panicking_request_handler_responds_with_internal_error() { + struct PanickingHandler; + #[async_trait] + impl UserInputHandler for PanickingHandler { + async fn handle( + &self, + _session_id: SessionId, + _question: String, + _choices: Option>, + _allow_freeform: Option, + ) -> Option { + panic!("handler blew up"); + } + } + + let (session, mut server) = create_session_pair_with_config(|cfg| { + cfg.with_user_input_handler(Arc::new(PanickingHandler)) + }) + .await; + + server + .send_request( + 901, + "userInput.request", + serde_json::json!({ + "sessionId": server.session_id, + "question": "boom", + "choices": null, + "allowFreeform": true, + }), + ) + .await; + + let response = timeout(TIMEOUT, server.read_response()).await.unwrap(); + assert_eq!(response["id"], 901); + assert_eq!(response["error"]["code"], -32603); + assert!(response.get("result").is_none()); + + // The loop survives the panicking handler and keeps serving requests. + server + .send_request( + 902, + "unknown.method", + serde_json::json!({ "sessionId": server.session_id }), + ) + .await; + let response = timeout(TIMEOUT, server.read_response()).await.unwrap(); + assert_eq!(response["id"], 902); + assert_eq!(response["error"]["code"], -32601); + + session.stop_event_loop().await; +} + /// Cancel-safety regression: dropping a Session does NOT abort the event /// loop mid-handler. The loop sees the buffered shutdown signal on its /// next select iteration and exits cleanly. This is the Drop equivalent @@ -4172,13 +5097,10 @@ async fn create_serializes_commands_strips_handler() { let rollback = &wire[1]; assert_eq!(rollback["name"], "rollback"); - assert!( - rollback.get("description").is_none(), - "description should be omitted when None, got: {rollback}" - ); + assert_eq!(rollback["description"], ""); assert!(rollback.get("handler").is_none()); let rollback_keys: Vec<&String> = rollback.as_object().unwrap().keys().collect(); - assert_eq!(rollback_keys.len(), 1, "got keys: {rollback_keys:?}"); + assert_eq!(rollback_keys.len(), 2, "got keys: {rollback_keys:?}"); } #[tokio::test] diff --git a/scripts/codegen/csharp.ts b/scripts/codegen/csharp.ts index 2d68e68e27..00afec6000 100644 --- a/scripts/codegen/csharp.ts +++ b/scripts/codegen/csharp.ts @@ -40,6 +40,7 @@ import { isSchemaExperimental, isSchemaInternal, isOpaqueJson, + isOpaqueInProcess, isObjectSchema, isVoidSchema, getNullableInner, @@ -355,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); } @@ -1351,9 +1387,6 @@ function resolveSessionPropertyType( function generateDataClass(variant: EventVariant, knownTypes: Map, nestedClasses: Map, enumOutput: string[]): string { const dataVisibility = isSchemaInternal(variant.dataSchema) ? "internal" : "public"; - if (!variant.dataSchema?.properties) return `${dataVisibility} sealed partial class ${variant.dataClassName} { }`; - - const required = new Set(variant.dataSchema.required || []); const lines: string[] = []; if (variant.dataDescription) { lines.push(...xmlDocComment(variant.dataDescription, "")); @@ -1366,6 +1399,12 @@ function generateDataClass(variant: EventVariant, knownTypes: Map a.localeCompare(b))) { @@ -2568,6 +2607,8 @@ function generateRpcCode( externalJsonSerializableRefs: Map> = new Map(), externalValueTypes: Set = new Set() ): string { + schema = cloneSchemaForCodegen(schema); + omitUnrepresentableInternalProperties(schema); emittedRpcClassSchemas.clear(); emittedRpcEnumResultTypes.clear(); experimentalRpcTypes.clear(); @@ -2682,14 +2723,23 @@ export async function generateRpc(schemaPath?: string, sessionEventsSchema?: JSO const resolvedPath = schemaPath ?? (await getApiSchemaPath()); handWrittenCSharpTypeNames = await collectHandWrittenCSharpTypeNames(); let schema = fixNullableRequiredRefsInApiSchema(cloneSchemaForCodegen((await loadSchemaJson(resolvedPath)) as ApiSchema)); + let sessionEventsCode: string | undefined; if (sessionEventsSchema) { + sessionEventsCode = generateSessionEventsCode(sessionEventsSchema); const sharedDefinitions = findSharedSchemaDefinitions( schema as unknown as Record, sessionEventsSchema as unknown as Record ); const reachableDefinitions = collectReachableDefinitionNames(sessionEventsSchema as unknown as Record); for (const name of [...sharedDefinitions]) { - if (!reachableDefinitions.has(name)) { + const typeName = typeToClassName(name); + const declarationPattern = new RegExp( + `\\bpublic\\s+(?:(?:sealed|abstract|partial|readonly)\\s+)*(?:class|struct|enum)\\s+${typeName}\\b` + ); + if ( + !reachableDefinitions.has(name) || + (!declarationPattern.test(sessionEventsCode) && !handWrittenCSharpTypeNames.has(typeName)) + ) { sharedDefinitions.delete(name); } } @@ -2697,8 +2747,7 @@ export async function generateRpc(schemaPath?: string, sessionEventsSchema?: JSO } const externalJsonSerializableRefs = new Map>(); const externalValueTypes = new Set(); - if (sessionEventsSchema) { - const sessionEventsCode = generateSessionEventsCode(sessionEventsSchema); + if (sessionEventsSchema && sessionEventsCode) { const externalRefs = collectExternalSchemaRefNames(schema); const sessionEventRefs = externalRefs.get("session-events.schema.json"); if (sessionEventRefs && sessionEventRefs.size > 0) { diff --git a/scripts/codegen/go.ts b/scripts/codegen/go.ts index d6eda7f99a..b4297a239c 100644 --- a/scripts/codegen/go.ts +++ b/scripts/codegen/go.ts @@ -560,6 +560,7 @@ interface GoCodegenCtx { discriminatedUnionRawVariantSuffix?: string; skipDefinitionTypeNames?: Set; encodingBlocks?: Set; + unionVariantMarshalers?: Set; packageName?: string; } @@ -1339,8 +1340,11 @@ function pushGoJSONSchemaMatchLines( } } -function goVariantMatchFuncName(variantTypeName: string): string { - return goUnexportedFunctionName("matches", variantTypeName); +function goVariantMatchFuncName(unionTypeName: string, variantTypeName: string): string { + const scopedVariantName = variantTypeName.startsWith(unionTypeName) + ? variantTypeName + : `${unionTypeName}${variantTypeName}`; + return goUnexportedFunctionName("matches", scopedVariantName); } // Minimal checks used to distinguish variants that share the same discriminator. @@ -1712,13 +1716,14 @@ function pushGoJSONTargetedMatchSpecLines( } function goVariantMatchFunctionLines( + unionTypeName: string, variant: GoDiscriminatedUnionVariant, groupVariants: GoDiscriminatedUnionVariant[], discriminatorProp: string, ctx: GoCodegenCtx ): string[] { const lines: string[] = []; - lines.push(`func ${goVariantMatchFuncName(variant.typeName)}(data []byte) bool {`); + lines.push(`func ${goVariantMatchFuncName(unionTypeName, variant.typeName)}(data []byte) bool {`); const spec = goVariantTargetedMatchSpec(variant, groupVariants, discriminatorProp, ctx); if (spec.positiveTerms.length === 0 && spec.negativeExistsPaths.length === 0) { pushGoJSONSchemaMatchLines(lines, variant.schema, "data", ctx, "\t", "raw"); @@ -1830,7 +1835,7 @@ function emitGoFlatDiscriminatedUnion( for (const variant of unionVariants) { const groupVariants = ambiguousGroupsByVariantTypeName.get(variant.typeName); if (groupVariants) { - pushGoEncodingBlock(goVariantMatchFunctionLines(variant, groupVariants, discriminatorProp, ctx), ctx); + pushGoEncodingBlock(goVariantMatchFunctionLines(typeName, variant, groupVariants, discriminatorProp, ctx), ctx); } } @@ -1868,7 +1873,7 @@ function emitGoFlatDiscriminatedUnion( unmarshalLines.push(`\t\treturn &d, nil`); } else { for (const mappedVariant of mappedVariants) { - unmarshalLines.push(`\t\tif ${goVariantMatchFuncName(mappedVariant.typeName)}(data) {`); + unmarshalLines.push(`\t\tif ${goVariantMatchFuncName(typeName, mappedVariant.typeName)}(data) {`); unmarshalLines.push(`\t\t\tvar d ${mappedVariant.typeName}`); unmarshalLines.push(`\t\t\tif err := json.Unmarshal(data, &d); err != nil {`); unmarshalLines.push(`\t\t\t\treturn nil, err`); @@ -1971,18 +1976,22 @@ function emitGoFlatDiscriminatedUnion( lines.push(`\treturn ${discGoType}(r.Discriminator)`); } lines.push(`}`); - pushGoEncodingBlock([ - `func (r ${variantTypeName}) MarshalJSON() ([]byte, error) {`, - `\ttype alias ${variantTypeName}`, - `\treturn json.Marshal(struct {`, - `\t\t${discGoName} ${discGoType} \`json:"${discriminatorProp}"\``, - `\t\talias`, - `\t}{`, - `\t\t${discGoName}: r.${discriminatorMethodName}(),`, - `\t\talias: alias(r),`, - `\t})`, - `}`, - ], ctx); + ctx.unionVariantMarshalers ??= new Set(); + if (!ctx.unionVariantMarshalers.has(variantTypeName)) { + ctx.unionVariantMarshalers.add(variantTypeName); + pushGoEncodingBlock([ + `func (r ${variantTypeName}) MarshalJSON() ([]byte, error) {`, + `\ttype alias ${variantTypeName}`, + `\treturn json.Marshal(struct {`, + `\t\t${discGoName} ${discGoType} \`json:"${discriminatorProp}"\``, + `\t\talias`, + `\t}{`, + `\t\t${discGoName}: r.${discriminatorMethodName}(),`, + `\t\talias: alias(r),`, + `\t})`, + `}`, + ], ctx); + } } ctx.structs.push(lines.join("\n")); @@ -2017,7 +2026,7 @@ function emitGoRequiredFieldDiscriminatedUnion( lines.push(``); for (const variant of unionVariants) { - pushGoEncodingBlock(goVariantMatchFunctionLines(variant, unionVariants, "", ctx), ctx); + pushGoEncodingBlock(goVariantMatchFunctionLines(typeName, variant, unionVariants, "", ctx), ctx); } const unmarshalLines: string[] = []; @@ -2026,7 +2035,7 @@ function emitGoRequiredFieldDiscriminatedUnion( unmarshalLines.push(`\t\treturn nil, nil`); unmarshalLines.push(`\t}`); for (const variant of unionVariants) { - unmarshalLines.push(`\tif ${goVariantMatchFuncName(variant.typeName)}(data) {`); + unmarshalLines.push(`\tif ${goVariantMatchFuncName(typeName, variant.typeName)}(data) {`); unmarshalLines.push(`\t\tvar d ${variant.typeName}`); unmarshalLines.push(`\t\tif err := json.Unmarshal(data, &d); err != nil {`); unmarshalLines.push(`\t\t\treturn nil, err`); @@ -2890,10 +2899,13 @@ function emitGoUnionWrapperStruct(typeName: string, schema: JSONSchema7, ctx: Go discriminatorValues: [], })); for (const variant of matchVariants) { - pushGoEncodingBlock(goVariantMatchFunctionLines(variant, matchVariants, "", ctx), ctx); + pushGoEncodingBlock(goVariantMatchFunctionLines(typeName, variant, matchVariants, "", ctx), ctx); } for (const [index, variant] of matchVariants.entries()) { - matchFunctionsByField.set(objectVariantSchemas[index].field.name, goVariantMatchFuncName(variant.typeName)); + matchFunctionsByField.set( + objectVariantSchemas[index].field.name, + goVariantMatchFuncName(typeName, variant.typeName) + ); } } encodingLines.push(`func (r ${typeName}) MarshalJSON() ([]byte, error) {`); @@ -3645,19 +3657,14 @@ function resolveSharedAnyOfVariant( async function collectHandWrittenGoPublicNames(): Promise> { const goDir = path.join(REPO_ROOT, "go"); const names = new Set(); - let entries: string[]; - try { - entries = await fs.readdir(goDir); - } catch { - return names; - } + // Read the directory entries with their types so the file check and the read + // are not separate operations on the same path. + const entries = await fs.readdir(goDir, { withFileTypes: true }).catch(() => []); for (const entry of entries) { - if (!entry.endsWith(".go")) continue; - if (entry.startsWith("z")) continue; - const filePath = path.join(goDir, entry); - const stat = await fs.stat(filePath); - if (!stat.isFile()) continue; - const content = await fs.readFile(filePath, "utf-8"); + if (!entry.isFile()) continue; + if (!entry.name.endsWith(".go")) continue; + if (entry.name.startsWith("z")) continue; + const content = await fs.readFile(path.join(goDir, entry.name), "utf-8"); for (const name of collectGoTopLevelNames(content, "type")) names.add(name); for (const name of collectGoTopLevelNames(content, "const")) names.add(name); } @@ -3960,7 +3967,10 @@ async function generateRpc(schemaPath?: string): Promise { if (generatedTypeCode.includes("time.Time")) { imports.push(`"time"`); } - if (schema.clientSession || schema.clientGlobal) { + const publicClientSession = schema.clientSession + ? filterNodeByVisibility(schema.clientSession, "public") + : null; + if (publicClientSession || schema.clientGlobal) { imports.push(`"errors"`, `"fmt"`); } imports.push(`"github.com/github/copilot-sdk/go/internal/jsonrpc2"`); @@ -4261,8 +4271,9 @@ function clientHandlerMethodName(rpcMethod: string): string { return toPascalCase(rpcMethod.split(".").at(-1)!); } -function emitClientSessionApiRegistration(lines: string[], clientSchema: Record, resolveType: (name: string) => string, unionInfos: Map): void { - const groups = collectClientGroups(clientSchema); +export function emitClientSessionApiRegistration(lines: string[], clientSchema: Record, resolveType: (name: string) => string, unionInfos: Map): void { + const publicClientSchema = filterNodeByVisibility(clientSchema, "public") ?? {}; + const groups = collectClientGroups(publicClientSchema); for (const { groupName, groupNode, methods } of groups) { const interfaceName = clientHandlerInterfaceName(groupName); @@ -4317,17 +4328,19 @@ function emitClientSessionApiRegistration(lines: string[], clientSchema: Record< lines.push(`}`); lines.push(``); - lines.push(`func clientSessionHandlerError(err error) *jsonrpc2.Error {`); - lines.push(`\tif err == nil {`); - lines.push(`\t\treturn nil`); - lines.push(`\t}`); - lines.push(`\tvar rpcErr *jsonrpc2.Error`); - lines.push(`\tif errors.As(err, &rpcErr) {`); - lines.push(`\t\treturn rpcErr`); - lines.push(`\t}`); - lines.push(`\treturn &jsonrpc2.Error{Code: -32603, Message: err.Error()}`); - lines.push(`}`); - lines.push(``); + if (groups.length > 0) { + lines.push(`func clientSessionHandlerError(err error) *jsonrpc2.Error {`); + lines.push(`\tif err == nil {`); + lines.push(`\t\treturn nil`); + lines.push(`\t}`); + lines.push(`\tvar rpcErr *jsonrpc2.Error`); + lines.push(`\tif errors.As(err, &rpcErr) {`); + lines.push(`\t\treturn rpcErr`); + lines.push(`\t}`); + lines.push(`\treturn &jsonrpc2.Error{Code: -32603, Message: err.Error()}`); + lines.push(`}`); + lines.push(``); + } lines.push(`// RegisterClientSessionAPIHandlers registers handlers for server-to-client session API calls.`); lines.push(`func RegisterClientSessionAPIHandlers(client *jsonrpc2.Client, getHandlers func(sessionID string) *ClientSessionAPIHandlers) {`); diff --git a/scripts/codegen/package-lock.json b/scripts/codegen/package-lock.json index 5ed410e943..3d631c1b11 100644 --- a/scripts/codegen/package-lock.json +++ b/scripts/codegen/package-lock.json @@ -697,9 +697,9 @@ "license": "BSD-3-Clause" }, "node_modules/js-yaml": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", - "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", "funding": [ { "type": "github", diff --git a/scripts/codegen/python.ts b/scripts/codegen/python.ts index 978021a984..b3bfcf8bc9 100644 --- a/scripts/codegen/python.ts +++ b/scripts/codegen/python.ts @@ -143,12 +143,22 @@ function placeholderToQuicktypeIdentifiers(placeholder: string): string[] { return [...new Set([basic, basic.replace(/Mcp/g, "MCP")])]; } -function postProcessExternalRefsForPython( +export function postProcessExternalRefsForPython( code: string, placeholderToReal: Map, externalEnumNames: Set = new Set() ): string { for (const [placeholder, realName] of placeholderToReal) { + const markerProperty = `__externalRefMarker_${placeholder}`; + const markerClass = [ + ...code.matchAll( + /(?:^|\n)(@dataclass\r?\nclass (\w+)\b[\s\S]*?)(?=\n@dataclass\b|\nclass\s+\w|\ndef\s+\w|$)/g + ), + ].find((match) => match[1].includes(`"${markerProperty}"`)); + if (markerClass) { + code = code.replace(markerClass[0], "\n"); + code = code.replace(new RegExp(`\\b${escapeRegExp(markerClass[2])}\\b`, "g"), realName); + } for (const quicktypeName of placeholderToQuicktypeIdentifiers(placeholder)) { code = code.replace( new RegExp( @@ -201,6 +211,17 @@ function collectPythonExternalEnumNames( return enumNames; } +function preservePythonSessionEventConstructorOrder(schema: JSONSchema7): void { + for (const definitions of [schema.definitions, schema.$defs]) { + if (!definitions) continue; + const customTool = definitions.PermissionRequestCustomTool; + if (!customTool || typeof customTool !== "object") continue; + const skipPermission = (customTool as JSONSchema7).properties?.skipPermission; + if (!skipPermission || typeof skipPermission !== "object") continue; + (skipPermission as Record)["x-copilot-sdk-append-last"] = true; + } +} + function preservePythonRpcStringDateFields(definitions: Record): void { const quotaSnapshot = definitions.AccountQuotaSnapshot; const resetDate = quotaSnapshot?.properties?.resetDate as JSONSchema7 | undefined; @@ -2574,6 +2595,13 @@ function emitPyFlatDiscriminatedUnion( ctx.classes.push(lines.join("\n")); } +const PYTHON_INTEGER_DECODER = [ + `def from_int(x: Any) -> int:`, + ` assert isinstance(x, (int, float)) and not isinstance(x, bool)`, + ` assert not isinstance(x, float) or x.is_integer()`, + ` return int(x)`, +].join("\n"); + export function generatePythonSessionEventsCode(schema: JSONSchema7): string { const variants = extractPyEventVariants(schema); const ctx: PyCodegenCtx = { @@ -2642,9 +2670,7 @@ export function generatePythonSessionEventsCode(schema: JSONSchema7): string { out.push(` return x`); out.push(``); out.push(``); - out.push(`def from_int(x: Any) -> int:`); - out.push(` assert isinstance(x, int) and not isinstance(x, bool)`); - out.push(` return x`); + out.push(PYTHON_INTEGER_DECODER); out.push(``); out.push(``); out.push(`def to_int(x: Any) -> int:`); @@ -2944,6 +2970,7 @@ async function generateSessionEvents(schemaPath?: string): Promise { const schema = addManagedApprovalRequiredToPermissionRequests( (await loadSchemaJson(resolvedPath)) as JSONSchema7 ); + preservePythonSessionEventConstructorOrder(schema); const processed = propagateInternalVisibility(postProcessSchema(schema)); let code = generatePythonSessionEventsCode(processed); const { typeNames } = collectInternalSymbols(processed); @@ -3347,6 +3374,14 @@ def _patch_model_capabilities(data: dict) -> dict: // Patch models.list to normalize capabilities before deserialization let finalCode = lines.join("\n"); + finalCode = finalCode.replace( + [ + `def from_int(x: Any) -> int:`, + ` assert isinstance(x, int) and not isinstance(x, bool)`, + ` return x`, + ].join("\n"), + PYTHON_INTEGER_DECODER + ); finalCode = finalCode.replace( `ModelList.from_dict(await self._client.request("models.list"`, `ModelList.from_dict(_patch_model_capabilities(await self._client.request("models.list"`, @@ -3738,12 +3773,13 @@ function clientSessionHandlerMethodName(rpcMethod: string): string { return toSnakeCase(parts[parts.length - 1]); } -function emitClientSessionApiRegistration( +export function emitClientSessionApiRegistration( lines: string[], node: Record, resolveType: (name: string) => string ): void { - const groups = Object.entries(node).filter(([, value]) => typeof value === "object" && value !== null && !isRpcMethod(value)); + const publicNode = filterNodeByVisibility(node, "public") ?? {}; + const groups = Object.entries(publicNode).filter(([, value]) => typeof value === "object" && value !== null && !isRpcMethod(value)); for (const [groupName, groupNode] of groups) { const handlerName = `${toPascalCase(groupName)}Handler`; diff --git a/scripts/codegen/rust.ts b/scripts/codegen/rust.ts index 4090318f05..b3cc5d5753 100644 --- a/scripts/codegen/rust.ts +++ b/scripts/codegen/rust.ts @@ -300,6 +300,7 @@ function tryEmitRustUnion( const enumName = (typeof schema.title === "string" && schema.title) || parentTypeName + toPascalCase(jsonPropName); + const isAllowedUnionType = ctx.allowedUnionTypeNames.has(enumName); const resolvedVariants: RustUnionVariant[] = []; for (let i = 0; i < nonNull.length; i++) { @@ -318,7 +319,20 @@ function tryEmitRustUnion( resolveObjectSchema(variant, ctx.definitions) ?? resolveSchema(variant, ctx.definitions) ?? variant; - if (!isObjectSchema(resolved)) return null; + if (!isObjectSchema(resolved)) { + if (!isAllowedUnionType) return null; + resolvedVariants.push({ + schema: resolved as JSONSchema7, + typeName: resolveRustType( + resolved as JSONSchema7, + enumName, + `variant${i + 1}`, + true, + ctx, + ), + }); + continue; + } const discriminatorValue = Object.values(resolved.properties ?? {}).find( (prop) => typeof prop === "object" && (prop as JSONSchema7).const !== undefined, ) as JSONSchema7 | undefined; @@ -335,7 +349,6 @@ function tryEmitRustUnion( } const discriminator = findRustDiscriminator(resolvedVariants); - const isAllowedUnionType = ctx.allowedUnionTypeNames.has(enumName); if (discriminator) { if ( ctx.unionDiscriminatorProperties && @@ -344,7 +357,7 @@ function tryEmitRustUnion( ) { return null; } - } else if (!ctx.allowUntaggedUnions || !isAllowedUnionType) { + } else if (!ctx.allowUntaggedUnions && !isAllowedUnionType) { return null; } @@ -365,9 +378,7 @@ function tryEmitRustUnion( const lines: string[] = []; if (schema.description) { - for (const line of schema.description.split(/\r?\n/)) { - lines.push(`/// ${line}`); - } + pushRustDoc(lines, schema.description); } pushRustExperimentalDocs(lines, isSchemaExperimental(schema) || ctx.experimentalTypeNames.has(enumName)); lines.push("#[derive(Debug, Clone, Serialize, Deserialize)]"); @@ -455,7 +466,8 @@ function pushRustExperimentalDocs( function pushRustDoc(lines: string[], text: string | undefined, indent = ""): void { if (!text) return; - for (const paragraph of text.trim().split(/\r?\n/)) { + const sanitized = text.replace(/\[::\]/g, "`[::]`"); + for (const paragraph of sanitized.trim().split(/\r?\n/)) { if (paragraph.trim().length === 0) { lines.push(`${indent}///`); } else { @@ -958,9 +970,7 @@ function emitRustStruct( for (const { propName, prop, isReq, rustField, rustType } of fields) { if (prop.description) { - for (const line of prop.description.split(/\r?\n/)) { - lines.push(` /// ${line}`); - } + pushRustDoc(lines, prop.description, " "); } pushRustExperimentalDocs(lines, isSchemaExperimental(prop), " "); const propIsInternal = isSchemaInternal(prop); @@ -1447,7 +1457,10 @@ function generateApiTypesCode( const defCollections = collectDefinitionCollections( apiSchema as Record, ); - const ctx = makeCtx(defCollections, { nonDefaultableTypes }); + const ctx = makeCtx(defCollections, { + nonDefaultableTypes, + allowedUnionTypeNames: ["AuthInfo", "McpOauthProbeResult", "SettableAuthInfo", "ToolResult"], + }); // Collect all RPC methods before emitting shared definitions so method stability // can propagate to referenced data types. diff --git a/scripts/codegen/typescript.ts b/scripts/codegen/typescript.ts index 30cefd3e90..f5e8acb146 100644 --- a/scripts/codegen/typescript.ts +++ b/scripts/codegen/typescript.ts @@ -25,6 +25,7 @@ import { collectExperimentalOnlyRpcReferencedDefinitionNames, collectReachableDefinitionNames, collectRpcMethodReferencedDefinitionNames, + filterNodeByVisibility, findSharedSchemaDefinitions, hasSchemaPayload, parseExternalSchemaRef, @@ -40,7 +41,9 @@ import { isSchemaInternal, appendPropertyMarkerTagsToDescriptions, getEnumValueDescriptions, - stripOpaqueJsonMarker, + isBareSchemaNode, + isOpaqueInProcess, + isOpaqueJson, loadSchemaJson, fixBrandCasing, type ApiSchema, @@ -52,6 +55,35 @@ 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}`; @@ -327,7 +359,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; @@ -349,24 +384,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")) { @@ -493,15 +556,23 @@ 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 @@ -659,6 +730,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 || {}); @@ -761,12 +833,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 @@ -1000,15 +1077,16 @@ function handlerMethodName(rpcMethod: string): string { * `getHandler` callback that resolves a sessionId to a handler object. * Param types include sessionId — handler code can simply ignore it. */ -function emitClientSessionApiRegistration(clientSchema: Record): string[] { +export function emitClientSessionApiRegistration(clientSchema: Record): string[] { const lines: string[] = []; - const groups = collectClientGroups(clientSchema); + const publicClientSchema = filterNodeByVisibility(clientSchema, "public") ?? {}; + const groups = collectClientGroups(publicClientSchema); // Emit a handler interface per group for (const [groupName, methods] of groups) { const interfaceName = toPascalCase(groupName) + "Handler"; - const groupDeprecated = isNodeFullyDeprecated(clientSchema[groupName] as Record); - const groupExperimental = isNodeFullyExperimental(clientSchema[groupName] as Record); + const groupDeprecated = isNodeFullyDeprecated(publicClientSchema[groupName] as Record); + const groupExperimental = isNodeFullyExperimental(publicClientSchema[groupName] as Record); if (groupDeprecated) { lines.push(`/** @deprecated Handler for \`${groupName}\` client session API methods. */`); } else if (groupExperimental) { diff --git a/scripts/codegen/utils.ts b/scripts/codegen/utils.ts index 42e78b9a07..bad31a7675 100644 --- a/scripts/codegen/utils.ts +++ b/scripts/codegen/utils.ts @@ -12,6 +12,8 @@ import type { JSONSchema7, JSONSchema7Definition } from "json-schema"; import path from "path"; import { fileURLToPath } from "url"; import { promisify } from "util"; +import { COPILOT_CLI_VERSION } from "../../nodejs/src/cliVersion.js"; +import { ensureCopilotPackage } from "../../nodejs/scripts/releaseArtifacts.js"; export const execFileAsync = promisify(execFile); @@ -45,59 +47,23 @@ export type SchemaWithSharedDefinitions = T }; // ── Schema paths ──────────────────────────────────────────────────────────── -const SDK_NODE_MODULES = path.join(REPO_ROOT, "nodejs/node_modules"); - /** - * Resolve a JSON schema shipped by the `@github/copilot` CLI package. - * - * The CLI package layout changed in 1.0.64-1: the umbrella `@github/copilot` - * package became a thin loader and its bundled assets (including the JSON - * schemas) moved into the platform-specific packages installed as optional - * dependencies, e.g. `@github/copilot-linux-x64` or `@github/copilot-win32-x64`. - * - * To support both layouts we look in the umbrella package first (older - * versions) and then in whichever platform package was installed for the - * current host. + * Resolve a JSON schema from the pinned Copilot CLI GitHub Release. */ -async function resolveCopilotSchemaPath(nodeModulesDir: string, fileName: string): Promise { - const candidates = [path.join(nodeModulesDir, "@github/copilot/schemas", fileName)]; - - const githubScopeDir = path.join(nodeModulesDir, "@github"); - try { - for (const entry of await fs.readdir(githubScopeDir)) { - if (entry.startsWith("copilot-")) { - candidates.push(path.join(githubScopeDir, entry, "schemas", fileName)); - } - } - } catch (err) { - const code = (err as NodeJS.ErrnoException).code; - if (code !== "ENOENT" && code !== "ENOTDIR") { - throw err; - } - // @github scope directory may not exist yet; fall through to the error below. - } - - for (const candidate of candidates) { - try { - await fs.access(candidate); - return candidate; - } catch { - // Try the next candidate. - } - } - - throw new Error( - `${fileName} not found under ${githubScopeDir}. Run 'npm ci' in nodejs/ first.` - ); +async function resolveCopilotSchemaPath(fileName: string): Promise { + const packageRoot = await ensureCopilotPackage(COPILOT_CLI_VERSION); + const schemaPath = path.join(packageRoot, "schemas", fileName); + await fs.access(schemaPath); + return schemaPath; } export async function getSessionEventsSchemaPath(): Promise { - return resolveCopilotSchemaPath(SDK_NODE_MODULES, "session-events.schema.json"); + return resolveCopilotSchemaPath("session-events.schema.json"); } export async function getApiSchemaPath(cliArg?: string): Promise { if (cliArg) return cliArg; - return resolveCopilotSchemaPath(SDK_NODE_MODULES, "api.schema.json"); + return resolveCopilotSchemaPath("api.schema.json"); } // ── Brand casing normalization ────────────────────────────────────────────── @@ -199,6 +165,27 @@ export function postProcessSchema(schema: JSONSchema7): JSONSchema7 { const processed = { ...schema } as JSONSchema7WithDefs; + if (processed.title === "ProviderModelConfig" && processed.properties) { + const tokenFields = new Set([ + "maxPromptTokens", + "maxContextWindowTokens", + "maxOutputTokens", + ]); + processed.properties = Object.fromEntries( + Object.entries(processed.properties).map(([key, value]) => { + if ( + tokenFields.has(key) && + typeof value === "object" && + value !== null && + (value as JSONSchema7).type === "number" + ) { + return [key, { ...(value as JSONSchema7), type: "integer" }]; + } + return [key, value]; + }) + ); + } + if ("const" in processed && typeof processed.const === "boolean") { processed.enum = [processed.const]; delete processed.const; @@ -456,6 +443,7 @@ export function cloneSchemaForCodegen(value: T): T { const PERMISSION_REQUEST_DEFINITION_NAMES = [ "PermissionRequestCustomTool", "PermissionRequestExtensionManagement", + "PermissionRequestExtensionEnvAccess", "PermissionRequestExtensionPermissionAccess", "PermissionRequestFactory", "PermissionRequestHook", @@ -943,6 +931,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/docs-validation/validate.ts b/scripts/docs-validation/validate.ts index b609ef8593..6ce615eab2 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/.npmrc b/test/harness/.npmrc new file mode 100644 index 0000000000..100721e01a --- /dev/null +++ b/test/harness/.npmrc @@ -0,0 +1 @@ +omit-lockfile-registry-resolved=true diff --git a/test/harness/anthropicMessagesAdapter.ts b/test/harness/anthropicMessagesAdapter.ts index acc74a2bf9..36e98c3b36 100644 --- a/test/harness/anthropicMessagesAdapter.ts +++ b/test/harness/anthropicMessagesAdapter.ts @@ -4,6 +4,7 @@ import type { ChatCompletion } from "openai/resources/chat/completions"; import { + canonicalUserMessageSeparator, CanonicalMessage, CanonicalToolCall, formatSseEvent, @@ -178,7 +179,7 @@ function convertAnthropicUserMessage( content: onlyText ? contentParts .map((part) => (part.type === "text" ? part.text : "")) - .join("\n") + .join(canonicalUserMessageSeparator) : [...contentParts], }); contentParts.length = 0; diff --git a/test/harness/capturingHttpProxy.test.ts b/test/harness/capturingHttpProxy.test.ts index f434d3e67c..cba4d45e53 100644 --- a/test/harness/capturingHttpProxy.test.ts +++ b/test/harness/capturingHttpProxy.test.ts @@ -10,9 +10,21 @@ describe("Capturing HTTP Proxy", () => { let proxy: CapturingHttpProxy; let testServer: http.Server; let testServerAddress: string; + let onHangingRequest: (() => void) | undefined; + let onStreamingResponse: (() => void) | undefined; beforeEach(async () => { testServer = http.createServer((req, res) => { + if (req.url === "/hang") { + onHangingRequest?.(); + return; + } + if (req.url === "/stream") { + res.writeHead(200, { "content-type": "text/plain" }); + res.write("started"); + onStreamingResponse?.(); + return; + } res.writeHead(200, { "content-type": "application/json" }); res.end(JSON.stringify({ message: "Hello", path: req.url })); }); @@ -71,4 +83,34 @@ describe("Capturing HTTP Proxy", () => { } as CapturedExchange, ]); }); + + test("stops while a proxied request is still active", async () => { + proxy = new CapturingHttpProxy(testServerAddress); + const proxyUrl = await proxy.start(); + const requestStarted = new Promise((resolve) => { + onHangingRequest = resolve; + }); + const request = fetch(`${proxyUrl}/hang`).catch(() => undefined); + await requestStarted; + + await proxy.stop(); + + await request; + }); + + test("stops while a proxied response is still streaming", async () => { + proxy = new CapturingHttpProxy(testServerAddress); + const proxyUrl = await proxy.start(); + const responseStarted = new Promise((resolve) => { + onStreamingResponse = resolve; + }); + const responsePromise = fetch(`${proxyUrl}/stream`); + await responseStarted; + const response = await responsePromise; + const body = response.text().catch(() => undefined); + + await proxy.stop(); + + await body; + }); }); diff --git a/test/harness/capturingHttpProxy.ts b/test/harness/capturingHttpProxy.ts index edccca4ead..fdc1fc46c1 100644 --- a/test/harness/capturingHttpProxy.ts +++ b/test/harness/capturingHttpProxy.ts @@ -10,7 +10,10 @@ import https from "https"; */ export class CapturingHttpProxy { private readonly capturedExchanges: CapturedExchange[] = []; + private readonly activeRequests = new Set(); + private readonly activeResponses = new Set(); private server?: http.Server; + private stopPromise?: Promise; constructor(private targetUrl: string) {} @@ -90,6 +93,10 @@ export class CapturingHttpProxy { res.end(); }, onError: (err) => { + if (!this.server) { + res.destroy(); + return; + } console.error("Error in proxying request:", err); const endTime = Date.now(); const formattedError = @@ -130,9 +137,19 @@ export class CapturingHttpProxy { } async stop(): Promise { - if (this.server) { - return new Promise((resolve, reject) => { - this.server!.close((err) => { + if (this.stopPromise) { + return this.stopPromise; + } + + const server = this.server; + if (!server) { + return; + } + + this.server = undefined; + this.stopPromise = (async () => { + const closed = new Promise((resolve, reject) => { + server.close((err) => { if (err) { reject(err); } else { @@ -140,14 +157,38 @@ export class CapturingHttpProxy { } }); }); - } + + // server.close() waits for active connections. A replayed streaming request + // can otherwise wedge fixture teardown after its test has already passed. + server.closeAllConnections(); + for (const response of this.activeResponses) { + response.destroy(); + } + this.activeResponses.clear(); + for (const request of this.activeRequests) { + request.destroy(); + } + this.activeRequests.clear(); + + await closed; + })(); + return this.stopPromise; } performRequest(options: PerformRequestOptions): void { + if (this.stopPromise) { + options.onError(new Error("Proxy is stopping")); + return; + } + const protocol = options.isHttps ? https : http; const upstreamRequest = protocol.request( options.requestOptions, (upstreamResponse) => { + this.activeResponses.add(upstreamResponse); + upstreamResponse.once("close", () => { + this.activeResponses.delete(upstreamResponse); + }); options.onResponseStart( upstreamResponse.statusCode || 500, upstreamResponse.headers, @@ -157,6 +198,10 @@ export class CapturingHttpProxy { }, ); + this.activeRequests.add(upstreamRequest); + upstreamRequest.once("close", () => { + this.activeRequests.delete(upstreamRequest); + }); upstreamRequest.on("error", options.onError); if (options.body) { diff --git a/test/harness/modelProtocolAdapterShared.ts b/test/harness/modelProtocolAdapterShared.ts index 1f879da5d0..dcfee3876e 100644 --- a/test/harness/modelProtocolAdapterShared.ts +++ b/test/harness/modelProtocolAdapterShared.ts @@ -4,6 +4,8 @@ export type JsonObject = Record; +export const canonicalUserMessageSeparator = "\n\n\n"; + export type CanonicalToolCall = { id: string; type: "function"; diff --git a/test/harness/modelProtocolAdapters.test.ts b/test/harness/modelProtocolAdapters.test.ts index ddb6fe40bf..add59ae77f 100644 --- a/test/harness/modelProtocolAdapters.test.ts +++ b/test/harness/modelProtocolAdapters.test.ts @@ -42,7 +42,7 @@ const endpoints: Record = { const models: Record = { capi: "gpt-4.1", - "anthropic-messages": "claude-sonnet-4.5", + "anthropic-messages": "claude-sonnet-5", "openai-responses": "gpt-4.1", "openai-completions": "gpt-4.1", }; @@ -128,6 +128,52 @@ async function postJson( } describe("Anthropic Messages adapter", () => { + test("canonicalizes adjacent plain-text content blocks", () => { + const result = JSON.parse( + anthropicMessagesRequestToChatCompletion( + JSON.stringify({ + model: "test-model", + messages: [ + { + role: "user", + content: [ + { type: "text", text: "First prompt" }, + { type: "text", text: "Recovery prompt" }, + ], + }, + ], + }), + ), + ) as { + messages: Array<{ role: string; content: unknown }>; + }; + + expect(result.messages).toEqual([ + { + role: "user", + content: "First prompt\n\n\nRecovery prompt", + }, + ]); + }); + + test.each([ + ["string content", "Hello"], + ["one text block", [{ type: "text", text: "Hello" }]], + ])("preserves a single plain-text user block from %s", (_, content) => { + const result = JSON.parse( + anthropicMessagesRequestToChatCompletion( + JSON.stringify({ + model: "test-model", + messages: [{ role: "user", content }], + }), + ), + ) as { + messages: Array<{ role: string; content: unknown }>; + }; + + expect(result.messages).toEqual([{ role: "user", content: "Hello" }]); + }); + test("normalizes messages, binary content, and tools", () => { const result = JSON.parse( anthropicMessagesRequestToChatCompletion( @@ -589,6 +635,36 @@ describe("protocol-aware replay", () => { }); }); + test("canonicalizes Anthropic content blocks before coalescing user messages", async () => { + await writeSnapshot([ + { role: "system", content: "${system}" }, + { role: "user", content: "First prompt" }, + { role: "user", content: "Recovery prompt" }, + { role: "user", content: "Final prompt" }, + { role: "assistant", content: "Recovered" }, + ]); + const request = requestFor("anthropic-messages", "Final prompt"); + request.messages = [ + { + role: "user", + content: [ + { type: "text", text: "First prompt" }, + { type: "text", text: "Recovery prompt" }, + ], + }, + ...(request.messages as unknown[]), + ]; + + await withProxy("anthropic-messages", async (proxyUrl) => { + const response = await postJson( + proxyUrl, + endpoints["anthropic-messages"], + request, + ); + expect(response.status).toBe(200); + }); + }); + test.each(backends)( "replays compaction responses through %s", async (backend) => { diff --git a/test/harness/package-lock.json b/test/harness/package-lock.json index 4702dbba63..547e0a6ec9 100644 --- a/test/harness/package-lock.json +++ b/test/harness/package-lock.json @@ -9,7 +9,6 @@ "version": "1.0.0", "license": "ISC", "devDependencies": { - "@github/copilot": "^1.0.78", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "@types/node-forge": "^1.3.14", @@ -26,8 +25,7 @@ }, "node_modules/@emnapi/core": { "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "integrity": "sha1-OAzMjyQS6iLR2XLff47iOjucdGc=", "dev": true, "license": "MIT", "optional": true, @@ -38,8 +36,7 @@ }, "node_modules/@emnapi/runtime": { "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "integrity": "sha1-SyYMDTU0IE6YxhELjbGph9JuyHw=", "dev": true, "license": "MIT", "optional": true, @@ -49,8 +46,7 @@ }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "integrity": "sha1-KP7SGhuhznl8RKBwq8lNQvOuhUg=", "dev": true, "license": "MIT", "optional": true, @@ -60,8 +56,7 @@ }, "node_modules/@esbuild/aix-ppc64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", - "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "integrity": "sha1-egGo0uwvuy2seK2tCbD6eB5Agr4=", "cpu": [ "ppc64" ], @@ -77,8 +72,7 @@ }, "node_modules/@esbuild/android-arm": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", - "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "integrity": "sha1-cEvSl95tdi3lTqu+r79V9nVqvi8=", "cpu": [ "arm" ], @@ -94,8 +88,7 @@ }, "node_modules/@esbuild/android-arm64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", - "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "integrity": "sha1-tUCifRTkr9BYSWpNvsTT9BTbEQo=", "cpu": [ "arm64" ], @@ -111,8 +104,7 @@ }, "node_modules/@esbuild/android-x64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", - "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "integrity": "sha1-0csWbTSw+/D+irRgpVlPJKN4cB4=", "cpu": [ "x64" ], @@ -128,8 +120,6 @@ }, "node_modules/@esbuild/darwin-arm64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", - "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", "cpu": [ "arm64" ], @@ -145,8 +135,7 @@ }, "node_modules/@esbuild/darwin-x64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", - "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "integrity": "sha1-ZVVqQyoeTXIDLYIYwZMvzKGkl3I=", "cpu": [ "x64" ], @@ -162,8 +151,7 @@ }, "node_modules/@esbuild/freebsd-arm64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", - "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "integrity": "sha1-LmHgWS+QMNfj2uGO4l68U1kYrvY=", "cpu": [ "arm64" ], @@ -179,8 +167,7 @@ }, "node_modules/@esbuild/freebsd-x64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", - "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "integrity": "sha1-yV7CiZWe+AecTcqBeh4sS+Zrm9M=", "cpu": [ "x64" ], @@ -196,8 +183,7 @@ }, "node_modules/@esbuild/linux-arm": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", - "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "integrity": "sha1-wJoPZ5F1kqwN6JKpvk04FN69Kmw=", "cpu": [ "arm" ], @@ -213,8 +199,7 @@ }, "node_modules/@esbuild/linux-arm64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", - "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "integrity": "sha1-QLIhdd2gYYLz7oFBGGxf8wTEpxc=", "cpu": [ "arm64" ], @@ -230,8 +215,7 @@ }, "node_modules/@esbuild/linux-ia32": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", - "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "integrity": "sha1-pYD5xnZ5eDOJHlGfx6EzfIr9jbM=", "cpu": [ "ia32" ], @@ -247,8 +231,7 @@ }, "node_modules/@esbuild/linux-loong64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", - "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "integrity": "sha1-RkUs8yHcf56Rwvp4Cla7Vuec1os=", "cpu": [ "loong64" ], @@ -264,8 +247,7 @@ }, "node_modules/@esbuild/linux-mips64el": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", - "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "integrity": "sha1-QhGzGE3WYI9T3LIuOfXTTuCIUsg=", "cpu": [ "mips64el" ], @@ -281,8 +263,7 @@ }, "node_modules/@esbuild/linux-ppc64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", - "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "integrity": "sha1-aXhXwqYcubC2u2ZS5AwdxeHKjl0=", "cpu": [ "ppc64" ], @@ -298,8 +279,7 @@ }, "node_modules/@esbuild/linux-riscv64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", - "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "integrity": "sha1-0ZKUPrFGpArExkl9DPe+NbmGvwg=", "cpu": [ "riscv64" ], @@ -315,8 +295,7 @@ }, "node_modules/@esbuild/linux-s390x": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", - "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "integrity": "sha1-rOoDVtoODrwI+Xz3ucLkAeHmSNw=", "cpu": [ "s390x" ], @@ -332,8 +311,7 @@ }, "node_modules/@esbuild/linux-x64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", - "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "integrity": "sha1-bww84MtkxTS3DExF7LLBbTTjXf0=", "cpu": [ "x64" ], @@ -349,8 +327,7 @@ }, "node_modules/@esbuild/netbsd-arm64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", - "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "integrity": "sha1-i813B3oNzjN4tXT+2ybSolO3PTY=", "cpu": [ "arm64" ], @@ -366,8 +343,7 @@ }, "node_modules/@esbuild/netbsd-x64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", - "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "integrity": "sha1-5/sqAemcgwyU5mI82f77TI+1g0c=", "cpu": [ "x64" ], @@ -383,8 +359,7 @@ }, "node_modules/@esbuild/openbsd-arm64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", - "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "integrity": "sha1-xSkJNy24uG4sVeBaiUADO1Zgo7I=", "cpu": [ "arm64" ], @@ -400,8 +375,7 @@ }, "node_modules/@esbuild/openbsd-x64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", - "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "integrity": "sha1-xCe5vlpkwmL/mn63C1+7qt9EbGw=", "cpu": [ "x64" ], @@ -417,8 +391,7 @@ }, "node_modules/@esbuild/openharmony-arm64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", - "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "integrity": "sha1-3JsUe6yi5sSzyFVxdB70hgpIkJc=", "cpu": [ "arm64" ], @@ -434,8 +407,7 @@ }, "node_modules/@esbuild/sunos-x64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", - "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "integrity": "sha1-zoZtEt8TwV5MmfBzo9Rm9uBkmzo=", "cpu": [ "x64" ], @@ -451,8 +423,7 @@ }, "node_modules/@esbuild/win32-arm64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", - "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "integrity": "sha1-dGjjaS0B1inVlB5dg4F7uA+eObQ=", "cpu": [ "arm64" ], @@ -468,8 +439,7 @@ }, "node_modules/@esbuild/win32-ia32": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", - "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "integrity": "sha1-pbwAY/sryrbQ7WPyoVN5WLwmnsY=", "cpu": [ "ia32" ], @@ -485,8 +455,7 @@ }, "node_modules/@esbuild/win32-x64": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", - "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "integrity": "sha1-EAZO5E9DR7kMmgK0Rrv4CpFjKxI=", "cpu": [ "x64" ], @@ -500,169 +469,8 @@ "node": ">=18" } }, - "node_modules/@github/copilot": { - "version": "1.0.78", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.78.tgz", - "integrity": "sha512-jn+8HLZC3R7d6K1/1g9L1iWNKzBVS3JdVcx40r3aWyS5r+MLV1OPNp0fo5OfRMCDIm3NmEaaoqypi9sQkCXuiQ==", - "dev": true, - "license": "SEE LICENSE IN LICENSE.md", - "dependencies": { - "detect-libc": "^2.1.2" - }, - "bin": { - "copilot": "npm-loader.js" - }, - "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.78", - "@github/copilot-darwin-x64": "1.0.78", - "@github/copilot-linux-arm64": "1.0.78", - "@github/copilot-linux-x64": "1.0.78", - "@github/copilot-linuxmusl-arm64": "1.0.78", - "@github/copilot-linuxmusl-x64": "1.0.78", - "@github/copilot-win32-arm64": "1.0.78", - "@github/copilot-win32-x64": "1.0.78" - } - }, - "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.78", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.78.tgz", - "integrity": "sha512-P11+VyWg8ad0WlywGtO2d7AxqTLJv4hkUicFg6Ycth5lfk00aCu/74YOOZSPO6C2bBBJhAza7oAdmauM6KEojw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "darwin" - ], - "bin": { - "copilot-darwin-arm64": "copilot" - } - }, - "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.78", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.78.tgz", - "integrity": "sha512-stimP3WDFs2GU8nJzTJbtRpZViV4bsf80yg7QrFq+G4RISQ3Nihg/3/H0U6UQF1+txMJ/Ohmb5RFYxSw1Hj2sw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "darwin" - ], - "bin": { - "copilot-darwin-x64": "copilot" - } - }, - "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.78", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.78.tgz", - "integrity": "sha512-K31PRKGTm252V1Lof7ypjg283R2QSm3BgoCvZfX2taos4wqC3SaTozSQKwW3dgrAx7A3G3SGEoilVCNqfigdZA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "copilot-linux-arm64": "copilot" - } - }, - "node_modules/@github/copilot-linux-x64": { - "version": "1.0.78", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.78.tgz", - "integrity": "sha512-QK3oMtAn9dIv+1u1kx0xNpZNtZxdI+uZVIyLl7myp+Oh2Uj8BLagVv6a7uP0cDphO3TgfIdlvpepCe5MIcx0fw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "copilot-linux-x64": "copilot" - } - }, - "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.78", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.78.tgz", - "integrity": "sha512-F/0cTMsz6ug4yiXn3RKaCAMsLR261U5Njb6G9Y/HeAI7ES/tKEo2t5SHuvgXaIH4mYiZsRvfDKdX7c0WgBX/Jg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "copilot-linuxmusl-arm64": "copilot" - } - }, - "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.78", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.78.tgz", - "integrity": "sha512-YMaJaeBGbArGAFYel+yFaFW/0rFgh0Oqki2f2mUtlonTX/xHr8EB4+mTnMJkHYMFy4gOTC3OtSEEe1NaW/cBXQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "copilot-linuxmusl-x64": "copilot" - } - }, - "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.78", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.78.tgz", - "integrity": "sha512-ktDkFXaaecEKD3hpM6ydM9lKOdoCfsQsXCmzLzE7DCmSpbbMCdfPfWfZ7MOclmKmpZ5/MNfr4U2l8CUqGerzYA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "win32" - ], - "bin": { - "copilot-win32-arm64": "copilot.exe" - } - }, - "node_modules/@github/copilot-win32-x64": { - "version": "1.0.78", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.78.tgz", - "integrity": "sha512-Gd8l2T4eqYEWlOEPd0SZznQ+YYgYrwOkE0QXodMkhCBbPdgu/uTzb7mnISWwnVAgqs7pONdF1GOpHkTo+ay8CQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "win32" - ], - "bin": { - "copilot-win32-x64": "copilot.exe" - } - }, "node_modules/@hono/node-server": { "version": "1.19.14", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", - "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", "dev": true, "license": "MIT", "engines": { @@ -674,15 +482,11 @@ }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, "node_modules/@modelcontextprotocol/sdk": { "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz", - "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", "dev": true, "license": "MIT", "dependencies": { @@ -721,28 +525,28 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "version": "1.2.3", + "integrity": "sha1-l+PUXXQk3F2h1OMvO/OykvbBtEw=", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@tybys/wasm-util": "^0.10.1" + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/Brooooooklyn" }, "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" } }, "node_modules/@oxc-project/types": { "version": "0.133.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", - "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", "dev": true, "license": "MIT", "funding": { @@ -751,8 +555,7 @@ }, "node_modules/@rolldown/binding-android-arm64": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", - "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", + "integrity": "sha1-VM6Pg4IhP0oxSgwve6g/gf/q5ZI=", "cpu": [ "arm64" ], @@ -768,8 +571,6 @@ }, "node_modules/@rolldown/binding-darwin-arm64": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", - "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", "cpu": [ "arm64" ], @@ -785,8 +586,7 @@ }, "node_modules/@rolldown/binding-darwin-x64": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", - "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", + "integrity": "sha1-U/V94fWZ7PHbE4I8/IjBj7gJVK0=", "cpu": [ "x64" ], @@ -802,8 +602,7 @@ }, "node_modules/@rolldown/binding-freebsd-x64": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", - "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", + "integrity": "sha1-bz/dobeuqsnSaKUmgEtPuW5ONfE=", "cpu": [ "x64" ], @@ -819,8 +618,7 @@ }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", - "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", + "integrity": "sha1-2HpFS/WFzJZ2hJN36R1uN1KXMm8=", "cpu": [ "arm" ], @@ -836,8 +634,7 @@ }, "node_modules/@rolldown/binding-linux-arm64-gnu": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", - "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", + "integrity": "sha1-QZ/Wv2Es80jxBSjLzZTrq5YH2NE=", "cpu": [ "arm64" ], @@ -853,8 +650,7 @@ }, "node_modules/@rolldown/binding-linux-arm64-musl": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", - "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", + "integrity": "sha1-/MaRhpa7doRId+HkkwoY/Q03QGk=", "cpu": [ "arm64" ], @@ -870,8 +666,7 @@ }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", - "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", + "integrity": "sha1-Mq7LfI2uXU8qjN5XoFjshpkVQvg=", "cpu": [ "ppc64" ], @@ -887,8 +682,7 @@ }, "node_modules/@rolldown/binding-linux-s390x-gnu": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", - "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", + "integrity": "sha1-vtk0bqgea7i5PPEfXYi3fbiQt2M=", "cpu": [ "s390x" ], @@ -904,8 +698,7 @@ }, "node_modules/@rolldown/binding-linux-x64-gnu": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", - "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", + "integrity": "sha1-ZMLSb3Xf/ZtaH5dVegCudyUMjLc=", "cpu": [ "x64" ], @@ -921,8 +714,7 @@ }, "node_modules/@rolldown/binding-linux-x64-musl": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", - "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", + "integrity": "sha1-WkUTLopHZZ7qrztUDClUqXyGD/M=", "cpu": [ "x64" ], @@ -938,8 +730,7 @@ }, "node_modules/@rolldown/binding-openharmony-arm64": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", - "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", + "integrity": "sha1-KQUTBoxV6EnchFejKv7h17Csswk=", "cpu": [ "arm64" ], @@ -955,8 +746,7 @@ }, "node_modules/@rolldown/binding-wasm32-wasi": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", - "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", + "integrity": "sha1-PZly2/GpU9PHr6pKDyDvKy458xs=", "cpu": [ "wasm32" ], @@ -974,8 +764,7 @@ }, "node_modules/@rolldown/binding-win32-arm64-msvc": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", - "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", + "integrity": "sha1-oASrYHoW1vA7y1VXKP+IivdXc60=", "cpu": [ "arm64" ], @@ -991,8 +780,7 @@ }, "node_modules/@rolldown/binding-win32-x64-msvc": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", - "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", + "integrity": "sha1-4qJbNGkaHMihIJ195wkGMCbdDNs=", "cpu": [ "x64" ], @@ -1008,22 +796,17 @@ }, "node_modules/@rolldown/pluginutils": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", - "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, "license": "MIT" }, "node_modules/@standard-schema/spec": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "dev": true, "license": "MIT" }, "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "version": "0.10.3", + "integrity": "sha1-AVy6np3UfOFNA9KoxdVHv7FpZl0=", "dev": true, "license": "MIT", "optional": true, @@ -1033,8 +816,6 @@ }, "node_modules/@types/chai": { "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "license": "MIT", "dependencies": { @@ -1044,22 +825,16 @@ }, "node_modules/@types/deep-eql": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", "dev": true, "license": "MIT" }, "node_modules/@types/estree": { "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, "node_modules/@types/node": { "version": "25.3.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.3.tgz", - "integrity": "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1068,8 +843,6 @@ }, "node_modules/@types/node-forge": { "version": "1.3.14", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz", - "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", "dev": true, "license": "MIT", "dependencies": { @@ -1078,8 +851,6 @@ }, "node_modules/@vitest/expect": { "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.8.tgz", - "integrity": "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1096,8 +867,6 @@ }, "node_modules/@vitest/mocker": { "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.8.tgz", - "integrity": "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==", "dev": true, "license": "MIT", "dependencies": { @@ -1123,8 +892,6 @@ }, "node_modules/@vitest/pretty-format": { "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.8.tgz", - "integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==", "dev": true, "license": "MIT", "dependencies": { @@ -1136,8 +903,6 @@ }, "node_modules/@vitest/runner": { "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.8.tgz", - "integrity": "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==", "dev": true, "license": "MIT", "dependencies": { @@ -1150,8 +915,6 @@ }, "node_modules/@vitest/snapshot": { "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.8.tgz", - "integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1166,8 +929,6 @@ }, "node_modules/@vitest/spy": { "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.8.tgz", - "integrity": "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==", "dev": true, "license": "MIT", "funding": { @@ -1176,8 +937,6 @@ }, "node_modules/@vitest/utils": { "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.8.tgz", - "integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==", "dev": true, "license": "MIT", "dependencies": { @@ -1191,8 +950,6 @@ }, "node_modules/accepts": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "dev": true, "license": "MIT", "dependencies": { @@ -1205,8 +962,6 @@ }, "node_modules/ajv": { "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", "dependencies": { @@ -1222,8 +977,6 @@ }, "node_modules/ajv-formats": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1240,8 +993,6 @@ }, "node_modules/assertion-error": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, "license": "MIT", "engines": { @@ -1250,8 +1001,6 @@ }, "node_modules/body-parser": { "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", "dev": true, "license": "MIT", "dependencies": { @@ -1275,8 +1024,6 @@ }, "node_modules/bytes": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true, "license": "MIT", "engines": { @@ -1285,8 +1032,6 @@ }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1299,8 +1044,6 @@ }, "node_modules/call-bound": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "dev": true, "license": "MIT", "dependencies": { @@ -1316,8 +1059,6 @@ }, "node_modules/chai": { "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true, "license": "MIT", "engines": { @@ -1326,8 +1067,6 @@ }, "node_modules/content-disposition": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", - "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", "dev": true, "license": "MIT", "engines": { @@ -1340,8 +1079,6 @@ }, "node_modules/content-type": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true, "license": "MIT", "engines": { @@ -1350,15 +1087,11 @@ }, "node_modules/convert-source-map": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, "license": "MIT" }, "node_modules/cookie": { "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "dev": true, "license": "MIT", "engines": { @@ -1367,8 +1100,6 @@ }, "node_modules/cookie-signature": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "dev": true, "license": "MIT", "engines": { @@ -1377,8 +1108,6 @@ }, "node_modules/cors": { "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", "dev": true, "license": "MIT", "dependencies": { @@ -1395,8 +1124,6 @@ }, "node_modules/cross-spawn": { "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { @@ -1410,8 +1137,6 @@ }, "node_modules/debug": { "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { @@ -1428,8 +1153,6 @@ }, "node_modules/depd": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true, "license": "MIT", "engines": { @@ -1438,8 +1161,6 @@ }, "node_modules/detect-libc": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1448,8 +1169,6 @@ }, "node_modules/dunder-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "dev": true, "license": "MIT", "dependencies": { @@ -1463,15 +1182,11 @@ }, "node_modules/ee-first": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "dev": true, "license": "MIT" }, "node_modules/encodeurl": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "dev": true, "license": "MIT", "engines": { @@ -1480,8 +1195,6 @@ }, "node_modules/es-define-property": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, "license": "MIT", "engines": { @@ -1490,8 +1203,6 @@ }, "node_modules/es-errors": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "dev": true, "license": "MIT", "engines": { @@ -1500,15 +1211,11 @@ }, "node_modules/es-module-lexer": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", - "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", "dev": true, "license": "MIT" }, "node_modules/es-object-atoms": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "dev": true, "license": "MIT", "dependencies": { @@ -1520,8 +1227,6 @@ }, "node_modules/esbuild": { "version": "0.28.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", - "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1562,15 +1267,11 @@ }, "node_modules/escape-html": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "dev": true, "license": "MIT" }, "node_modules/estree-walker": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, "license": "MIT", "dependencies": { @@ -1579,8 +1280,6 @@ }, "node_modules/etag": { "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "dev": true, "license": "MIT", "engines": { @@ -1589,8 +1288,6 @@ }, "node_modules/eventsource": { "version": "3.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", - "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", "dev": true, "license": "MIT", "dependencies": { @@ -1602,8 +1299,6 @@ }, "node_modules/eventsource-parser": { "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", "dev": true, "license": "MIT", "engines": { @@ -1612,8 +1307,6 @@ }, "node_modules/expect-type": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1622,8 +1315,6 @@ }, "node_modules/express": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "dev": true, "license": "MIT", "dependencies": { @@ -1666,8 +1357,6 @@ }, "node_modules/express-rate-limit": { "version": "8.5.2", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", - "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", "dev": true, "license": "MIT", "dependencies": { @@ -1685,15 +1374,12 @@ }, "node_modules/fast-deep-equal": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true, "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", - "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", + "version": "3.1.7", + "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", "dev": true, "funding": [ { @@ -1709,8 +1395,6 @@ }, "node_modules/fdir": { "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", "engines": { @@ -1727,8 +1411,6 @@ }, "node_modules/finalhandler": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "dev": true, "license": "MIT", "dependencies": { @@ -1749,8 +1431,6 @@ }, "node_modules/forwarded": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "dev": true, "license": "MIT", "engines": { @@ -1759,8 +1439,6 @@ }, "node_modules/fresh": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "dev": true, "license": "MIT", "engines": { @@ -1769,10 +1447,7 @@ }, "node_modules/fsevents": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, - "hasInstallScript": true, "license": "MIT", "optional": true, "os": [ @@ -1784,8 +1459,6 @@ }, "node_modules/function-bind": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, "license": "MIT", "funding": { @@ -1794,8 +1467,6 @@ }, "node_modules/get-intrinsic": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1819,8 +1490,6 @@ }, "node_modules/get-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "dev": true, "license": "MIT", "dependencies": { @@ -1833,8 +1502,6 @@ }, "node_modules/gopd": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, "license": "MIT", "engines": { @@ -1846,8 +1513,6 @@ }, "node_modules/has-symbols": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, "license": "MIT", "engines": { @@ -1859,8 +1524,6 @@ }, "node_modules/hasown": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1871,9 +1534,7 @@ } }, "node_modules/hono": { - "version": "4.12.32", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.32.tgz", - "integrity": "sha512-XcuyW9qE2kJn07PkecMOBd5Vq/hMy7mmGw+idz1yblbg9N17ijJODrvPkn7/dwL3Kulj8LcRJ69DLOWf91dRUg==", + "version": "4.13.1", "dev": true, "license": "MIT", "engines": { @@ -1882,8 +1543,6 @@ }, "node_modules/http-errors": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1903,8 +1562,6 @@ }, "node_modules/iconv-lite": { "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "dev": true, "license": "MIT", "dependencies": { @@ -1920,15 +1577,11 @@ }, "node_modules/inherits": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true, "license": "ISC" }, "node_modules/ip-address": { "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": { @@ -1937,8 +1590,6 @@ }, "node_modules/ipaddr.js": { "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "dev": true, "license": "MIT", "engines": { @@ -1947,22 +1598,16 @@ }, "node_modules/is-promise": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "dev": true, "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, "license": "ISC" }, "node_modules/jose": { "version": "6.1.3", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", - "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", "dev": true, "license": "MIT", "funding": { @@ -1971,22 +1616,16 @@ }, "node_modules/json-schema-traverse": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true, "license": "MIT" }, "node_modules/json-schema-typed": { "version": "8.0.2", - "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", - "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", "dev": true, "license": "BSD-2-Clause" }, "node_modules/lightningcss": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "dev": true, "license": "MPL-2.0", "dependencies": { @@ -2015,8 +1654,7 @@ }, "node_modules/lightningcss-android-arm64": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "integrity": "sha1-8DOIURbf79nG9UeHUj41FLYeGWg=", "cpu": [ "arm64" ], @@ -2036,8 +1674,6 @@ }, "node_modules/lightningcss-darwin-arm64": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", "cpu": [ "arm64" ], @@ -2057,8 +1693,7 @@ }, "node_modules/lightningcss-darwin-x64": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "integrity": "sha1-NfPpczLRMLnKGB4RtWje1q68bV4=", "cpu": [ "x64" ], @@ -2078,8 +1713,7 @@ }, "node_modules/lightningcss-freebsd-x64": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "integrity": "sha1-l3enZHK2Ttb/lDQq1kx7r9eUpXU=", "cpu": [ "x64" ], @@ -2099,8 +1733,7 @@ }, "node_modules/lightningcss-linux-arm-gnueabihf": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "integrity": "sha1-E65lLhq3O5E117faFy9mbEEK1T0=", "cpu": [ "arm" ], @@ -2120,8 +1753,7 @@ }, "node_modules/lightningcss-linux-arm64-gnu": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "integrity": "sha1-QXhYeVqUWS9oASOhsfnaig4e8zU=", "cpu": [ "arm64" ], @@ -2141,8 +1773,7 @@ }, "node_modules/lightningcss-linux-arm64-musl": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "integrity": "sha1-a+NmkugQtxgECAL9gJYjz/5zITM=", "cpu": [ "arm64" ], @@ -2162,8 +1793,7 @@ }, "node_modules/lightningcss-linux-x64-gnu": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "integrity": "sha1-C3gDr06yHP043Tn+Kru1PH3QkfY=", "cpu": [ "x64" ], @@ -2183,8 +1813,7 @@ }, "node_modules/lightningcss-linux-x64-musl": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "integrity": "sha1-iNyLqGXd3bGsXvBLDxYYBEGMFjs=", "cpu": [ "x64" ], @@ -2204,8 +1833,7 @@ }, "node_modules/lightningcss-win32-arm64-msvc": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "integrity": "sha1-TzC6P6XpJfW3n5RejMDRdsOxqzg=", "cpu": [ "arm64" ], @@ -2225,8 +1853,7 @@ }, "node_modules/lightningcss-win32-x64-msvc": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "integrity": "sha1-FBqlYFZFBkkokCu0rwRfp9n0Igo=", "cpu": [ "x64" ], @@ -2246,8 +1873,6 @@ }, "node_modules/magic-string": { "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2256,8 +1881,6 @@ }, "node_modules/math-intrinsics": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "dev": true, "license": "MIT", "engines": { @@ -2266,8 +1889,6 @@ }, "node_modules/media-typer": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "dev": true, "license": "MIT", "engines": { @@ -2276,8 +1897,6 @@ }, "node_modules/merge-descriptors": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "dev": true, "license": "MIT", "engines": { @@ -2289,8 +1908,6 @@ }, "node_modules/mime-db": { "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "dev": true, "license": "MIT", "engines": { @@ -2299,8 +1916,6 @@ }, "node_modules/mime-types": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "dev": true, "license": "MIT", "dependencies": { @@ -2316,15 +1931,11 @@ }, "node_modules/ms": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, "license": "MIT" }, "node_modules/nanoid": { "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": [ { @@ -2342,8 +1953,6 @@ }, "node_modules/negotiator": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "dev": true, "license": "MIT", "engines": { @@ -2352,8 +1961,6 @@ }, "node_modules/node-forge": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", - "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", "dev": true, "license": "(BSD-3-Clause OR GPL-2.0)", "engines": { @@ -2362,8 +1969,6 @@ }, "node_modules/object-assign": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "dev": true, "license": "MIT", "engines": { @@ -2372,8 +1977,6 @@ }, "node_modules/object-inspect": { "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true, "license": "MIT", "engines": { @@ -2385,8 +1988,6 @@ }, "node_modules/obug": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", - "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", "dev": true, "funding": [ "https://github.com/sponsors/sxzz", @@ -2396,8 +1997,6 @@ }, "node_modules/on-finished": { "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dev": true, "license": "MIT", "dependencies": { @@ -2409,8 +2008,6 @@ }, "node_modules/once": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, "license": "ISC", "dependencies": { @@ -2419,8 +2016,6 @@ }, "node_modules/openai": { "version": "6.17.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-6.17.0.tgz", - "integrity": "sha512-NHRpPEUPzAvFOAFs9+9pC6+HCw/iWsYsKCMPXH5Kw7BpMxqd8g/A07/1o7Gx2TWtCnzevVRyKMRFqyiHyAlqcA==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2441,8 +2036,6 @@ }, "node_modules/parseurl": { "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "dev": true, "license": "MIT", "engines": { @@ -2451,8 +2044,6 @@ }, "node_modules/path-key": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", "engines": { @@ -2461,8 +2052,6 @@ }, "node_modules/path-to-regexp": { "version": "8.4.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", - "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", "dev": true, "license": "MIT", "funding": { @@ -2472,22 +2061,16 @@ }, "node_modules/pathe": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, "license": "MIT" }, "node_modules/picocolors": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -2499,8 +2082,6 @@ }, "node_modules/pkce-challenge": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", - "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", "dev": true, "license": "MIT", "engines": { @@ -2509,8 +2090,6 @@ }, "node_modules/postcss": { "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": [ { @@ -2538,8 +2117,6 @@ }, "node_modules/proxy-addr": { "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "dev": true, "license": "MIT", "dependencies": { @@ -2552,8 +2129,6 @@ }, "node_modules/qs": { "version": "6.15.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", - "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -2568,8 +2143,6 @@ }, "node_modules/range-parser": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "dev": true, "license": "MIT", "engines": { @@ -2578,8 +2151,6 @@ }, "node_modules/raw-body": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "dev": true, "license": "MIT", "dependencies": { @@ -2594,8 +2165,6 @@ }, "node_modules/require-from-string": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true, "license": "MIT", "engines": { @@ -2604,8 +2173,6 @@ }, "node_modules/rolldown": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", - "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", "dev": true, "license": "MIT", "dependencies": { @@ -2638,8 +2205,6 @@ }, "node_modules/router": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2655,15 +2220,11 @@ }, "node_modules/safer-buffer": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true, "license": "MIT" }, "node_modules/send": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2689,8 +2250,6 @@ }, "node_modules/serve-static": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "dev": true, "license": "MIT", "dependencies": { @@ -2709,15 +2268,11 @@ }, "node_modules/setprototypeof": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true, "license": "ISC" }, "node_modules/shebang-command": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "license": "MIT", "dependencies": { @@ -2729,8 +2284,6 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", "engines": { @@ -2739,8 +2292,6 @@ }, "node_modules/side-channel": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dev": true, "license": "MIT", "dependencies": { @@ -2759,8 +2310,6 @@ }, "node_modules/side-channel-list": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "dev": true, "license": "MIT", "dependencies": { @@ -2776,8 +2325,6 @@ }, "node_modules/side-channel-map": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "dev": true, "license": "MIT", "dependencies": { @@ -2795,8 +2342,6 @@ }, "node_modules/side-channel-weakmap": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "dev": true, "license": "MIT", "dependencies": { @@ -2815,15 +2360,11 @@ }, "node_modules/siginfo": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true, "license": "ISC" }, "node_modules/source-map-js": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -2832,15 +2373,11 @@ }, "node_modules/stackback": { "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "dev": true, "license": "MIT" }, "node_modules/statuses": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "dev": true, "license": "MIT", "engines": { @@ -2849,22 +2386,16 @@ }, "node_modules/std-env": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", - "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", "dev": true, "license": "MIT" }, "node_modules/tinybench": { "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", "dev": true, "license": "MIT" }, "node_modules/tinyexec": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", - "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", "dev": true, "license": "MIT", "engines": { @@ -2873,8 +2404,6 @@ }, "node_modules/tinyglobby": { "version": "0.2.17", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", - "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { @@ -2890,8 +2419,6 @@ }, "node_modules/tinyrainbow": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", "dev": true, "license": "MIT", "engines": { @@ -2900,8 +2427,6 @@ }, "node_modules/toidentifier": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true, "license": "MIT", "engines": { @@ -2910,16 +2435,13 @@ }, "node_modules/tslib": { "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "integrity": "sha1-YS7+TtI11Wfoq6Xypfq3AoCt6D8=", "dev": true, "license": "0BSD", "optional": true }, "node_modules/tsx": { "version": "4.22.4", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", - "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", "dev": true, "license": "MIT", "dependencies": { @@ -2937,8 +2459,6 @@ }, "node_modules/type-is": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "dev": true, "license": "MIT", "dependencies": { @@ -2952,8 +2472,6 @@ }, "node_modules/typescript": { "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2966,15 +2484,11 @@ }, "node_modules/undici-types": { "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "dev": true, "license": "MIT" }, "node_modules/unpipe": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "dev": true, "license": "MIT", "engines": { @@ -2983,8 +2497,6 @@ }, "node_modules/vary": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "dev": true, "license": "MIT", "engines": { @@ -2993,8 +2505,6 @@ }, "node_modules/vite": { "version": "8.0.16", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", - "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", "dev": true, "license": "MIT", "dependencies": { @@ -3071,8 +2581,6 @@ }, "node_modules/vitest": { "version": "4.1.8", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.8.tgz", - "integrity": "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==", "dev": true, "license": "MIT", "dependencies": { @@ -3161,8 +2669,6 @@ }, "node_modules/which": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "license": "ISC", "dependencies": { @@ -3177,8 +2683,6 @@ }, "node_modules/why-is-node-running": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, "license": "MIT", "dependencies": { @@ -3194,15 +2698,11 @@ }, "node_modules/wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true, "license": "ISC" }, "node_modules/yaml": { "version": "2.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", "dev": true, "license": "ISC", "bin": { @@ -3217,8 +2717,6 @@ }, "node_modules/zod": { "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "dev": true, "license": "MIT", "funding": { @@ -3227,8 +2725,6 @@ }, "node_modules/zod-to-json-schema": { "version": "3.25.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", - "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", "dev": true, "license": "ISC", "peerDependencies": { diff --git a/test/harness/package.json b/test/harness/package.json index f1556534d0..9b37dfb9d0 100644 --- a/test/harness/package.json +++ b/test/harness/package.json @@ -14,7 +14,6 @@ "node": "^20.19.0 || >=22.12.0" }, "devDependencies": { - "@github/copilot": "^1.0.78", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "@types/node-forge": "^1.3.14", diff --git a/test/harness/replayingCapiProxy.test.ts b/test/harness/replayingCapiProxy.test.ts index c5747a3067..2aa535e798 100644 --- a/test/harness/replayingCapiProxy.test.ts +++ b/test/harness/replayingCapiProxy.test.ts @@ -24,13 +24,21 @@ import { ShellConfig } from "./util"; describe("ReplayingCapiProxy", () => { let tempDir: string; let workDir: string; + let githubActions: string | undefined; beforeEach(async () => { + githubActions = process.env.GITHUB_ACTIONS; + delete process.env.GITHUB_ACTIONS; tempDir = await mkdtemp(path.join(os.tmpdir(), "capi-proxy-test-")); workDir = path.join(tempDir, "work"); }); afterEach(async () => { + if (githubActions === undefined) { + delete process.env.GITHUB_ACTIONS; + } else { + process.env.GITHUB_ACTIONS = githubActions; + } await rm(tempDir, { recursive: true, force: true }); }); @@ -546,9 +554,65 @@ Always include PINEAPPLE_COCONUT_42. expect(toolMessage?.content).toBe("Tool 'report_intent' does not exist."); }); - test("removes runtime advisories from background agent start results", async () => { + test("normalizes interrupted tool execution results", async () => { + const requestBody = JSON.stringify({ + messages: [ + { role: "user", content: "Run a slow analysis" }, + { + role: "assistant", + tool_calls: [ + { + id: "tc1", + type: "function", + function: { + name: "slow_analysis", + arguments: '{"value":"test_abort"}', + }, + }, + { + id: "tc2", + type: "function", + function: { + name: "powershell", + arguments: '{"command":"sleep 100"}', + }, + }, + ], + }, + { + role: "tool", + tool_call_id: "tc1", + content: + 'Failed to execute `slow_analysis` tool with arguments: {"value":"test_abort"} due to error: Error: Session aborted', + }, + { + role: "tool", + tool_call_id: "tc2", + content: "", + }, + ], + }); + const responseBody = JSON.stringify({ + choices: [{ message: { role: "assistant", content: "Done" } }], + }); + + const outputPath = await createProxy([ + { url: "/chat/completions", requestBody, responseBody }, + ]); + + const result = await readYamlOutput(outputPath); + const toolMessages = result.conversations[0].messages.filter( + (m) => m.role === "tool", + ); + expect(toolMessages.map((message) => message.content)).toEqual([ + "The execution of this tool, or a previous tool was interrupted.", + "The execution of this tool, or a previous tool was interrupted.", + ]); + }); + + test("normalizes background agent IDs and removes runtime advisories", async () => { const stableResult = - "Agent started in background with agent_id: read-file. You'll be notified when it completes. Tell the user you're waiting and end your response, or continue unrelated work until notified."; + "Agent started in background with agent_id: background-agent. You'll be notified when it completes. Tell the user you're waiting and end your response, or continue unrelated work until notified."; const requestBody = JSON.stringify({ messages: [ { role: "user", content: "Help me" }, @@ -565,7 +629,8 @@ Always include PINEAPPLE_COCONUT_42. { role: "tool", tool_call_id: "tc1", - content: `${stableResult} The agent supports multi-turn conversations.`, + content: + "Agent started in background with agent_id: 3e0c7565-6091-58cb-85bb-6cb14db23ef7. You'll be notified when it completes. Tell the user you're waiting and end your response, or continue unrelated work until notified. The agent supports multi-turn conversations.", }, ], }); @@ -902,6 +967,119 @@ Always include PINEAPPLE_COCONUT_42. } }); + test("matches semantically equivalent interrupted shell results", async () => { + const originalShellConfig = + process.platform === "win32" + ? ShellConfig.powerShell + : ShellConfig.bash; + const cachePath = path.join(tempDir, "cache.yaml"); + const cacheContent = yaml.stringify({ + models: ["test-model"], + conversations: [ + { + messages: [ + { role: "system", content: "${system}" }, + { role: "user", content: "Run command" }, + { + role: "assistant", + tool_calls: [ + { + id: "toolcall_0", + type: "function", + function: { + name: "${shell}", + arguments: '{"command":"sleep 100"}', + }, + }, + ], + }, + { + role: "tool", + tool_call_id: "toolcall_0", + content: + "The execution of this tool, or a previous tool was interrupted.", + }, + { role: "assistant", content: "Ready for another request." }, + ], + }, + ], + } satisfies NormalizedData); + await writeFile(cachePath, cacheContent); + + const proxy = new ReplayingCapiProxy( + "http://localhost:9999", + cachePath, + workDir, + ); + const proxyUrl = await proxy.start(); + + try { + const messages = [ + { role: "system", content: "System prompt" }, + { role: "user", content: "Run command" }, + { + role: "assistant", + tool_calls: [ + { + id: "runtime-call-id", + type: "function", + function: { + name: originalShellConfig.shellToolName, + arguments: '{"command":"sleep 100"}', + }, + }, + ], + }, + ]; + const interruptedResponse = await makeRequest( + proxyUrl, + "/chat/completions", + { + body: { + model: "test-model", + messages: [ + ...messages, + { + role: "tool", + tool_call_id: "runtime-call-id", + content: + "", + }, + ], + }, + }, + ); + + expect(interruptedResponse.status).toBe(200); + expect( + (JSON.parse(interruptedResponse.body) as ChatCompletion).choices[0] + .message.content, + ).toBe("Ready for another request."); + + const meaningfulErrorResponse = await makeRequest( + proxyUrl, + "/chat/completions", + { + body: { + model: "test-model", + messages: [ + ...messages, + { + role: "tool", + tool_call_id: "runtime-call-id", + content: + "The command failed because the executable was missing.", + }, + ], + }, + }, + ); + expect(meaningfulErrorResponse.status).toBe(500); + } finally { + await proxy.stop(); + } + }); + test("matches available-tools results after the built-in tool set changes", async () => { const cachePath = path.join(tempDir, "cache.yaml"); // Legacy snapshot recorded before write_agent was a built-in tool: the @@ -1161,6 +1339,112 @@ Always include PINEAPPLE_COCONUT_42. } }); + test("replays background agent calls with the runtime-generated ID", async () => { + const cachePath = path.join(tempDir, "cache.yaml"); + const startResult = + "Agent started in background with agent_id: read-file. You'll be notified when it completes. Tell the user you're waiting and end your response, or continue unrelated work until notified."; + const notification = + '\nAgent "read-file" (explore) has completed successfully. Use read_agent with agent_id "read-file" to retrieve the full results.\n'; + const cacheContent = yaml.stringify({ + models: ["test-model"], + conversations: [ + { + messages: [ + { role: "system", content: "${system}" }, + { role: "user", content: "Read the file" }, + { + role: "assistant", + tool_calls: [ + { + id: "toolcall_0", + type: "function", + function: { + name: "task", + arguments: + '{"agent_type":"explore","name":"read-file","prompt":"Read it","mode":"background"}', + }, + }, + ], + }, + { + role: "tool", + tool_call_id: "toolcall_0", + content: startResult, + }, + { role: "assistant", content: "Waiting." }, + { role: "user", content: notification }, + { + role: "assistant", + tool_calls: [ + { + id: "toolcall_1", + type: "function", + function: { + name: "read_agent", + arguments: '{"agent_id":"read-file","wait":true}', + }, + }, + ], + }, + ], + }, + ], + } satisfies NormalizedData); + await writeFile(cachePath, cacheContent); + + const proxy = new ReplayingCapiProxy( + "http://localhost:9999", + cachePath, + workDir, + ); + const proxyUrl = await proxy.start(); + const runtimeAgentId = "3e0c7565-6091-58cb-85bb-6cb14db23ef7"; + + try { + const response = await makeRequest(proxyUrl, "/chat/completions", { + body: { + model: "test-model", + messages: [ + { role: "system", content: "Be helpful" }, + { role: "user", content: "Read the file" }, + { + role: "assistant", + tool_calls: [ + { + id: "runtime-call-id", + type: "function", + function: { + name: "task", + arguments: + '{"agent_type":"explore","name":"read-file","prompt":"Read it","mode":"background"}', + }, + }, + ], + }, + { + role: "tool", + tool_call_id: "runtime-call-id", + content: startResult.replace("read-file", runtimeAgentId), + }, + { role: "assistant", content: "Waiting." }, + { role: "user", content: notification }, + ], + }, + }); + + expect(response.status).toBe(200); + const parsed = JSON.parse(response.body) as ChatCompletion; + const toolCall = parsed.choices[0].message + .tool_calls![0] as ChatCompletionMessageFunctionToolCall; + expect(JSON.parse(toolCall.function.arguments)).toEqual({ + agent_id: runtimeAgentId, + wait: true, + }); + } finally { + await proxy.stop(); + } + }); + test("matches parallel tool results regardless of arrival order", async () => { const cachePath = path.join(tempDir, "cache.yaml"); const cacheContent = yaml.stringify({ @@ -1315,6 +1599,42 @@ Always include PINEAPPLE_COCONUT_42. } }); + test.each([false, true])( + "defaults to Sonnet 5 without stored models (capture exists: %s)", + async (captureExists) => { + const cachePath = path.join(tempDir, "cache.yaml"); + if (captureExists) { + await writeFile( + cachePath, + yaml.stringify({ + models: [], + conversations: [], + } satisfies NormalizedData), + ); + } + + const proxy = new ReplayingCapiProxy( + "http://localhost:9999", + cachePath, + workDir, + ); + const proxyUrl = await proxy.start(); + + try { + const response = await makeRequest(proxyUrl, "/models", { + method: "GET", + }); + expect(response.status).toBe(200); + const parsed = JSON.parse(response.body) as { + data: Array<{ id: string }>; + }; + expect(parsed.data.map((model) => model.id)).toEqual(["claude-sonnet-5"]); + } finally { + await proxy.stop(); + } + }, + ); + test("returns cached models for /models endpoint", async () => { const cachePath = path.join(tempDir, "cache.yaml"); const cacheContent = yaml.stringify({ diff --git a/test/harness/replayingCapiProxy.ts b/test/harness/replayingCapiProxy.ts index 47ebda9f7b..7da04cabc4 100644 --- a/test/harness/replayingCapiProxy.ts +++ b/test/harness/replayingCapiProxy.ts @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ -import { appendFileSync, existsSync } from "fs"; +import { appendFileSync } from "fs"; import { mkdir, readFile, writeFile } from "fs/promises"; import type { ChatCompletion, @@ -26,6 +26,7 @@ import { chatCompletionResponseToAnthropicMessage, chatCompletionResponseToAnthropicSseChunks, } from "./anthropicMessagesAdapter"; +import { canonicalUserMessageSeparator } from "./modelProtocolAdapterShared"; import { chatCompletionResponseToResponsesApiMessage, chatCompletionResponseToResponsesApiSseChunks, @@ -106,7 +107,7 @@ const normalizedToolNames: Record = { * Default model to use when no stored data is available for a given test. * This enables responding to /models without needing to have a capture file. */ -const defaultModel = "claude-sonnet-4.5"; +const defaultModel = "claude-sonnet-5"; /** * An HTTP proxy that not only captures HTTP exchanges, but also stores them in a file on disk and @@ -125,6 +126,7 @@ export class ReplayingCapiProxy extends CapturingHttpProxy { private startPromise: Promise | null = null; private defaultToolResultNormalizers: ToolResultNormalizer[] = [ { toolName: "*", normalizer: normalizeLargeOutputFilepaths }, + { toolName: "*", normalizer: normalizeInterruptedToolResult }, { toolName: "${shell}", normalizer: normalizeShellExitMarkers }, { toolName: "*", normalizer: normalizeGhAuthMessages }, { toolName: "*", normalizer: normalizeAvailableToolNames }, @@ -205,17 +207,30 @@ export class ReplayingCapiProxy extends CapturingHttpProxy { } private async loadStoredData(): Promise { - if (this.state && existsSync(this.state.filePath)) { - const content = await readFile(this.state.filePath, "utf-8"); - this.state.storedData = yaml.parse(content) as NormalizedData; - normalizeToolResultOrder(this.state.storedData.conversations); - normalizeStoredUserMessages(this.state.storedData.conversations); - normalizeStoredToolMessages(this.state.storedData.conversations); - normalizeStoredMessagesForBackend( - this.state.storedData.conversations, - this.state.backend, - ); + if (!this.state) { + return; + } + // Read directly and treat a missing file as "nothing stored" rather than + // checking for existence first, which would be a check-then-use race. + const content = await readFile(this.state.filePath, "utf-8").catch( + (error: NodeJS.ErrnoException) => { + if (error.code === "ENOENT") { + return undefined; + } + throw error; + }, + ); + if (content === undefined) { + return; } + this.state.storedData = yaml.parse(content) as NormalizedData; + normalizeToolResultOrder(this.state.storedData.conversations); + normalizeStoredUserMessages(this.state.storedData.conversations); + normalizeStoredToolMessages(this.state.storedData.conversations); + normalizeStoredMessagesForBackend( + this.state.storedData.conversations, + this.state.backend, + ); } async stop(skipWritingCache?: boolean): Promise { @@ -416,6 +431,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\//)) { @@ -789,6 +849,7 @@ async function findSavedChatCompletionResponse( if (!requestModel) { throw new Error("Unable to determine model from request"); } + const backgroundAgentIds = extractBackgroundAgentIds(requestBody); // Now find a matching cached conversation (i.e., one for which this request is a prefix) for (const conversation of storedData.conversations) { @@ -802,6 +863,7 @@ async function findSavedChatCompletionResponse( conversation.messages, replyIndex, workDir, + backgroundAgentIds, ); } } @@ -938,7 +1000,7 @@ function coalesceAdjacentUserMessages(requestBody: string): string { typeof previous.content === "string" && typeof message.content === "string" ) { - previous.content = `${previous.content.trimEnd()}\n\n\n${message.content.trimStart()}`; + previous.content = `${previous.content.trimEnd()}${canonicalUserMessageSeparator}${message.content.trimStart()}`; } else { messages.push(message); } @@ -948,7 +1010,7 @@ function coalesceAdjacentUserMessages(requestBody: string): string { if (message.role === "user" && typeof message.content === "string") { message.content = normalizeUserMessage(message.content).replace( /\n{5,}/g, - "\n\n\n", + canonicalUserMessageSeparator, ); } } @@ -1023,8 +1085,11 @@ function normalizeToolCalls( // still match cached responses even if these details change. for (const conv of conversations) { const idMap = new Map(); + const backgroundAgentNamesByToolCallId = new Map(); + const backgroundAgentNamesByRuntimeId = new Map(); const precedingMessages: NormalizedMessage[] = []; let counter = 0; + let unnamedBackgroundAgentCounter = 0; for (const msg of conv.messages) { for (const tc of msg.tool_calls ?? []) { // Normalize ID in tool calls @@ -1037,6 +1102,29 @@ function normalizeToolCalls( if (normalizedToolName) { tc.function!.name = normalizedToolName; } + + if (tc.function?.name === "task") { + const configuredName = getBackgroundAgentName( + tc.function.arguments, + ); + const fallbackName = + unnamedBackgroundAgentCounter === 0 + ? "background-agent" + : `background-agent-${unnamedBackgroundAgentCounter}`; + backgroundAgentNamesByToolCallId.set( + tc.id, + configuredName ?? fallbackName, + ); + unnamedBackgroundAgentCounter++; + } else if ( + tc.function?.name === "read_agent" || + tc.function?.name === "write_agent" + ) { + tc.function.arguments = normalizeBackgroundAgentArguments( + tc.function.arguments, + backgroundAgentNamesByRuntimeId, + ); + } } if (msg.role === "tool" && msg.tool_call_id) { @@ -1049,6 +1137,19 @@ function normalizeToolCalls( .flatMap((m) => m.tool_calls ?? []) .find((tc) => tc.id === msg.tool_call_id); if (precedingToolCall) { + if (precedingToolCall.function?.name === "task") { + const runtimeId = getBackgroundAgentId(msg.content); + const stableName = backgroundAgentNamesByToolCallId.get( + msg.tool_call_id, + ); + if (runtimeId && stableName) { + backgroundAgentNamesByRuntimeId.set(runtimeId, stableName); + msg.content = normalizeBackgroundAgentStartMessageWithId( + msg.content, + stableName, + ); + } + } for (const normalizer of resultNormalizers) { if ( precedingToolCall.function?.name === normalizer.toolName || @@ -1057,6 +1158,10 @@ function normalizeToolCalls( msg.content = normalizer.normalizer(msg.content); } } + msg.content = replaceBackgroundAgentIds( + msg.content, + backgroundAgentNamesByRuntimeId, + ); } } } @@ -1303,7 +1408,8 @@ function coalesceMessages( continue; } - const separator = message.role === "user" ? "\n\n\n" : ""; + const separator = + message.role === "user" ? canonicalUserMessageSeparator : ""; const previousContent = previous.content ?? ""; const currentContent = message.content ?? ""; const content = `${previousContent}${previousContent && currentContent ? separator : ""}${currentContent}`; @@ -1324,6 +1430,7 @@ function normalizeStoredToolMessages(conversations: NormalizedConversation[]) { for (const conversation of conversations) { for (const message of conversation.messages) { if (message.role === "tool" && typeof message.content === "string") { + message.content = normalizeInterruptedToolResult(message.content); message.content = normalizeAvailableToolNames(message.content); message.content = normalizeBackgroundAgentStartMessage(message.content); message.content = normalizeReadAgentResult(message.content); @@ -1447,13 +1554,192 @@ function normalizeAvailableToolNames(result: string): string { ); } +function normalizeInterruptedToolResult(result: string): string { + return result.replace( + /^(?:Failed to execute `[^`]+` tool(?: with arguments: [\s\S]*?)? due to error: (?:Error: )?Session aborted|)$/, + "The execution of this tool, or a previous tool was interrupted.", + ); +} + function normalizeBackgroundAgentStartMessage(result: string): string { + return normalizeBackgroundAgentStartMessageWithId(result); +} + +function normalizeBackgroundAgentStartMessageWithId( + result: string, + stableId?: string, +): string { return result.replace( - /^(Agent started in background with agent_id: .*?\. You'll be notified when it completes\. Tell the user you're waiting and end your response, or continue unrelated work until notified\.).*$/s, - "$1", + /^Agent started in background with agent_id: (.*?)(\. You'll be notified when it completes\. Tell the user you're waiting and end your response, or continue unrelated work until notified\.).*$/s, + (_match, runtimeId: string, suffix: string) => + `Agent started in background with agent_id: ${stableId ?? runtimeId}${suffix}`, ); } +function getBackgroundAgentName(argumentsJson: string): string | undefined { + try { + const args = JSON.parse(argumentsJson) as unknown; + if ( + args && + typeof args === "object" && + "name" in args && + typeof args.name === "string" + ) { + return args.name; + } + } catch { + // Invalid tool arguments are left for the runtime to report. + } + return undefined; +} + +function getBackgroundAgentId(content: string): string | undefined { + const match = content.match( + /^Agent started in background with agent_id: (.*?)\. You'll be notified when it completes\./s, + ); + if (match?.[1]) { + return match[1]; + } + + try { + const parsed = JSON.parse(content) as unknown; + if ( + parsed && + typeof parsed === "object" && + "textResultForLlm" in parsed && + typeof parsed.textResultForLlm === "string" + ) { + return getBackgroundAgentId(parsed.textResultForLlm); + } + } catch { + // Plain-text tool results are expected. + } + return undefined; +} + +function replaceBackgroundAgentIds( + content: string, + backgroundAgentNamesByRuntimeId: Map, +): string { + let normalized = content; + for (const [runtimeId, stableName] of backgroundAgentNamesByRuntimeId) { + normalized = normalized.split(runtimeId).join(stableName); + } + return normalized; +} + +function rewriteBackgroundAgentArguments( + argumentsJson: string, + replacements: Map, +): string { + try { + const args = JSON.parse(argumentsJson) as unknown; + if (!args || typeof args !== "object") { + return argumentsJson; + } + + if ( + "agent_id" in args && + typeof args.agent_id === "string" && + replacements.has(args.agent_id) + ) { + args.agent_id = replacements.get(args.agent_id); + } + if ("agent_ids" in args && Array.isArray(args.agent_ids)) { + args.agent_ids = args.agent_ids.map((agentId: unknown) => + typeof agentId === "string" + ? (replacements.get(agentId) ?? agentId) + : agentId, + ); + } + return JSON.stringify(args); + } catch { + return argumentsJson; + } +} + +function normalizeBackgroundAgentArguments( + argumentsJson: string, + backgroundAgentNamesByRuntimeId: Map, +): string { + return rewriteBackgroundAgentArguments( + argumentsJson, + backgroundAgentNamesByRuntimeId, + ); +} + +function extractBackgroundAgentIds( + requestBody: string | undefined, +): Map { + const result = new Map(); + if (!requestBody) { + return result; + } + + try { + const request = JSON.parse(requestBody) as { + messages?: Array<{ + role?: string; + tool_call_id?: string; + content?: unknown; + tool_calls?: Array<{ + id?: string; + function?: { name?: string; arguments?: string }; + }>; + }>; + }; + const backgroundAgentNamesByToolCallId = new Map(); + let unnamedBackgroundAgentCounter = 0; + for (const message of request.messages ?? []) { + for (const toolCall of message.tool_calls ?? []) { + if ( + toolCall.id && + toolCall.function?.name === "task" && + toolCall.function.arguments + ) { + const configuredName = getBackgroundAgentName( + toolCall.function.arguments, + ); + const fallbackName = + unnamedBackgroundAgentCounter === 0 + ? "background-agent" + : `background-agent-${unnamedBackgroundAgentCounter}`; + backgroundAgentNamesByToolCallId.set( + toolCall.id, + configuredName ?? fallbackName, + ); + unnamedBackgroundAgentCounter++; + } + } + + if ( + message.role === "tool" && + message.tool_call_id && + typeof message.content === "string" + ) { + const stableName = backgroundAgentNamesByToolCallId.get( + message.tool_call_id, + ); + const runtimeId = getBackgroundAgentId(message.content); + if (stableName && runtimeId) { + result.set(stableName, runtimeId); + } + } + } + } catch { + // Request parsing failures are handled by the normal replay matcher. + } + + return result; +} + +function expandBackgroundAgentArguments( + argumentsJson: string, + backgroundAgentIds: Map, +): string { + return rewriteBackgroundAgentArguments(argumentsJson, backgroundAgentIds); +} + // Transforms a single OpenAI-style inbound response message into normalized form function transformOpenAIResponseChoice( choices: ChatCompletion.Choice[], @@ -1605,6 +1891,7 @@ function createOpenAIResponse( messages: NormalizedMessage[], responseStartIndex: number, workDir: string, + backgroundAgentIds: Map, ): ChatCompletion { // Here we recreate the strange CAPI/productcode behavior of using multiple choices to represent // multiple assistant messages in a row. This is the inverse of the logic in transformOpenAIResponseChoice(). @@ -1621,7 +1908,10 @@ function createOpenAIResponse( type: "function" as const, function: { name: expandToolName(tc.function?.name ?? ""), - arguments: expandWorkDir(tc.function?.arguments, workDir, true) ?? "{}", + arguments: expandBackgroundAgentArguments( + expandWorkDir(tc.function?.arguments, workDir, true) ?? "{}", + backgroundAgentIds, + ), }, })); @@ -1735,11 +2025,18 @@ function createGetModelsResponse(modelIds: string[]) { } async function writeFileIfDifferent(filePath: string, contents: string) { - if (existsSync(filePath)) { - const existingContents = await readFile(filePath, "utf-8"); - if (existingContents === contents) { - return; - } + // Read directly instead of checking for existence first, which would be a + // check-then-use race on the same path. + const existingContents = await readFile(filePath, "utf-8").catch( + (error: NodeJS.ErrnoException) => { + if (error.code === "ENOENT") { + return undefined; + } + throw error; + }, + ); + if (existingContents === contents) { + return; } await writeFile(filePath, contents, "utf-8"); diff --git a/test/harness/test-mcp-server.mjs b/test/harness/test-mcp-server.mjs index b2b32606dc..a3a84b42b3 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/abort/should_abort_during_active_streaming.yaml b/test/snapshots/abort/should_abort_during_active_streaming.yaml index 70981ee597..8556fec349 100644 --- a/test/snapshots/abort/should_abort_during_active_streaming.yaml +++ b/test/snapshots/abort/should_abort_during_active_streaming.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/abort/should_abort_during_active_tool_execution.yaml b/test/snapshots/abort/should_abort_during_active_tool_execution.yaml index 99ea89f7b0..a975cae284 100644 --- a/test/snapshots/abort/should_abort_during_active_tool_execution.yaml +++ b/test/snapshots/abort/should_abort_during_active_tool_execution.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/agent_and_compact_rpc/should_compact_session_history_after_messages.yaml b/test/snapshots/agent_and_compact_rpc/should_compact_session_history_after_messages.yaml index ac5cc94336..498bfebad0 100644 --- a/test/snapshots/agent_and_compact_rpc/should_compact_session_history_after_messages.yaml +++ b/test/snapshots/agent_and_compact_rpc/should_compact_session_history_after_messages.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/ask_user/ask_user_does_not_block_sibling_tool_call_in_same_turn.yaml b/test/snapshots/ask_user/ask_user_does_not_block_sibling_tool_call_in_same_turn.yaml index 4ba16d4d81..1624cb5317 100644 --- a/test/snapshots/ask_user/ask_user_does_not_block_sibling_tool_call_in_same_turn.yaml +++ b/test/snapshots/ask_user/ask_user_does_not_block_sibling_tool_call_in_same_turn.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/ask_user/should_handle_freeform_user_input_response.yaml b/test/snapshots/ask_user/should_handle_freeform_user_input_response.yaml index 49944c9732..c33ce7e8f2 100644 --- a/test/snapshots/ask_user/should_handle_freeform_user_input_response.yaml +++ b/test/snapshots/ask_user/should_handle_freeform_user_input_response.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/ask_user/should_invoke_user_input_handler_when_model_uses_ask_user_tool.yaml b/test/snapshots/ask_user/should_invoke_user_input_handler_when_model_uses_ask_user_tool.yaml index 4549b99dc1..417f0b3446 100644 --- a/test/snapshots/ask_user/should_invoke_user_input_handler_when_model_uses_ask_user_tool.yaml +++ b/test/snapshots/ask_user/should_invoke_user_input_handler_when_model_uses_ask_user_tool.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/ask_user/should_receive_choices_in_user_input_request.yaml b/test/snapshots/ask_user/should_receive_choices_in_user_input_request.yaml index 705378061f..97e08e5852 100644 --- a/test/snapshots/ask_user/should_receive_choices_in_user_input_request.yaml +++ b/test/snapshots/ask_user/should_receive_choices_in_user_input_request.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/builtin_tools/should_capture_exit_code_in_output.yaml b/test/snapshots/builtin_tools/should_capture_exit_code_in_output.yaml index 01cf1298d3..5c3c638d50 100644 --- a/test/snapshots/builtin_tools/should_capture_exit_code_in_output.yaml +++ b/test/snapshots/builtin_tools/should_capture_exit_code_in_output.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/builtin_tools/should_capture_stderr_output.yaml b/test/snapshots/builtin_tools/should_capture_stderr_output.yaml index 0ba318148d..ba0bd164ea 100644 --- a/test/snapshots/builtin_tools/should_capture_stderr_output.yaml +++ b/test/snapshots/builtin_tools/should_capture_stderr_output.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/builtin_tools/should_create_a_new_file.yaml b/test/snapshots/builtin_tools/should_create_a_new_file.yaml index bf9288cf01..869777e9a4 100644 --- a/test/snapshots/builtin_tools/should_create_a_new_file.yaml +++ b/test/snapshots/builtin_tools/should_create_a_new_file.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system @@ -54,6 +54,6 @@ conversations: arguments: '{"path":"${workdir}/new_file.txt"}' - role: tool tool_call_id: toolcall_2 - content: 1. Created by test + content: Created by test - role: assistant content: ✓ Done! Created `new_file.txt` with content "Created by test" and confirmed the content matches. diff --git a/test/snapshots/builtin_tools/should_edit_a_file_successfully.yaml b/test/snapshots/builtin_tools/should_edit_a_file_successfully.yaml index 0f21418628..922d7751dd 100644 --- a/test/snapshots/builtin_tools/should_edit_a_file_successfully.yaml +++ b/test/snapshots/builtin_tools/should_edit_a_file_successfully.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system @@ -57,9 +57,8 @@ conversations: - role: tool tool_call_id: toolcall_2 content: |- - 1. Hi Universe - 2. Goodbye World - 3. + Hi Universe + Goodbye World - role: assistant content: |- Done! The file now contains: diff --git a/test/snapshots/builtin_tools/should_find_files_by_pattern.yaml b/test/snapshots/builtin_tools/should_find_files_by_pattern.yaml index 6cf85ea51d..338dd03ef2 100644 --- a/test/snapshots/builtin_tools/should_find_files_by_pattern.yaml +++ b/test/snapshots/builtin_tools/should_find_files_by_pattern.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/builtin_tools/should_handle_nonexistent_file_gracefully.yaml b/test/snapshots/builtin_tools/should_handle_nonexistent_file_gracefully.yaml index c5c00fb65c..410da4e089 100644 --- a/test/snapshots/builtin_tools/should_handle_nonexistent_file_gracefully.yaml +++ b/test/snapshots/builtin_tools/should_handle_nonexistent_file_gracefully.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/builtin_tools/should_read_file_with_line_range.yaml b/test/snapshots/builtin_tools/should_read_file_with_line_range.yaml index cd17d86708..23a7fec7a1 100644 --- a/test/snapshots/builtin_tools/should_read_file_with_line_range.yaml +++ b/test/snapshots/builtin_tools/should_read_file_with_line_range.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system @@ -45,9 +45,9 @@ conversations: - role: tool tool_call_id: toolcall_1 content: |- - 2. line2 - 3. line3 - 4. line4 + line2 + line3 + line4 - role: assistant content: |- Lines 2 through 4 of 'lines.txt' contain: diff --git a/test/snapshots/builtin_tools/should_search_for_patterns_in_files.yaml b/test/snapshots/builtin_tools/should_search_for_patterns_in_files.yaml index f0af500b6e..615b9ae39e 100644 --- a/test/snapshots/builtin_tools/should_search_for_patterns_in_files.yaml +++ b/test/snapshots/builtin_tools/should_search_for_patterns_in_files.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/canvas/canvas_list_discovers_declared_canvases.yaml b/test/snapshots/canvas/canvas_list_discovers_declared_canvases.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/canvas/canvas_list_discovers_declared_canvases.yaml +++ b/test/snapshots/canvas/canvas_list_discovers_declared_canvases.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/client/listmodels_withcustomhandler_callshandler.yaml b/test/snapshots/client/listmodels_withcustomhandler_callshandler.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/client/listmodels_withcustomhandler_callshandler.yaml +++ b/test/snapshots/client/listmodels_withcustomhandler_callshandler.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/client/should_force_stop_client.yaml b/test/snapshots/client/should_force_stop_client.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/client/should_force_stop_client.yaml +++ b/test/snapshots/client/should_force_stop_client.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/client/should_get_authenticated_status.yaml b/test/snapshots/client/should_get_authenticated_status.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/client/should_get_authenticated_status.yaml +++ b/test/snapshots/client/should_get_authenticated_status.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/client/should_get_status.yaml b/test/snapshots/client/should_get_status.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/client/should_get_status.yaml +++ b/test/snapshots/client/should_get_status.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/client/should_list_models_when_authenticated.yaml b/test/snapshots/client/should_list_models_when_authenticated.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/client/should_list_models_when_authenticated.yaml +++ b/test/snapshots/client/should_list_models_when_authenticated.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/client/should_start_ping_and_stop_stdio_client.yaml b/test/snapshots/client/should_start_ping_and_stop_stdio_client.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/client/should_start_ping_and_stop_stdio_client.yaml +++ b/test/snapshots/client/should_start_ping_and_stop_stdio_client.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/client/should_start_ping_and_stop_tcp_client.yaml b/test/snapshots/client/should_start_ping_and_stop_tcp_client.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/client/should_start_ping_and_stop_tcp_client.yaml +++ b/test/snapshots/client/should_start_ping_and_stop_tcp_client.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/client/should_stop_client_with_active_session.yaml b/test/snapshots/client/should_stop_client_with_active_session.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/client/should_stop_client_with_active_session.yaml +++ b/test/snapshots/client/should_stop_client_with_active_session.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/client_api/should_delete_session_by_id.yaml b/test/snapshots/client_api/should_delete_session_by_id.yaml index 0981462bf6..bfeaca5f6d 100644 --- a/test/snapshots/client_api/should_delete_session_by_id.yaml +++ b/test/snapshots/client_api/should_delete_session_by_id.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/client_api/should_track_last_session_id_after_session_created.yaml b/test/snapshots/client_api/should_track_last_session_id_after_session_created.yaml index 8486832a46..8e3aa9d94c 100644 --- a/test/snapshots/client_api/should_track_last_session_id_after_session_created.yaml +++ b/test/snapshots/client_api/should_track_last_session_id_after_session_created.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/client_lifecycle/should_emit_session_lifecycle_events.yaml b/test/snapshots/client_lifecycle/should_emit_session_lifecycle_events.yaml index beb8b443d2..3569a8ca8e 100644 --- a/test/snapshots/client_lifecycle/should_emit_session_lifecycle_events.yaml +++ b/test/snapshots/client_lifecycle/should_emit_session_lifecycle_events.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/client_lifecycle/should_receive_session_deleted_lifecycle_event_when_deleted.yaml b/test/snapshots/client_lifecycle/should_receive_session_deleted_lifecycle_event_when_deleted.yaml index 4419c5854e..bb4a148072 100644 --- a/test/snapshots/client_lifecycle/should_receive_session_deleted_lifecycle_event_when_deleted.yaml +++ b/test/snapshots/client_lifecycle/should_receive_session_deleted_lifecycle_event_when_deleted.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/client_lifecycle/should_return_last_session_id_after_sending_a_message.yaml b/test/snapshots/client_lifecycle/should_return_last_session_id_after_sending_a_message.yaml index 3b9da534c2..62da2b03a2 100644 --- a/test/snapshots/client_lifecycle/should_return_last_session_id_after_sending_a_message.yaml +++ b/test/snapshots/client_lifecycle/should_return_last_session_id_after_sending_a_message.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/client_options/should_listen_on_configured_tcp_port.yaml b/test/snapshots/client_options/should_listen_on_configured_tcp_port.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/client_options/should_listen_on_configured_tcp_port.yaml +++ b/test/snapshots/client_options/should_listen_on_configured_tcp_port.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/client_options/should_use_client_cwd_for_default_workingdirectory.yaml b/test/snapshots/client_options/should_use_client_cwd_for_default_workingdirectory.yaml index 6d9167e94a..c87d0cb124 100644 --- a/test/snapshots/client_options/should_use_client_cwd_for_default_workingdirectory.yaml +++ b/test/snapshots/client_options/should_use_client_cwd_for_default_workingdirectory.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system @@ -25,6 +25,6 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. I am in the client cwd + content: I am in the client cwd - role: assistant content: 'The file `marker.txt` says: "I am in the client cwd"' diff --git a/test/snapshots/combinedconfiguration/accept_mcp_servers_and_custom_agents.yaml b/test/snapshots/combinedconfiguration/accept_mcp_servers_and_custom_agents.yaml index 60d1eadeaf..51716b5c80 100644 --- a/test/snapshots/combinedconfiguration/accept_mcp_servers_and_custom_agents.yaml +++ b/test/snapshots/combinedconfiguration/accept_mcp_servers_and_custom_agents.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/commands/session_with_commands_creates_successfully.yaml b/test/snapshots/commands/session_with_commands_creates_successfully.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/commands/session_with_commands_creates_successfully.yaml +++ b/test/snapshots/commands/session_with_commands_creates_successfully.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/commands/session_with_commands_resumes_successfully.yaml b/test/snapshots/commands/session_with_commands_resumes_successfully.yaml index 0981462bf6..bfeaca5f6d 100644 --- a/test/snapshots/commands/session_with_commands_resumes_successfully.yaml +++ b/test/snapshots/commands/session_with_commands_resumes_successfully.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/commands/session_with_no_commands_creates_successfully.yaml b/test/snapshots/commands/session_with_no_commands_creates_successfully.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/commands/session_with_no_commands_creates_successfully.yaml +++ b/test/snapshots/commands/session_with_no_commands_creates_successfully.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/compaction/should_not_emit_compaction_events_when_infinite_sessions_disabled.yaml b/test/snapshots/compaction/should_not_emit_compaction_events_when_infinite_sessions_disabled.yaml index 9773a132f5..6d966efe2f 100644 --- a/test/snapshots/compaction/should_not_emit_compaction_events_when_infinite_sessions_disabled.yaml +++ b/test/snapshots/compaction/should_not_emit_compaction_events_when_infinite_sessions_disabled.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/compaction/should_trigger_compaction_with_low_threshold_and_emit_events.yaml b/test/snapshots/compaction/should_trigger_compaction_with_low_threshold_and_emit_events.yaml index 9deca12228..7d476ec66c 100644 --- a/test/snapshots/compaction/should_trigger_compaction_with_low_threshold_and_emit_events.yaml +++ b/test/snapshots/compaction/should_trigger_compaction_with_low_threshold_and_emit_events.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/customagents/accept_custom_agent_config_on_create.yaml b/test/snapshots/customagents/accept_custom_agent_config_on_create.yaml index 56da15bae1..dbf50977bc 100644 --- a/test/snapshots/customagents/accept_custom_agent_config_on_create.yaml +++ b/test/snapshots/customagents/accept_custom_agent_config_on_create.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/customagents/accept_custom_agent_config_on_resume.yaml b/test/snapshots/customagents/accept_custom_agent_config_on_resume.yaml index 16db486e88..e454b1e96c 100644 --- a/test/snapshots/customagents/accept_custom_agent_config_on_resume.yaml +++ b/test/snapshots/customagents/accept_custom_agent_config_on_resume.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/elicitation/confirm_returns_false_when_handler_declines.yaml b/test/snapshots/elicitation/confirm_returns_false_when_handler_declines.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/elicitation/confirm_returns_false_when_handler_declines.yaml +++ b/test/snapshots/elicitation/confirm_returns_false_when_handler_declines.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/elicitation/confirm_returns_true_when_handler_accepts.yaml b/test/snapshots/elicitation/confirm_returns_true_when_handler_accepts.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/elicitation/confirm_returns_true_when_handler_accepts.yaml +++ b/test/snapshots/elicitation/confirm_returns_true_when_handler_accepts.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/elicitation/defaults_capabilities_when_not_provided.yaml b/test/snapshots/elicitation/defaults_capabilities_when_not_provided.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/elicitation/defaults_capabilities_when_not_provided.yaml +++ b/test/snapshots/elicitation/defaults_capabilities_when_not_provided.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/elicitation/elicitation_returns_all_action_shapes.yaml b/test/snapshots/elicitation/elicitation_returns_all_action_shapes.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/elicitation/elicitation_returns_all_action_shapes.yaml +++ b/test/snapshots/elicitation/elicitation_returns_all_action_shapes.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/elicitation/elicitation_throws_when_capability_is_missing.yaml b/test/snapshots/elicitation/elicitation_throws_when_capability_is_missing.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/elicitation/elicitation_throws_when_capability_is_missing.yaml +++ b/test/snapshots/elicitation/elicitation_throws_when_capability_is_missing.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/elicitation/input_returns_freeform_value.yaml b/test/snapshots/elicitation/input_returns_freeform_value.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/elicitation/input_returns_freeform_value.yaml +++ b/test/snapshots/elicitation/input_returns_freeform_value.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/elicitation/select_returns_selected_option.yaml b/test/snapshots/elicitation/select_returns_selected_option.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/elicitation/select_returns_selected_option.yaml +++ b/test/snapshots/elicitation/select_returns_selected_option.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/elicitation/sends_requestelicitation_when_handler_provided.yaml b/test/snapshots/elicitation/sends_requestelicitation_when_handler_provided.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/elicitation/sends_requestelicitation_when_handler_provided.yaml +++ b/test/snapshots/elicitation/sends_requestelicitation_when_handler_provided.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/elicitation/session_without_elicitationhandler_creates_successfully.yaml b/test/snapshots/elicitation/session_without_elicitationhandler_creates_successfully.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/elicitation/session_without_elicitationhandler_creates_successfully.yaml +++ b/test/snapshots/elicitation/session_without_elicitationhandler_creates_successfully.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/elicitation/should_report_elicitation_capability_based_on_handler_presence.yaml b/test/snapshots/elicitation/should_report_elicitation_capability_based_on_handler_presence.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/elicitation/should_report_elicitation_capability_based_on_handler_presence.yaml +++ b/test/snapshots/elicitation/should_report_elicitation_capability_based_on_handler_presence.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/event_fidelity/should_emit_assistant_message_with_messageid.yaml b/test/snapshots/event_fidelity/should_emit_assistant_message_with_messageid.yaml index caac261e2a..1499c083ca 100644 --- a/test/snapshots/event_fidelity/should_emit_assistant_message_with_messageid.yaml +++ b/test/snapshots/event_fidelity/should_emit_assistant_message_with_messageid.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/event_fidelity/should_emit_assistant_usage_event_after_model_call.yaml b/test/snapshots/event_fidelity/should_emit_assistant_usage_event_after_model_call.yaml index 48667da723..af30539431 100644 --- a/test/snapshots/event_fidelity/should_emit_assistant_usage_event_after_model_call.yaml +++ b/test/snapshots/event_fidelity/should_emit_assistant_usage_event_after_model_call.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/event_fidelity/should_emit_events_in_correct_order_for_tool_using_conversation.yaml b/test/snapshots/event_fidelity/should_emit_events_in_correct_order_for_tool_using_conversation.yaml index 8ce730f0fb..726cbf9322 100644 --- a/test/snapshots/event_fidelity/should_emit_events_in_correct_order_for_tool_using_conversation.yaml +++ b/test/snapshots/event_fidelity/should_emit_events_in_correct_order_for_tool_using_conversation.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system @@ -44,7 +44,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. Hello World + content: Hello World - role: assistant content: |- The file 'hello.txt' contains: diff --git a/test/snapshots/event_fidelity/should_emit_pending_messages_modified_event_when_message_queue_changes.yaml b/test/snapshots/event_fidelity/should_emit_pending_messages_modified_event_when_message_queue_changes.yaml index ecc10bdbd6..2b24bf8a50 100644 --- a/test/snapshots/event_fidelity/should_emit_pending_messages_modified_event_when_message_queue_changes.yaml +++ b/test/snapshots/event_fidelity/should_emit_pending_messages_modified_event_when_message_queue_changes.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/event_fidelity/should_emit_session_usage_info_event_after_model_call.yaml b/test/snapshots/event_fidelity/should_emit_session_usage_info_event_after_model_call.yaml index 48667da723..af30539431 100644 --- a/test/snapshots/event_fidelity/should_emit_session_usage_info_event_after_model_call.yaml +++ b/test/snapshots/event_fidelity/should_emit_session_usage_info_event_after_model_call.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/event_fidelity/should_emit_tool_execution_events_with_correct_fields.yaml b/test/snapshots/event_fidelity/should_emit_tool_execution_events_with_correct_fields.yaml index a6583a15ec..717f09ea24 100644 --- a/test/snapshots/event_fidelity/should_emit_tool_execution_events_with_correct_fields.yaml +++ b/test/snapshots/event_fidelity/should_emit_tool_execution_events_with_correct_fields.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system @@ -44,7 +44,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. test data + content: test data - role: assistant content: |- The file `data.txt` contains: diff --git a/test/snapshots/event_fidelity/should_include_valid_fields_on_all_events.yaml b/test/snapshots/event_fidelity/should_include_valid_fields_on_all_events.yaml index 48667da723..af30539431 100644 --- a/test/snapshots/event_fidelity/should_include_valid_fields_on_all_events.yaml +++ b/test/snapshots/event_fidelity/should_include_valid_fields_on_all_events.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/event_fidelity/should_preserve_message_order_in_getmessages_after_tool_use.yaml b/test/snapshots/event_fidelity/should_preserve_message_order_in_getmessages_after_tool_use.yaml index 2799cdec61..6fcaebc6a0 100644 --- a/test/snapshots/event_fidelity/should_preserve_message_order_in_getmessages_after_tool_use.yaml +++ b/test/snapshots/event_fidelity/should_preserve_message_order_in_getmessages_after_tool_use.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system @@ -15,6 +15,6 @@ conversations: arguments: '{"path":"order.txt"}' - role: tool tool_call_id: toolcall_0 - content: 1. ORDER_CONTENT_42 + content: ORDER_CONTENT_42 - role: assistant content: The number in 'order.txt' is **42**. diff --git a/test/snapshots/hooks/deny_tool_execution_when_pre_tool_use_returns_deny.yaml b/test/snapshots/hooks/deny_tool_execution_when_pre_tool_use_returns_deny.yaml index 10bca8e4b0..296ee0db9a 100644 --- a/test/snapshots/hooks/deny_tool_execution_when_pre_tool_use_returns_deny.yaml +++ b/test/snapshots/hooks/deny_tool_execution_when_pre_tool_use_returns_deny.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/hooks/invoke_both_hooks_for_single_tool_call.yaml b/test/snapshots/hooks/invoke_both_hooks_for_single_tool_call.yaml index 6a51857ab0..10e3a06189 100644 --- a/test/snapshots/hooks/invoke_both_hooks_for_single_tool_call.yaml +++ b/test/snapshots/hooks/invoke_both_hooks_for_single_tool_call.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system @@ -44,7 +44,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. Testing both hooks! + content: Testing both hooks! - role: assistant content: |- The file **both.txt** contains: diff --git a/test/snapshots/hooks/invoke_post_tool_use_hook_after_model_runs_a_tool.yaml b/test/snapshots/hooks/invoke_post_tool_use_hook_after_model_runs_a_tool.yaml index 18b324f098..3bf5ee1f4d 100644 --- a/test/snapshots/hooks/invoke_post_tool_use_hook_after_model_runs_a_tool.yaml +++ b/test/snapshots/hooks/invoke_post_tool_use_hook_after_model_runs_a_tool.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system @@ -44,7 +44,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. World from the test! + content: World from the test! - role: assistant content: |- The file `world.txt` contains: diff --git a/test/snapshots/hooks/invoke_pre_tool_use_hook_when_model_runs_a_tool.yaml b/test/snapshots/hooks/invoke_pre_tool_use_hook_when_model_runs_a_tool.yaml index 1ce0fe67a0..86c2865756 100644 --- a/test/snapshots/hooks/invoke_pre_tool_use_hook_when_model_runs_a_tool.yaml +++ b/test/snapshots/hooks/invoke_pre_tool_use_hook_when_model_runs_a_tool.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system @@ -44,7 +44,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. Hello from the test! + content: Hello from the test! - role: assistant content: |- The file **hello.txt** contains: diff --git a/test/snapshots/hooks/should_deny_tool_execution_when_pretooluse_returns_deny.yaml b/test/snapshots/hooks/should_deny_tool_execution_when_pretooluse_returns_deny.yaml index 2860f52c15..a1ecaef30e 100644 --- a/test/snapshots/hooks/should_deny_tool_execution_when_pretooluse_returns_deny.yaml +++ b/test/snapshots/hooks/should_deny_tool_execution_when_pretooluse_returns_deny.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/hooks/should_invoke_both_pretooluse_and_posttooluse_hooks_for_a_single_tool_call.yaml b/test/snapshots/hooks/should_invoke_both_pretooluse_and_posttooluse_hooks_for_a_single_tool_call.yaml index 6a51857ab0..10e3a06189 100644 --- a/test/snapshots/hooks/should_invoke_both_pretooluse_and_posttooluse_hooks_for_a_single_tool_call.yaml +++ b/test/snapshots/hooks/should_invoke_both_pretooluse_and_posttooluse_hooks_for_a_single_tool_call.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system @@ -44,7 +44,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. Testing both hooks! + content: Testing both hooks! - role: assistant content: |- The file **both.txt** contains: diff --git a/test/snapshots/hooks/should_invoke_both_pretooluse_and_posttooluse_hooks_for_single_tool_call.yaml b/test/snapshots/hooks/should_invoke_both_pretooluse_and_posttooluse_hooks_for_single_tool_call.yaml index 6a51857ab0..10e3a06189 100644 --- a/test/snapshots/hooks/should_invoke_both_pretooluse_and_posttooluse_hooks_for_single_tool_call.yaml +++ b/test/snapshots/hooks/should_invoke_both_pretooluse_and_posttooluse_hooks_for_single_tool_call.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system @@ -44,7 +44,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. Testing both hooks! + content: Testing both hooks! - role: assistant content: |- The file **both.txt** contains: diff --git a/test/snapshots/hooks/should_invoke_posttooluse_hook_after_model_runs_a_tool.yaml b/test/snapshots/hooks/should_invoke_posttooluse_hook_after_model_runs_a_tool.yaml index 18b324f098..3bf5ee1f4d 100644 --- a/test/snapshots/hooks/should_invoke_posttooluse_hook_after_model_runs_a_tool.yaml +++ b/test/snapshots/hooks/should_invoke_posttooluse_hook_after_model_runs_a_tool.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system @@ -44,7 +44,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. World from the test! + content: World from the test! - role: assistant content: |- The file `world.txt` contains: diff --git a/test/snapshots/hooks/should_invoke_pretooluse_hook_when_model_runs_a_tool.yaml b/test/snapshots/hooks/should_invoke_pretooluse_hook_when_model_runs_a_tool.yaml index 1ce0fe67a0..86c2865756 100644 --- a/test/snapshots/hooks/should_invoke_pretooluse_hook_when_model_runs_a_tool.yaml +++ b/test/snapshots/hooks/should_invoke_pretooluse_hook_when_model_runs_a_tool.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system @@ -44,7 +44,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. Hello from the test! + content: Hello from the test! - role: assistant content: |- The file **hello.txt** contains: diff --git a/test/snapshots/hooks_extended/should_allow_posttooluse_to_return_modifiedresult.yaml b/test/snapshots/hooks_extended/should_allow_posttooluse_to_return_modifiedresult.yaml index a340e9326c..f5cbaff9c6 100644 --- a/test/snapshots/hooks_extended/should_allow_posttooluse_to_return_modifiedresult.yaml +++ b/test/snapshots/hooks_extended/should_allow_posttooluse_to_return_modifiedresult.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/hooks_extended/should_allow_pretooluse_to_return_modifiedargs_and_suppressoutput.yaml b/test/snapshots/hooks_extended/should_allow_pretooluse_to_return_modifiedargs_and_suppressoutput.yaml index 8415fe771b..89c3ee9234 100644 --- a/test/snapshots/hooks_extended/should_allow_pretooluse_to_return_modifiedargs_and_suppressoutput.yaml +++ b/test/snapshots/hooks_extended/should_allow_pretooluse_to_return_modifiedargs_and_suppressoutput.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system 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 index 6485670a1c..6c1a055c04 100644 --- 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 @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/hooks_extended/should_invoke_onerroroccurred_hook_when_error_occurs.yaml b/test/snapshots/hooks_extended/should_invoke_onerroroccurred_hook_when_error_occurs.yaml index dcd2f32be2..2a17b8a0a3 100644 --- a/test/snapshots/hooks_extended/should_invoke_onerroroccurred_hook_when_error_occurs.yaml +++ b/test/snapshots/hooks_extended/should_invoke_onerroroccurred_hook_when_error_occurs.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/hooks_extended/should_invoke_onsessionend_hook_when_session_is_disconnected.yaml b/test/snapshots/hooks_extended/should_invoke_onsessionend_hook_when_session_is_disconnected.yaml index bde2373cc2..bd61da81cb 100644 --- a/test/snapshots/hooks_extended/should_invoke_onsessionend_hook_when_session_is_disconnected.yaml +++ b/test/snapshots/hooks_extended/should_invoke_onsessionend_hook_when_session_is_disconnected.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/hooks_extended/should_invoke_onsessionstart_hook_on_new_session.yaml b/test/snapshots/hooks_extended/should_invoke_onsessionstart_hook_on_new_session.yaml index 0d2da93e50..e200c7f68f 100644 --- a/test/snapshots/hooks_extended/should_invoke_onsessionstart_hook_on_new_session.yaml +++ b/test/snapshots/hooks_extended/should_invoke_onsessionstart_hook_on_new_session.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/hooks_extended/should_invoke_onuserpromptsubmitted_hook_when_sending_a_message.yaml b/test/snapshots/hooks_extended/should_invoke_onuserpromptsubmitted_hook_when_sending_a_message.yaml index beb8b443d2..3569a8ca8e 100644 --- a/test/snapshots/hooks_extended/should_invoke_onuserpromptsubmitted_hook_when_sending_a_message.yaml +++ b/test/snapshots/hooks_extended/should_invoke_onuserpromptsubmitted_hook_when_sending_a_message.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/hooks_extended/should_invoke_posttoolusefailure_hook_for_failed_tool_result.yaml b/test/snapshots/hooks_extended/should_invoke_posttoolusefailure_hook_for_failed_tool_result.yaml index bbe815735c..a1c3f4fb2d 100644 --- a/test/snapshots/hooks_extended/should_invoke_posttoolusefailure_hook_for_failed_tool_result.yaml +++ b/test/snapshots/hooks_extended/should_invoke_posttoolusefailure_hook_for_failed_tool_result.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/hooks_extended/should_invoke_sessionend_hook.yaml b/test/snapshots/hooks_extended/should_invoke_sessionend_hook.yaml index 75fccc4e1f..017000cfba 100644 --- a/test/snapshots/hooks_extended/should_invoke_sessionend_hook.yaml +++ b/test/snapshots/hooks_extended/should_invoke_sessionend_hook.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/hooks_extended/should_invoke_sessionstart_hook.yaml b/test/snapshots/hooks_extended/should_invoke_sessionstart_hook.yaml index c1d643b4c3..725222bfd3 100644 --- a/test/snapshots/hooks_extended/should_invoke_sessionstart_hook.yaml +++ b/test/snapshots/hooks_extended/should_invoke_sessionstart_hook.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system @@ -13,7 +13,7 @@ conversations: Hi! 👋 - I'm GitHub Copilot CLI, powered by claude-sonnet-4.5. I'm here to help you with software engineering tasks + I'm GitHub Copilot CLI, powered by claude-sonnet-5. I'm here to help you with software engineering tasks like exploring codebases, running commands, making code changes, and more. diff --git a/test/snapshots/hooks_extended/should_invoke_userpromptsubmitted_hook_and_modify_prompt.yaml b/test/snapshots/hooks_extended/should_invoke_userpromptsubmitted_hook_and_modify_prompt.yaml index ceb291c9dd..688dc01e1e 100644 --- a/test/snapshots/hooks_extended/should_invoke_userpromptsubmitted_hook_and_modify_prompt.yaml +++ b/test/snapshots/hooks_extended/should_invoke_userpromptsubmitted_hook_and_modify_prompt.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system 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 index db2b029680..1fdf62b876 100644 --- 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 @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/hooks_extended/should_register_erroroccurred_hook.yaml b/test/snapshots/hooks_extended/should_register_erroroccurred_hook.yaml index 2f02a0570d..ef34d886b1 100644 --- a/test/snapshots/hooks_extended/should_register_erroroccurred_hook.yaml +++ b/test/snapshots/hooks_extended/should_register_erroroccurred_hook.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/mcp-and-agents/should_accept_both_mcp_servers_and_custom_agents.yaml b/test/snapshots/mcp-and-agents/should_accept_both_mcp_servers_and_custom_agents.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/mcp-and-agents/should_accept_both_mcp_servers_and_custom_agents.yaml +++ b/test/snapshots/mcp-and-agents/should_accept_both_mcp_servers_and_custom_agents.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/mcp-and-agents/should_accept_custom_agent_configuration_on_session_create.yaml b/test/snapshots/mcp-and-agents/should_accept_custom_agent_configuration_on_session_create.yaml index 56da15bae1..dbf50977bc 100644 --- a/test/snapshots/mcp-and-agents/should_accept_custom_agent_configuration_on_session_create.yaml +++ b/test/snapshots/mcp-and-agents/should_accept_custom_agent_configuration_on_session_create.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/mcp-and-agents/should_accept_custom_agent_configuration_on_session_resume.yaml b/test/snapshots/mcp-and-agents/should_accept_custom_agent_configuration_on_session_resume.yaml index 9703495c66..dbc06f70b1 100644 --- a/test/snapshots/mcp-and-agents/should_accept_custom_agent_configuration_on_session_resume.yaml +++ b/test/snapshots/mcp-and-agents/should_accept_custom_agent_configuration_on_session_resume.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/mcp-and-agents/should_accept_mcp_server_configuration_on_session_create.yaml b/test/snapshots/mcp-and-agents/should_accept_mcp_server_configuration_on_session_create.yaml index 9fe2fcd070..7cd9232226 100644 --- a/test/snapshots/mcp-and-agents/should_accept_mcp_server_configuration_on_session_create.yaml +++ b/test/snapshots/mcp-and-agents/should_accept_mcp_server_configuration_on_session_create.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/mcp-and-agents/should_accept_mcp_server_configuration_on_session_resume.yaml b/test/snapshots/mcp-and-agents/should_accept_mcp_server_configuration_on_session_resume.yaml index 82c9917c34..96321ffe3a 100644 --- a/test/snapshots/mcp-and-agents/should_accept_mcp_server_configuration_on_session_resume.yaml +++ b/test/snapshots/mcp-and-agents/should_accept_mcp_server_configuration_on_session_resume.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/mcp_and_agents/accept_custom_agent_config_on_create.yaml b/test/snapshots/mcp_and_agents/accept_custom_agent_config_on_create.yaml index 56da15bae1..dbf50977bc 100644 --- a/test/snapshots/mcp_and_agents/accept_custom_agent_config_on_create.yaml +++ b/test/snapshots/mcp_and_agents/accept_custom_agent_config_on_create.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/mcp_and_agents/accept_custom_agent_config_on_resume.yaml b/test/snapshots/mcp_and_agents/accept_custom_agent_config_on_resume.yaml index 16db486e88..e454b1e96c 100644 --- a/test/snapshots/mcp_and_agents/accept_custom_agent_config_on_resume.yaml +++ b/test/snapshots/mcp_and_agents/accept_custom_agent_config_on_resume.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/mcp_and_agents/accept_mcp_server_config_on_create.yaml b/test/snapshots/mcp_and_agents/accept_mcp_server_config_on_create.yaml index 9fe2fcd070..7cd9232226 100644 --- a/test/snapshots/mcp_and_agents/accept_mcp_server_config_on_create.yaml +++ b/test/snapshots/mcp_and_agents/accept_mcp_server_config_on_create.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/mcp_and_agents/accept_mcp_server_config_on_resume.yaml b/test/snapshots/mcp_and_agents/accept_mcp_server_config_on_resume.yaml index f9918fa133..fe4181ccd2 100644 --- a/test/snapshots/mcp_and_agents/accept_mcp_server_config_on_resume.yaml +++ b/test/snapshots/mcp_and_agents/accept_mcp_server_config_on_resume.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/mcp_and_agents/accept_mcp_server_config_without_args.yaml b/test/snapshots/mcp_and_agents/accept_mcp_server_config_without_args.yaml index 9fe2fcd070..7cd9232226 100644 --- a/test/snapshots/mcp_and_agents/accept_mcp_server_config_without_args.yaml +++ b/test/snapshots/mcp_and_agents/accept_mcp_server_config_without_args.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/mcp_and_agents/accept_mcp_servers_and_custom_agents.yaml b/test/snapshots/mcp_and_agents/accept_mcp_servers_and_custom_agents.yaml index 60d1eadeaf..51716b5c80 100644 --- a/test/snapshots/mcp_and_agents/accept_mcp_servers_and_custom_agents.yaml +++ b/test/snapshots/mcp_and_agents/accept_mcp_servers_and_custom_agents.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/mcp_and_agents/should_accept_both_mcp_servers_and_custom_agents.yaml b/test/snapshots/mcp_and_agents/should_accept_both_mcp_servers_and_custom_agents.yaml index 60d1eadeaf..51716b5c80 100644 --- a/test/snapshots/mcp_and_agents/should_accept_both_mcp_servers_and_custom_agents.yaml +++ b/test/snapshots/mcp_and_agents/should_accept_both_mcp_servers_and_custom_agents.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/mcp_and_agents/should_accept_custom_agent_configuration_on_session_create.yaml b/test/snapshots/mcp_and_agents/should_accept_custom_agent_configuration_on_session_create.yaml index 56da15bae1..dbf50977bc 100644 --- a/test/snapshots/mcp_and_agents/should_accept_custom_agent_configuration_on_session_create.yaml +++ b/test/snapshots/mcp_and_agents/should_accept_custom_agent_configuration_on_session_create.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/mcp_and_agents/should_accept_custom_agent_configuration_on_session_resume.yaml b/test/snapshots/mcp_and_agents/should_accept_custom_agent_configuration_on_session_resume.yaml index 9703495c66..dbc06f70b1 100644 --- a/test/snapshots/mcp_and_agents/should_accept_custom_agent_configuration_on_session_resume.yaml +++ b/test/snapshots/mcp_and_agents/should_accept_custom_agent_configuration_on_session_resume.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/mcp_and_agents/should_accept_defaultagent_configuration_on_session_resume.yaml b/test/snapshots/mcp_and_agents/should_accept_defaultagent_configuration_on_session_resume.yaml index 65fe6664e6..5fab1b6334 100644 --- a/test/snapshots/mcp_and_agents/should_accept_defaultagent_configuration_on_session_resume.yaml +++ b/test/snapshots/mcp_and_agents/should_accept_defaultagent_configuration_on_session_resume.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/mcp_and_agents/should_accept_mcp_server_configuration_on_session_create.yaml b/test/snapshots/mcp_and_agents/should_accept_mcp_server_configuration_on_session_create.yaml index 9fe2fcd070..7cd9232226 100644 --- a/test/snapshots/mcp_and_agents/should_accept_mcp_server_configuration_on_session_create.yaml +++ b/test/snapshots/mcp_and_agents/should_accept_mcp_server_configuration_on_session_create.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/mcp_and_agents/should_accept_mcp_server_configuration_on_session_resume.yaml b/test/snapshots/mcp_and_agents/should_accept_mcp_server_configuration_on_session_resume.yaml index 250402101b..c8aa45cfff 100644 --- a/test/snapshots/mcp_and_agents/should_accept_mcp_server_configuration_on_session_resume.yaml +++ b/test/snapshots/mcp_and_agents/should_accept_mcp_server_configuration_on_session_resume.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/mcp_and_agents/should_accept_mcp_server_configuration_without_args.yaml b/test/snapshots/mcp_and_agents/should_accept_mcp_server_configuration_without_args.yaml index 9fe2fcd070..7cd9232226 100644 --- a/test/snapshots/mcp_and_agents/should_accept_mcp_server_configuration_without_args.yaml +++ b/test/snapshots/mcp_and_agents/should_accept_mcp_server_configuration_without_args.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/mcp_and_agents/should_handle_custom_agent_with_mcp_servers.yaml b/test/snapshots/mcp_and_agents/should_handle_custom_agent_with_mcp_servers.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/mcp_and_agents/should_handle_custom_agent_with_mcp_servers.yaml +++ b/test/snapshots/mcp_and_agents/should_handle_custom_agent_with_mcp_servers.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/mcp_and_agents/should_handle_custom_agent_with_tools_configuration.yaml b/test/snapshots/mcp_and_agents/should_handle_custom_agent_with_tools_configuration.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/mcp_and_agents/should_handle_custom_agent_with_tools_configuration.yaml +++ b/test/snapshots/mcp_and_agents/should_handle_custom_agent_with_tools_configuration.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/mcp_and_agents/should_handle_multiple_custom_agents.yaml b/test/snapshots/mcp_and_agents/should_handle_multiple_custom_agents.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/mcp_and_agents/should_handle_multiple_custom_agents.yaml +++ b/test/snapshots/mcp_and_agents/should_handle_multiple_custom_agents.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/mcp_and_agents/should_handle_multiple_mcp_servers.yaml b/test/snapshots/mcp_and_agents/should_handle_multiple_mcp_servers.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/mcp_and_agents/should_handle_multiple_mcp_servers.yaml +++ b/test/snapshots/mcp_and_agents/should_handle_multiple_mcp_servers.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/mcp_and_agents/should_hide_excluded_tools_from_default_agent.yaml b/test/snapshots/mcp_and_agents/should_hide_excluded_tools_from_default_agent.yaml index f5506bb184..bf8c7b4d33 100644 --- a/test/snapshots/mcp_and_agents/should_hide_excluded_tools_from_default_agent.yaml +++ b/test/snapshots/mcp_and_agents/should_hide_excluded_tools_from_default_agent.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/mcp_and_agents/should_pass_literal_env_values_to_mcp_server_subprocess.yaml b/test/snapshots/mcp_and_agents/should_pass_literal_env_values_to_mcp_server_subprocess.yaml index 29ba0fc68b..b3eab15f66 100644 --- a/test/snapshots/mcp_and_agents/should_pass_literal_env_values_to_mcp_server_subprocess.yaml +++ b/test/snapshots/mcp_and_agents/should_pass_literal_env_values_to_mcp_server_subprocess.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/mcp_and_agents/should_round_trip_mcp_server_elicitation_request.yaml b/test/snapshots/mcp_and_agents/should_round_trip_mcp_server_elicitation_request.yaml index c1df8e8023..9aa12434df 100644 --- a/test/snapshots/mcp_and_agents/should_round_trip_mcp_server_elicitation_request.yaml +++ b/test/snapshots/mcp_and_agents/should_round_trip_mcp_server_elicitation_request.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/mcpservers/accept_mcp_server_config_on_create.yaml b/test/snapshots/mcpservers/accept_mcp_server_config_on_create.yaml index 9fe2fcd070..7cd9232226 100644 --- a/test/snapshots/mcpservers/accept_mcp_server_config_on_create.yaml +++ b/test/snapshots/mcpservers/accept_mcp_server_config_on_create.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/mcpservers/accept_mcp_server_config_on_resume.yaml b/test/snapshots/mcpservers/accept_mcp_server_config_on_resume.yaml index 82c9917c34..96321ffe3a 100644 --- a/test/snapshots/mcpservers/accept_mcp_server_config_on_resume.yaml +++ b/test/snapshots/mcpservers/accept_mcp_server_config_on_resume.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/mode_empty/empty_mode_append_caller_instruction_takes_effect_and_env_context_stripped.yaml b/test/snapshots/mode_empty/empty_mode_append_caller_instruction_takes_effect_and_env_context_stripped.yaml index fac88270d5..6193c536b4 100644 --- a/test/snapshots/mode_empty/empty_mode_append_caller_instruction_takes_effect_and_env_context_stripped.yaml +++ b/test/snapshots/mode_empty/empty_mode_append_caller_instruction_takes_effect_and_env_context_stripped.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/mode_empty/empty_mode_builtin_star_exposes_all_built_in_tools.yaml b/test/snapshots/mode_empty/empty_mode_builtin_star_exposes_all_built_in_tools.yaml index decf64bc37..b6606842af 100644 --- a/test/snapshots/mode_empty/empty_mode_builtin_star_exposes_all_built_in_tools.yaml +++ b/test/snapshots/mode_empty/empty_mode_builtin_star_exposes_all_built_in_tools.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/mode_empty/empty_mode_excluded_tools_subtracts_from_available_tools.yaml b/test/snapshots/mode_empty/empty_mode_excluded_tools_subtracts_from_available_tools.yaml index decf64bc37..b6606842af 100644 --- a/test/snapshots/mode_empty/empty_mode_excluded_tools_subtracts_from_available_tools.yaml +++ b/test/snapshots/mode_empty/empty_mode_excluded_tools_subtracts_from_available_tools.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/mode_empty/empty_mode_isolated_set_shell_tool_is_not_exposed.yaml b/test/snapshots/mode_empty/empty_mode_isolated_set_shell_tool_is_not_exposed.yaml index decf64bc37..b6606842af 100644 --- a/test/snapshots/mode_empty/empty_mode_isolated_set_shell_tool_is_not_exposed.yaml +++ b/test/snapshots/mode_empty/empty_mode_isolated_set_shell_tool_is_not_exposed.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/mode_empty/empty_mode_strips_environment_context_from_the_system_message_by_default.yaml b/test/snapshots/mode_empty/empty_mode_strips_environment_context_from_the_system_message_by_default.yaml index 6f23714d94..58ec8e370a 100644 --- a/test/snapshots/mode_empty/empty_mode_strips_environment_context_from_the_system_message_by_default.yaml +++ b/test/snapshots/mode_empty/empty_mode_strips_environment_context_from_the_system_message_by_default.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/mode_empty/empty_mode_system_message_replace_llm_follows_caller_content_verbatim.yaml b/test/snapshots/mode_empty/empty_mode_system_message_replace_llm_follows_caller_content_verbatim.yaml index 5d63a94018..cfa604adbe 100644 --- a/test/snapshots/mode_empty/empty_mode_system_message_replace_llm_follows_caller_content_verbatim.yaml +++ b/test/snapshots/mode_empty/empty_mode_system_message_replace_llm_follows_caller_content_verbatim.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/mode_handlers/should_invoke_auto_mode_switch_handler_when_rate_limited.yaml b/test/snapshots/mode_handlers/should_invoke_auto_mode_switch_handler_when_rate_limited.yaml index 19c271b4f1..30fee89306 100644 --- a/test/snapshots/mode_handlers/should_invoke_auto_mode_switch_handler_when_rate_limited.yaml +++ b/test/snapshots/mode_handlers/should_invoke_auto_mode_switch_handler_when_rate_limited.yaml @@ -1,8 +1,8 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 - auto errors: - - model: claude-sonnet-4.5 + - model: claude-sonnet-5 status: 429 code: user_weekly_rate_limited message: You've reached your weekly rate limit. diff --git a/test/snapshots/mode_handlers/should_invoke_exit_plan_mode_handler_when_model_uses_tool.yaml b/test/snapshots/mode_handlers/should_invoke_exit_plan_mode_handler_when_model_uses_tool.yaml index 078ba05483..9ee28ad083 100644 --- a/test/snapshots/mode_handlers/should_invoke_exit_plan_mode_handler_when_model_uses_tool.yaml +++ b/test/snapshots/mode_handlers/should_invoke_exit_plan_mode_handler_when_model_uses_tool.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/multi_client/both_clients_see_tool_request_and_completion_events.yaml b/test/snapshots/multi_client/both_clients_see_tool_request_and_completion_events.yaml index 62f0d004a0..89f28fcbe1 100644 --- a/test/snapshots/multi_client/both_clients_see_tool_request_and_completion_events.yaml +++ b/test/snapshots/multi_client/both_clients_see_tool_request_and_completion_events.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/multi_client/disconnecting_client_removes_its_tools.yaml b/test/snapshots/multi_client/disconnecting_client_removes_its_tools.yaml index b6410e0d1b..925e8f3076 100644 --- a/test/snapshots/multi_client/disconnecting_client_removes_its_tools.yaml +++ b/test/snapshots/multi_client/disconnecting_client_removes_its_tools.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/multi_client/one_client_approves_permission_and_both_see_the_result.yaml b/test/snapshots/multi_client/one_client_approves_permission_and_both_see_the_result.yaml index 2397bfdc01..fce177c1c3 100644 --- a/test/snapshots/multi_client/one_client_approves_permission_and_both_see_the_result.yaml +++ b/test/snapshots/multi_client/one_client_approves_permission_and_both_see_the_result.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/multi_client/one_client_rejects_permission_and_both_see_the_result.yaml b/test/snapshots/multi_client/one_client_rejects_permission_and_both_see_the_result.yaml index ba9db87d08..105a472f9c 100644 --- a/test/snapshots/multi_client/one_client_rejects_permission_and_both_see_the_result.yaml +++ b/test/snapshots/multi_client/one_client_rejects_permission_and_both_see_the_result.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/multi_client/two_clients_register_different_tools_and_agent_uses_both.yaml b/test/snapshots/multi_client/two_clients_register_different_tools_and_agent_uses_both.yaml index c04864d827..39d56792d5 100644 --- a/test/snapshots/multi_client/two_clients_register_different_tools_and_agent_uses_both.yaml +++ b/test/snapshots/multi_client/two_clients_register_different_tools_and_agent_uses_both.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/multi_provider_registry/should_register_multiple_providers_with_custom_agents_bound_to_their_models.yaml b/test/snapshots/multi_provider_registry/should_register_multiple_providers_with_custom_agents_bound_to_their_models.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/multi_provider_registry/should_register_multiple_providers_with_custom_agents_bound_to_their_models.yaml +++ b/test/snapshots/multi_provider_registry/should_register_multiple_providers_with_custom_agents_bound_to_their_models.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/multi_turn/should_handle_file_creation_then_reading_across_turns.yaml b/test/snapshots/multi_turn/should_handle_file_creation_then_reading_across_turns.yaml index 0d79c3e1ab..7d991ce94e 100644 --- a/test/snapshots/multi_turn/should_handle_file_creation_then_reading_across_turns.yaml +++ b/test/snapshots/multi_turn/should_handle_file_creation_then_reading_across_turns.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system @@ -83,7 +83,7 @@ conversations: arguments: '{"path":"${workdir}/greeting.txt"}' - role: tool tool_call_id: toolcall_2 - content: 1. Hello from multi-turn test + content: Hello from multi-turn test - role: assistant content: |- The exact contents of `greeting.txt` are: diff --git a/test/snapshots/multi_turn/should_use_tool_results_from_previous_turns.yaml b/test/snapshots/multi_turn/should_use_tool_results_from_previous_turns.yaml index b930bb46ac..3ea91fec16 100644 --- a/test/snapshots/multi_turn/should_use_tool_results_from_previous_turns.yaml +++ b/test/snapshots/multi_turn/should_use_tool_results_from_previous_turns.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system @@ -44,7 +44,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. The magic number is 42. + content: The magic number is 42. - role: assistant content: The magic number is **42**. - messages: @@ -69,7 +69,7 @@ conversations: content: Tool 'report_intent' does not exist. - role: tool tool_call_id: toolcall_1 - content: 1. The magic number is 42. + content: The magic number is 42. - role: assistant content: The magic number is **42**. - role: user diff --git a/test/snapshots/pending_work_resume/should_continue_parallel_pending_external_tool_requests_after_resume.yaml b/test/snapshots/pending_work_resume/should_continue_parallel_pending_external_tool_requests_after_resume.yaml index acae9a8b24..29574ef20b 100644 --- a/test/snapshots/pending_work_resume/should_continue_parallel_pending_external_tool_requests_after_resume.yaml +++ b/test/snapshots/pending_work_resume/should_continue_parallel_pending_external_tool_requests_after_resume.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/pending_work_resume/should_continue_pending_external_tool_request_after_resume.yaml b/test/snapshots/pending_work_resume/should_continue_pending_external_tool_request_after_resume.yaml index f9fcc188a7..c39ad4f6e4 100644 --- a/test/snapshots/pending_work_resume/should_continue_pending_external_tool_request_after_resume.yaml +++ b/test/snapshots/pending_work_resume/should_continue_pending_external_tool_request_after_resume.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/pending_work_resume/should_continue_pending_permission_request_after_resume.yaml b/test/snapshots/pending_work_resume/should_continue_pending_permission_request_after_resume.yaml index 4856cdc4c6..4e001d5cf2 100644 --- a/test/snapshots/pending_work_resume/should_continue_pending_permission_request_after_resume.yaml +++ b/test/snapshots/pending_work_resume/should_continue_pending_permission_request_after_resume.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/pending_work_resume/should_keep_pending_external_tool_handleable_on_cold_resume_when_continuependingwork_is_false.yaml b/test/snapshots/pending_work_resume/should_keep_pending_external_tool_handleable_on_cold_resume_when_continuependingwork_is_false.yaml index 8a32e431a7..d8ffe5df61 100644 --- a/test/snapshots/pending_work_resume/should_keep_pending_external_tool_handleable_on_cold_resume_when_continuependingwork_is_false.yaml +++ b/test/snapshots/pending_work_resume/should_keep_pending_external_tool_handleable_on_cold_resume_when_continuependingwork_is_false.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/pending_work_resume/should_keep_pending_external_tool_handleable_on_warm_resume_when_continuependingwork_is_false.yaml b/test/snapshots/pending_work_resume/should_keep_pending_external_tool_handleable_on_warm_resume_when_continuependingwork_is_false.yaml index 4ae08f8a80..85098c636c 100644 --- a/test/snapshots/pending_work_resume/should_keep_pending_external_tool_handleable_on_warm_resume_when_continuependingwork_is_false.yaml +++ b/test/snapshots/pending_work_resume/should_keep_pending_external_tool_handleable_on_warm_resume_when_continuependingwork_is_false.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/pending_work_resume/should_report_continuependingwork_true_in_resume_event.yaml b/test/snapshots/pending_work_resume/should_report_continuependingwork_true_in_resume_event.yaml index 3b5c7dfe41..bef3a92876 100644 --- a/test/snapshots/pending_work_resume/should_report_continuependingwork_true_in_resume_event.yaml +++ b/test/snapshots/pending_work_resume/should_report_continuependingwork_true_in_resume_event.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/pending_work_resume/should_resume_successfully_when_no_pending_work_exists.yaml b/test/snapshots/pending_work_resume/should_resume_successfully_when_no_pending_work_exists.yaml index d7117cee65..de0c2634b1 100644 --- a/test/snapshots/pending_work_resume/should_resume_successfully_when_no_pending_work_exists.yaml +++ b/test/snapshots/pending_work_resume/should_resume_successfully_when_no_pending_work_exists.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/per-session-auth/session_auth_status_is_unauthenticated_without_token.yaml b/test/snapshots/per-session-auth/session_auth_status_is_unauthenticated_without_token.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/per-session-auth/session_auth_status_is_unauthenticated_without_token.yaml +++ b/test/snapshots/per-session-auth/session_auth_status_is_unauthenticated_without_token.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/per-session-auth/session_fails_with_invalid_token.yaml b/test/snapshots/per-session-auth/session_fails_with_invalid_token.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/per-session-auth/session_fails_with_invalid_token.yaml +++ b/test/snapshots/per-session-auth/session_fails_with_invalid_token.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/per-session-auth/session_token_overrides_client_token.yaml b/test/snapshots/per-session-auth/session_token_overrides_client_token.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/per-session-auth/session_token_overrides_client_token.yaml +++ b/test/snapshots/per-session-auth/session_token_overrides_client_token.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/per-session-auth/session_uses_client_token_when_no_session_token_is_supplied.yaml b/test/snapshots/per-session-auth/session_uses_client_token_when_no_session_token_is_supplied.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/per-session-auth/session_uses_client_token_when_no_session_token_is_supplied.yaml +++ b/test/snapshots/per-session-auth/session_uses_client_token_when_no_session_token_is_supplied.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/permissions/async_permission_handler.yaml b/test/snapshots/permissions/async_permission_handler.yaml index 1d46c38a41..bf3431703c 100644 --- a/test/snapshots/permissions/async_permission_handler.yaml +++ b/test/snapshots/permissions/async_permission_handler.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/permissions/deny_permission.yaml b/test/snapshots/permissions/deny_permission.yaml index 480b640fb3..63ed54f671 100644 --- a/test/snapshots/permissions/deny_permission.yaml +++ b/test/snapshots/permissions/deny_permission.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/permissions/permission_handler_errors.yaml b/test/snapshots/permissions/permission_handler_errors.yaml index cee78a0929..f8f274d66e 100644 --- a/test/snapshots/permissions/permission_handler_errors.yaml +++ b/test/snapshots/permissions/permission_handler_errors.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/permissions/permission_handler_for_shell_commands.yaml b/test/snapshots/permissions/permission_handler_for_shell_commands.yaml index 1d46c38a41..bf3431703c 100644 --- a/test/snapshots/permissions/permission_handler_for_shell_commands.yaml +++ b/test/snapshots/permissions/permission_handler_for_shell_commands.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/permissions/permission_handler_for_write_operations.yaml b/test/snapshots/permissions/permission_handler_for_write_operations.yaml index a4ede6fcb1..9811c4a6ab 100644 --- a/test/snapshots/permissions/permission_handler_for_write_operations.yaml +++ b/test/snapshots/permissions/permission_handler_for_write_operations.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system @@ -47,7 +47,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. original content + content: original content - role: assistant content: "Now I'll replace 'original' with 'modified':" - role: assistant @@ -82,7 +82,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. original content + content: original content - role: assistant content: "Now I'll replace 'original' with 'modified':" tool_calls: diff --git a/test/snapshots/permissions/resume_session_with_permission_handler.yaml b/test/snapshots/permissions/resume_session_with_permission_handler.yaml index 6296a0d73e..ade442a927 100644 --- a/test/snapshots/permissions/resume_session_with_permission_handler.yaml +++ b/test/snapshots/permissions/resume_session_with_permission_handler.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/permissions/should_deny_permission_when_handler_returns_denied.yaml b/test/snapshots/permissions/should_deny_permission_when_handler_returns_denied.yaml index ef6f60dbed..8e28d8156d 100644 --- a/test/snapshots/permissions/should_deny_permission_when_handler_returns_denied.yaml +++ b/test/snapshots/permissions/should_deny_permission_when_handler_returns_denied.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/permissions/should_deny_permission_with_noresult_kind.yaml b/test/snapshots/permissions/should_deny_permission_with_noresult_kind.yaml index 0dfbd9e6b2..5f9a98ac80 100644 --- a/test/snapshots/permissions/should_deny_permission_with_noresult_kind.yaml +++ b/test/snapshots/permissions/should_deny_permission_with_noresult_kind.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/permissions/should_deny_tool_operations_when_handler_explicitly_denies.yaml b/test/snapshots/permissions/should_deny_tool_operations_when_handler_explicitly_denies.yaml index 0d25979c7c..0f7c4782cc 100644 --- a/test/snapshots/permissions/should_deny_tool_operations_when_handler_explicitly_denies.yaml +++ b/test/snapshots/permissions/should_deny_tool_operations_when_handler_explicitly_denies.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/permissions/should_deny_tool_operations_when_handler_explicitly_denies_after_resume.yaml b/test/snapshots/permissions/should_deny_tool_operations_when_handler_explicitly_denies_after_resume.yaml index 9a23c55f0a..fc802f16da 100644 --- a/test/snapshots/permissions/should_deny_tool_operations_when_handler_explicitly_denies_after_resume.yaml +++ b/test/snapshots/permissions/should_deny_tool_operations_when_handler_explicitly_denies_after_resume.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/permissions/should_handle_async_permission_handler.yaml b/test/snapshots/permissions/should_handle_async_permission_handler.yaml index 1d46c38a41..bf3431703c 100644 --- a/test/snapshots/permissions/should_handle_async_permission_handler.yaml +++ b/test/snapshots/permissions/should_handle_async_permission_handler.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/permissions/should_handle_concurrent_permission_requests_from_parallel_tools.yaml b/test/snapshots/permissions/should_handle_concurrent_permission_requests_from_parallel_tools.yaml index 924123536c..7432c4caf9 100644 --- a/test/snapshots/permissions/should_handle_concurrent_permission_requests_from_parallel_tools.yaml +++ b/test/snapshots/permissions/should_handle_concurrent_permission_requests_from_parallel_tools.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/permissions/should_handle_permission_handler_errors_gracefully.yaml b/test/snapshots/permissions/should_handle_permission_handler_errors_gracefully.yaml index 2a2db62101..17b75a4925 100644 --- a/test/snapshots/permissions/should_handle_permission_handler_errors_gracefully.yaml +++ b/test/snapshots/permissions/should_handle_permission_handler_errors_gracefully.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system 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 0000000000..8e28d8156d --- /dev/null +++ b/test/snapshots/permissions/should_honor_a_decision_annotated_with_decisioncontext.yaml @@ -0,0 +1,24 @@ +models: + - claude-sonnet-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/permissions/should_invoke_permission_handler_for_write_operations.yaml b/test/snapshots/permissions/should_invoke_permission_handler_for_write_operations.yaml index a4ede6fcb1..9811c4a6ab 100644 --- a/test/snapshots/permissions/should_invoke_permission_handler_for_write_operations.yaml +++ b/test/snapshots/permissions/should_invoke_permission_handler_for_write_operations.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system @@ -47,7 +47,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. original content + content: original content - role: assistant content: "Now I'll replace 'original' with 'modified':" - role: assistant @@ -82,7 +82,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. original content + content: original content - role: assistant content: "Now I'll replace 'original' with 'modified':" tool_calls: diff --git a/test/snapshots/permissions/should_receive_toolcallid_in_permission_requests.yaml b/test/snapshots/permissions/should_receive_toolcallid_in_permission_requests.yaml index 90407df6fc..cd73f7e165 100644 --- a/test/snapshots/permissions/should_receive_toolcallid_in_permission_requests.yaml +++ b/test/snapshots/permissions/should_receive_toolcallid_in_permission_requests.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/permissions/should_resume_session_with_permission_handler.yaml b/test/snapshots/permissions/should_resume_session_with_permission_handler.yaml index 6296a0d73e..ade442a927 100644 --- a/test/snapshots/permissions/should_resume_session_with_permission_handler.yaml +++ b/test/snapshots/permissions/should_resume_session_with_permission_handler.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/permissions/should_short_circuit_permission_handler_when_set_approve_all_enabled.yaml b/test/snapshots/permissions/should_short_circuit_permission_handler_when_set_approve_all_enabled.yaml index 3a6d66dc8d..1c33d19a98 100644 --- a/test/snapshots/permissions/should_short_circuit_permission_handler_when_set_approve_all_enabled.yaml +++ b/test/snapshots/permissions/should_short_circuit_permission_handler_when_set_approve_all_enabled.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/permissions/should_wait_for_slow_permission_handler.yaml b/test/snapshots/permissions/should_wait_for_slow_permission_handler.yaml index 19398ce5d6..9ecbdb6a51 100644 --- a/test/snapshots/permissions/should_wait_for_slow_permission_handler.yaml +++ b/test/snapshots/permissions/should_wait_for_slow_permission_handler.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/permissions/should_work_with_approve_all_permission_handler.yaml b/test/snapshots/permissions/should_work_with_approve_all_permission_handler.yaml index 9199977dba..554b4b1f70 100644 --- a/test/snapshots/permissions/should_work_with_approve_all_permission_handler.yaml +++ b/test/snapshots/permissions/should_work_with_approve_all_permission_handler.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/permissions/tool_call_id_in_permission_requests.yaml b/test/snapshots/permissions/tool_call_id_in_permission_requests.yaml index 90407df6fc..cd73f7e165 100644 --- a/test/snapshots/permissions/tool_call_id_in_permission_requests.yaml +++ b/test/snapshots/permissions/tool_call_id_in_permission_requests.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/pre_mcp_tool_call_hook/should_remove_meta_via_premcptoolcall_hook.yaml b/test/snapshots/pre_mcp_tool_call_hook/should_remove_meta_via_premcptoolcall_hook.yaml index c771647842..5e1970247a 100644 --- a/test/snapshots/pre_mcp_tool_call_hook/should_remove_meta_via_premcptoolcall_hook.yaml +++ b/test/snapshots/pre_mcp_tool_call_hook/should_remove_meta_via_premcptoolcall_hook.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/pre_mcp_tool_call_hook/should_replace_meta_via_premcptoolcall_hook.yaml b/test/snapshots/pre_mcp_tool_call_hook/should_replace_meta_via_premcptoolcall_hook.yaml index d7ff876a6c..b8d0e30d19 100644 --- a/test/snapshots/pre_mcp_tool_call_hook/should_replace_meta_via_premcptoolcall_hook.yaml +++ b/test/snapshots/pre_mcp_tool_call_hook/should_replace_meta_via_premcptoolcall_hook.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/pre_mcp_tool_call_hook/should_set_meta_via_premcptoolcall_hook.yaml b/test/snapshots/pre_mcp_tool_call_hook/should_set_meta_via_premcptoolcall_hook.yaml index 1d92fe8eed..cf77c42735 100644 --- a/test/snapshots/pre_mcp_tool_call_hook/should_set_meta_via_premcptoolcall_hook.yaml +++ b/test/snapshots/pre_mcp_tool_call_hook/should_set_meta_via_premcptoolcall_hook.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/resume_mcp_oauth/should_resume_a_persisted_session_with_mcp_auth_handler.yaml b/test/snapshots/resume_mcp_oauth/should_resume_a_persisted_session_with_mcp_auth_handler.yaml index 250402101b..c8aa45cfff 100644 --- a/test/snapshots/resume_mcp_oauth/should_resume_a_persisted_session_with_mcp_auth_handler.yaml +++ b/test/snapshots/resume_mcp_oauth/should_resume_a_persisted_session_with_mcp_auth_handler.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system 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 0000000000..31753aed11 --- /dev/null +++ b/test/snapshots/rewind/should_restore_tracked_file_and_conversation.yaml @@ -0,0 +1,37 @@ +models: + - claude-sonnet-5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: Use the edit tool to replace the exact contents of rewind-sdk.txt from Original rewind content to Prepared + rewind content. After the tool succeeds, reply with exactly SDK_REWIND_READY. + - role: assistant + tool_calls: + - id: toolcall_0 + type: function + function: + name: edit + arguments: '{"path":"${workdir}/rewind-sdk.txt","old_str":"Original rewind content","new_str":"Prepared rewind + content"}' + - role: tool + tool_call_id: toolcall_0 + content: File ${workdir}/rewind-sdk.txt updated with changes. + - role: assistant + content: SDK_REWIND_READY + - role: user + content: Use the edit tool to replace the exact contents of rewind-sdk.txt from Prepared rewind content to SDK rewind + content. After the tool succeeds, reply with exactly SDK_REWIND_DONE. + - role: assistant + tool_calls: + - id: toolcall_1 + type: function + function: + name: edit + arguments: '{"path":"${workdir}/rewind-sdk.txt","old_str":"Prepared rewind content","new_str":"SDK rewind content"}' + - role: tool + tool_call_id: toolcall_1 + content: File ${workdir}/rewind-sdk.txt updated with changes. + - role: assistant + content: SDK_REWIND_DONE diff --git a/test/snapshots/rpc_additional_edge_cases/mode_set_to_same_value_multiple_times_stays_stable.yaml b/test/snapshots/rpc_additional_edge_cases/mode_set_to_same_value_multiple_times_stays_stable.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_additional_edge_cases/mode_set_to_same_value_multiple_times_stays_stable.yaml +++ b/test/snapshots/rpc_additional_edge_cases/mode_set_to_same_value_multiple_times_stays_stable.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_additional_edge_cases/name_set_with_unicode_round_trips.yaml b/test/snapshots/rpc_additional_edge_cases/name_set_with_unicode_round_trips.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_additional_edge_cases/name_set_with_unicode_round_trips.yaml +++ b/test/snapshots/rpc_additional_edge_cases/name_set_with_unicode_round_trips.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_additional_edge_cases/permissions_reset_session_approvals_on_fresh_session_is_noop.yaml b/test/snapshots/rpc_additional_edge_cases/permissions_reset_session_approvals_on_fresh_session_is_noop.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_additional_edge_cases/permissions_reset_session_approvals_on_fresh_session_is_noop.yaml +++ b/test/snapshots/rpc_additional_edge_cases/permissions_reset_session_approvals_on_fresh_session_is_noop.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_additional_edge_cases/permissions_set_approve_all_toggle_round_trips.yaml b/test/snapshots/rpc_additional_edge_cases/permissions_set_approve_all_toggle_round_trips.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_additional_edge_cases/permissions_set_approve_all_toggle_round_trips.yaml +++ b/test/snapshots/rpc_additional_edge_cases/permissions_set_approve_all_toggle_round_trips.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_additional_edge_cases/plan_delete_when_none_exists_is_idempotent.yaml b/test/snapshots/rpc_additional_edge_cases/plan_delete_when_none_exists_is_idempotent.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_additional_edge_cases/plan_delete_when_none_exists_is_idempotent.yaml +++ b/test/snapshots/rpc_additional_edge_cases/plan_delete_when_none_exists_is_idempotent.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_additional_edge_cases/plan_update_with_empty_content_then_read_returns_empty.yaml b/test/snapshots/rpc_additional_edge_cases/plan_update_with_empty_content_then_read_returns_empty.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_additional_edge_cases/plan_update_with_empty_content_then_read_returns_empty.yaml +++ b/test/snapshots/rpc_additional_edge_cases/plan_update_with_empty_content_then_read_returns_empty.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_additional_edge_cases/shell_exec_with_zero_timeout_does_not_kill_long_running_command.yaml b/test/snapshots/rpc_additional_edge_cases/shell_exec_with_zero_timeout_does_not_kill_long_running_command.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_additional_edge_cases/shell_exec_with_zero_timeout_does_not_kill_long_running_command.yaml +++ b/test/snapshots/rpc_additional_edge_cases/shell_exec_with_zero_timeout_does_not_kill_long_running_command.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_additional_edge_cases/usage_get_metrics_on_fresh_session_returns_zero_tokens.yaml b/test/snapshots/rpc_additional_edge_cases/usage_get_metrics_on_fresh_session_returns_zero_tokens.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_additional_edge_cases/usage_get_metrics_on_fresh_session_returns_zero_tokens.yaml +++ b/test/snapshots/rpc_additional_edge_cases/usage_get_metrics_on_fresh_session_returns_zero_tokens.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_additional_edge_cases/workspaces_create_file_with_empty_content_round_trips.yaml b/test/snapshots/rpc_additional_edge_cases/workspaces_create_file_with_empty_content_round_trips.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_additional_edge_cases/workspaces_create_file_with_empty_content_round_trips.yaml +++ b/test/snapshots/rpc_additional_edge_cases/workspaces_create_file_with_empty_content_round_trips.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_additional_edge_cases/workspaces_create_file_with_large_content_round_trips.yaml b/test/snapshots/rpc_additional_edge_cases/workspaces_create_file_with_large_content_round_trips.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_additional_edge_cases/workspaces_create_file_with_large_content_round_trips.yaml +++ b/test/snapshots/rpc_additional_edge_cases/workspaces_create_file_with_large_content_round_trips.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_additional_edge_cases/workspaces_create_file_with_unicode_content_round_trips.yaml b/test/snapshots/rpc_additional_edge_cases/workspaces_create_file_with_unicode_content_round_trips.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_additional_edge_cases/workspaces_create_file_with_unicode_content_round_trips.yaml +++ b/test/snapshots/rpc_additional_edge_cases/workspaces_create_file_with_unicode_content_round_trips.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_additional_edge_cases/workspaces_createfile_then_listfiles_returns_sorted_or_stable_order.yaml b/test/snapshots/rpc_additional_edge_cases/workspaces_createfile_then_listfiles_returns_sorted_or_stable_order.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_additional_edge_cases/workspaces_createfile_then_listfiles_returns_sorted_or_stable_order.yaml +++ b/test/snapshots/rpc_additional_edge_cases/workspaces_createfile_then_listfiles_returns_sorted_or_stable_order.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_additional_edge_cases/workspaces_getworkspace_returns_stable_result_across_calls.yaml b/test/snapshots/rpc_additional_edge_cases/workspaces_getworkspace_returns_stable_result_across_calls.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_additional_edge_cases/workspaces_getworkspace_returns_stable_result_across_calls.yaml +++ b/test/snapshots/rpc_additional_edge_cases/workspaces_getworkspace_returns_stable_result_across_calls.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_agents/should_call_agent_reload.yaml b/test/snapshots/rpc_agents/should_call_agent_reload.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_agents/should_call_agent_reload.yaml +++ b/test/snapshots/rpc_agents/should_call_agent_reload.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_agents/should_deselect_current_agent.yaml b/test/snapshots/rpc_agents/should_deselect_current_agent.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_agents/should_deselect_current_agent.yaml +++ b/test/snapshots/rpc_agents/should_deselect_current_agent.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_agents/should_emit_subagent_selected_and_deselected_events.yaml b/test/snapshots/rpc_agents/should_emit_subagent_selected_and_deselected_events.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_agents/should_emit_subagent_selected_and_deselected_events.yaml +++ b/test/snapshots/rpc_agents/should_emit_subagent_selected_and_deselected_events.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_agents/should_list_available_custom_agents.yaml b/test/snapshots/rpc_agents/should_list_available_custom_agents.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_agents/should_list_available_custom_agents.yaml +++ b/test/snapshots/rpc_agents/should_list_available_custom_agents.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_agents/should_return_empty_list_when_no_custom_agents_configured.yaml b/test/snapshots/rpc_agents/should_return_empty_list_when_no_custom_agents_configured.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_agents/should_return_empty_list_when_no_custom_agents_configured.yaml +++ b/test/snapshots/rpc_agents/should_return_empty_list_when_no_custom_agents_configured.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_agents/should_return_null_when_no_agent_is_selected.yaml b/test/snapshots/rpc_agents/should_return_null_when_no_agent_is_selected.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_agents/should_return_null_when_no_agent_is_selected.yaml +++ b/test/snapshots/rpc_agents/should_return_null_when_no_agent_is_selected.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_agents/should_select_and_get_current_agent.yaml b/test/snapshots/rpc_agents/should_select_and_get_current_agent.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_agents/should_select_and_get_current_agent.yaml +++ b/test/snapshots/rpc_agents/should_select_and_get_current_agent.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_event_side_effects/should_allow_session_use_after_truncate.yaml b/test/snapshots/rpc_event_side_effects/should_allow_session_use_after_truncate.yaml index 7c58a8da96..200cc28dba 100644 --- a/test/snapshots/rpc_event_side_effects/should_allow_session_use_after_truncate.yaml +++ b/test/snapshots/rpc_event_side_effects/should_allow_session_use_after_truncate.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/rpc_event_side_effects/should_emit_mode_changed_event_when_mode_set.yaml b/test/snapshots/rpc_event_side_effects/should_emit_mode_changed_event_when_mode_set.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_event_side_effects/should_emit_mode_changed_event_when_mode_set.yaml +++ b/test/snapshots/rpc_event_side_effects/should_emit_mode_changed_event_when_mode_set.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_event_side_effects/should_emit_plan_changed_event_for_update_and_delete.yaml b/test/snapshots/rpc_event_side_effects/should_emit_plan_changed_event_for_update_and_delete.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_event_side_effects/should_emit_plan_changed_event_for_update_and_delete.yaml +++ b/test/snapshots/rpc_event_side_effects/should_emit_plan_changed_event_for_update_and_delete.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_event_side_effects/should_emit_plan_changed_update_operation_on_second_update.yaml b/test/snapshots/rpc_event_side_effects/should_emit_plan_changed_update_operation_on_second_update.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_event_side_effects/should_emit_plan_changed_update_operation_on_second_update.yaml +++ b/test/snapshots/rpc_event_side_effects/should_emit_plan_changed_update_operation_on_second_update.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_event_side_effects/should_emit_snapshot_rewind_event_and_remove_events_on_truncate.yaml b/test/snapshots/rpc_event_side_effects/should_emit_snapshot_rewind_event_and_remove_events_on_truncate.yaml index 7c58a8da96..200cc28dba 100644 --- a/test/snapshots/rpc_event_side_effects/should_emit_snapshot_rewind_event_and_remove_events_on_truncate.yaml +++ b/test/snapshots/rpc_event_side_effects/should_emit_snapshot_rewind_event_and_remove_events_on_truncate.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/rpc_event_side_effects/should_emit_title_changed_event_when_name_set.yaml b/test/snapshots/rpc_event_side_effects/should_emit_title_changed_event_when_name_set.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_event_side_effects/should_emit_title_changed_event_when_name_set.yaml +++ b/test/snapshots/rpc_event_side_effects/should_emit_title_changed_event_when_name_set.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_event_side_effects/should_emit_workspace_file_changed_event_when_file_created.yaml b/test/snapshots/rpc_event_side_effects/should_emit_workspace_file_changed_event_when_file_created.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_event_side_effects/should_emit_workspace_file_changed_event_when_file_created.yaml +++ b/test/snapshots/rpc_event_side_effects/should_emit_workspace_file_changed_event_when_file_created.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_mcp_and_skills/should_list_and_toggle_session_skills.yaml b/test/snapshots/rpc_mcp_and_skills/should_list_and_toggle_session_skills.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_mcp_and_skills/should_list_and_toggle_session_skills.yaml +++ b/test/snapshots/rpc_mcp_and_skills/should_list_and_toggle_session_skills.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_mcp_and_skills/should_list_extensions.yaml b/test/snapshots/rpc_mcp_and_skills/should_list_extensions.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_mcp_and_skills/should_list_extensions.yaml +++ b/test/snapshots/rpc_mcp_and_skills/should_list_extensions.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_mcp_and_skills/should_list_mcp_servers_with_configured_server.yaml b/test/snapshots/rpc_mcp_and_skills/should_list_mcp_servers_with_configured_server.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_mcp_and_skills/should_list_mcp_servers_with_configured_server.yaml +++ b/test/snapshots/rpc_mcp_and_skills/should_list_mcp_servers_with_configured_server.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_mcp_and_skills/should_list_plugins.yaml b/test/snapshots/rpc_mcp_and_skills/should_list_plugins.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_mcp_and_skills/should_list_plugins.yaml +++ b/test/snapshots/rpc_mcp_and_skills/should_list_plugins.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_mcp_and_skills/should_reload_session_skills.yaml b/test/snapshots/rpc_mcp_and_skills/should_reload_session_skills.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_mcp_and_skills/should_reload_session_skills.yaml +++ b/test/snapshots/rpc_mcp_and_skills/should_reload_session_skills.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_mcp_and_skills/should_report_error_when_extensions_are_not_available.yaml b/test/snapshots/rpc_mcp_and_skills/should_report_error_when_extensions_are_not_available.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_mcp_and_skills/should_report_error_when_extensions_are_not_available.yaml +++ b/test/snapshots/rpc_mcp_and_skills/should_report_error_when_extensions_are_not_available.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_mcp_and_skills/should_report_error_when_mcp_host_is_not_initialized.yaml b/test/snapshots/rpc_mcp_and_skills/should_report_error_when_mcp_host_is_not_initialized.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_mcp_and_skills/should_report_error_when_mcp_host_is_not_initialized.yaml +++ b/test/snapshots/rpc_mcp_and_skills/should_report_error_when_mcp_host_is_not_initialized.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_mcp_and_skills/should_report_error_when_mcp_oauth_server_is_not_configured.yaml b/test/snapshots/rpc_mcp_and_skills/should_report_error_when_mcp_oauth_server_is_not_configured.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_mcp_and_skills/should_report_error_when_mcp_oauth_server_is_not_configured.yaml +++ b/test/snapshots/rpc_mcp_and_skills/should_report_error_when_mcp_oauth_server_is_not_configured.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_mcp_and_skills/should_report_error_when_mcp_oauth_server_is_not_remote.yaml b/test/snapshots/rpc_mcp_and_skills/should_report_error_when_mcp_oauth_server_is_not_remote.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_mcp_and_skills/should_report_error_when_mcp_oauth_server_is_not_remote.yaml +++ b/test/snapshots/rpc_mcp_and_skills/should_report_error_when_mcp_oauth_server_is_not_remote.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_mcp_config/should_call_server_mcp_config_rpcs.yaml b/test/snapshots/rpc_mcp_config/should_call_server_mcp_config_rpcs.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_mcp_config/should_call_server_mcp_config_rpcs.yaml +++ b/test/snapshots/rpc_mcp_config/should_call_server_mcp_config_rpcs.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_mcp_config/should_round_trip_http_mcp_oauth_config_rpc.yaml b/test/snapshots/rpc_mcp_config/should_round_trip_http_mcp_oauth_config_rpc.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_mcp_config/should_round_trip_http_mcp_oauth_config_rpc.yaml +++ b/test/snapshots/rpc_mcp_config/should_round_trip_http_mcp_oauth_config_rpc.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_mcp_lifecycle/should_configure_github_mcp_server.yaml b/test/snapshots/rpc_mcp_lifecycle/should_configure_github_mcp_server.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_mcp_lifecycle/should_configure_github_mcp_server.yaml +++ b/test/snapshots/rpc_mcp_lifecycle/should_configure_github_mcp_server.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_mcp_lifecycle/should_list_tools_and_report_running_status_for_connected_server.yaml b/test/snapshots/rpc_mcp_lifecycle/should_list_tools_and_report_running_status_for_connected_server.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_mcp_lifecycle/should_list_tools_and_report_running_status_for_connected_server.yaml +++ b/test/snapshots/rpc_mcp_lifecycle/should_list_tools_and_report_running_status_for_connected_server.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_mcp_lifecycle/should_reload_mcp_servers_with_config.yaml b/test/snapshots/rpc_mcp_lifecycle/should_reload_mcp_servers_with_config.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_mcp_lifecycle/should_reload_mcp_servers_with_config.yaml +++ b/test/snapshots/rpc_mcp_lifecycle/should_reload_mcp_servers_with_config.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_mcp_lifecycle/should_start_and_restart_mcp_server.yaml b/test/snapshots/rpc_mcp_lifecycle/should_start_and_restart_mcp_server.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_mcp_lifecycle/should_start_and_restart_mcp_server.yaml +++ b/test/snapshots/rpc_mcp_lifecycle/should_start_and_restart_mcp_server.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_mcp_lifecycle/should_stop_running_mcp_server.yaml b/test/snapshots/rpc_mcp_lifecycle/should_stop_running_mcp_server.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_mcp_lifecycle/should_stop_running_mcp_server.yaml +++ b/test/snapshots/rpc_mcp_lifecycle/should_stop_running_mcp_server.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_mcp_lifecycle/should_throw_when_listing_tools_for_unconnected_server.yaml b/test/snapshots/rpc_mcp_lifecycle/should_throw_when_listing_tools_for_unconnected_server.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_mcp_lifecycle/should_throw_when_listing_tools_for_unconnected_server.yaml +++ b/test/snapshots/rpc_mcp_lifecycle/should_throw_when_listing_tools_for_unconnected_server.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_server/should_call_rpc_account_get_quota_when_authenticated.yaml b/test/snapshots/rpc_server/should_call_rpc_account_get_quota_when_authenticated.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_server/should_call_rpc_account_get_quota_when_authenticated.yaml +++ b/test/snapshots/rpc_server/should_call_rpc_account_get_quota_when_authenticated.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_server/should_call_rpc_models_list_with_typed_result.yaml b/test/snapshots/rpc_server/should_call_rpc_models_list_with_typed_result.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_server/should_call_rpc_models_list_with_typed_result.yaml +++ b/test/snapshots/rpc_server/should_call_rpc_models_list_with_typed_result.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_server/should_call_rpc_ping_with_typed_params_and_result.yaml b/test/snapshots/rpc_server/should_call_rpc_ping_with_typed_params_and_result.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_server/should_call_rpc_ping_with_typed_params_and_result.yaml +++ b/test/snapshots/rpc_server/should_call_rpc_ping_with_typed_params_and_result.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_server/should_call_rpc_tools_list_with_typed_result.yaml b/test/snapshots/rpc_server/should_call_rpc_tools_list_with_typed_result.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_server/should_call_rpc_tools_list_with_typed_result.yaml +++ b/test/snapshots/rpc_server/should_call_rpc_tools_list_with_typed_result.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_server/should_discover_server_mcp_and_skills.yaml b/test/snapshots/rpc_server/should_discover_server_mcp_and_skills.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_server/should_discover_server_mcp_and_skills.yaml +++ b/test/snapshots/rpc_server/should_discover_server_mcp_and_skills.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_server_misc/should_get_set_and_clear_user_settings.yaml b/test/snapshots/rpc_server_misc/should_get_set_and_clear_user_settings.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_server_misc/should_get_set_and_clear_user_settings.yaml +++ b/test/snapshots/rpc_server_misc/should_get_set_and_clear_user_settings.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_server_misc/should_login_list_getcurrentauth_and_logout_account.yaml b/test/snapshots/rpc_server_misc/should_login_list_getcurrentauth_and_logout_account.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_server_misc/should_login_list_getcurrentauth_and_logout_account.yaml +++ b/test/snapshots/rpc_server_misc/should_login_list_getcurrentauth_and_logout_account.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_server_misc/should_reject_send_attachments_from_non_extension_connection.yaml b/test/snapshots/rpc_server_misc/should_reject_send_attachments_from_non_extension_connection.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_server_misc/should_reject_send_attachments_from_non_extension_connection.yaml +++ b/test/snapshots/rpc_server_misc/should_reject_send_attachments_from_non_extension_connection.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_server_misc/should_reload_user_settings.yaml b/test/snapshots/rpc_server_misc/should_reload_user_settings.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_server_misc/should_reload_user_settings.yaml +++ b/test/snapshots/rpc_server_misc/should_reload_user_settings.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_server_misc/should_report_agent_registry_spawn_gate_closed.yaml b/test/snapshots/rpc_server_misc/should_report_agent_registry_spawn_gate_closed.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_server_misc/should_report_agent_registry_spawn_gate_closed.yaml +++ b/test/snapshots/rpc_server_misc/should_report_agent_registry_spawn_gate_closed.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_server_misc/should_report_not_found_when_opening_session_without_context.yaml b/test/snapshots/rpc_server_misc/should_report_not_found_when_opening_session_without_context.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_server_misc/should_report_not_found_when_opening_session_without_context.yaml +++ b/test/snapshots/rpc_server_misc/should_report_not_found_when_opening_session_without_context.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_server_misc/should_shut_down_owned_runtime.yaml b/test/snapshots/rpc_server_misc/should_shut_down_owned_runtime.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_server_misc/should_shut_down_owned_runtime.yaml +++ b/test/snapshots/rpc_server_misc/should_shut_down_owned_runtime.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_server_plugins/should_enable_and_disable_marketplace_plugin.yaml b/test/snapshots/rpc_server_plugins/should_enable_and_disable_marketplace_plugin.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_server_plugins/should_enable_and_disable_marketplace_plugin.yaml +++ b/test/snapshots/rpc_server_plugins/should_enable_and_disable_marketplace_plugin.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_server_plugins/should_install_direct_local_plugin_with_deprecation_warning.yaml b/test/snapshots/rpc_server_plugins/should_install_direct_local_plugin_with_deprecation_warning.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_server_plugins/should_install_direct_local_plugin_with_deprecation_warning.yaml +++ b/test/snapshots/rpc_server_plugins/should_install_direct_local_plugin_with_deprecation_warning.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_server_plugins/should_install_list_and_uninstall_plugin_from_local_marketplace.yaml b/test/snapshots/rpc_server_plugins/should_install_list_and_uninstall_plugin_from_local_marketplace.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_server_plugins/should_install_list_and_uninstall_plugin_from_local_marketplace.yaml +++ b/test/snapshots/rpc_server_plugins/should_install_list_and_uninstall_plugin_from_local_marketplace.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_server_plugins/should_list_browse_refresh_and_remove_local_marketplace.yaml b/test/snapshots/rpc_server_plugins/should_list_browse_refresh_and_remove_local_marketplace.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_server_plugins/should_list_browse_refresh_and_remove_local_marketplace.yaml +++ b/test/snapshots/rpc_server_plugins/should_list_browse_refresh_and_remove_local_marketplace.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_server_plugins/should_reload_mcp_config_cache.yaml b/test/snapshots/rpc_server_plugins/should_reload_mcp_config_cache.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_server_plugins/should_reload_mcp_config_cache.yaml +++ b/test/snapshots/rpc_server_plugins/should_reload_mcp_config_cache.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_server_plugins/should_update_all_installed_plugins.yaml b/test/snapshots/rpc_server_plugins/should_update_all_installed_plugins.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_server_plugins/should_update_all_installed_plugins.yaml +++ b/test/snapshots/rpc_server_plugins/should_update_all_installed_plugins.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_server_plugins/should_update_single_marketplace_plugin.yaml b/test/snapshots/rpc_server_plugins/should_update_single_marketplace_plugin.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_server_plugins/should_update_single_marketplace_plugin.yaml +++ b/test/snapshots/rpc_server_plugins/should_update_single_marketplace_plugin.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_server_remote_control/should_reach_runtime_when_starting_remote_control_for_unknown_session.yaml b/test/snapshots/rpc_server_remote_control/should_reach_runtime_when_starting_remote_control_for_unknown_session.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_server_remote_control/should_reach_runtime_when_starting_remote_control_for_unknown_session.yaml +++ b/test/snapshots/rpc_server_remote_control/should_reach_runtime_when_starting_remote_control_for_unknown_session.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_server_remote_control/should_reject_transfer_when_off_with_compare_and_swap.yaml b/test/snapshots/rpc_server_remote_control/should_reject_transfer_when_off_with_compare_and_swap.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_server_remote_control/should_reject_transfer_when_off_with_compare_and_swap.yaml +++ b/test/snapshots/rpc_server_remote_control/should_reject_transfer_when_off_with_compare_and_swap.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_server_remote_control/should_report_not_stopped_when_remote_control_is_off.yaml b/test/snapshots/rpc_server_remote_control/should_report_not_stopped_when_remote_control_is_off.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_server_remote_control/should_report_not_stopped_when_remote_control_is_off.yaml +++ b/test/snapshots/rpc_server_remote_control/should_report_not_stopped_when_remote_control_is_off.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_server_remote_control/should_report_remote_control_status_as_off.yaml b/test/snapshots/rpc_server_remote_control/should_report_remote_control_status_as_off.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_server_remote_control/should_report_remote_control_status_as_off.yaml +++ b/test/snapshots/rpc_server_remote_control/should_report_remote_control_status_as_off.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_server_remote_control/should_treat_set_steering_as_no_op_when_off.yaml b/test/snapshots/rpc_server_remote_control/should_treat_set_steering_as_no_op_when_off.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_server_remote_control/should_treat_set_steering_as_no_op_when_off.yaml +++ b/test/snapshots/rpc_server_remote_control/should_treat_set_steering_as_no_op_when_off.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_session_state/should_call_session_rpc_model_getcurrent.yaml b/test/snapshots/rpc_session_state/should_call_session_rpc_model_getcurrent.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_session_state/should_call_session_rpc_model_getcurrent.yaml +++ b/test/snapshots/rpc_session_state/should_call_session_rpc_model_getcurrent.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_session_state/should_call_session_rpc_model_switchto.yaml b/test/snapshots/rpc_session_state/should_call_session_rpc_model_switchto.yaml index b276b6a398..9011c931e7 100644 --- a/test/snapshots/rpc_session_state/should_call_session_rpc_model_switchto.yaml +++ b/test/snapshots/rpc_session_state/should_call_session_rpc_model_switchto.yaml @@ -1,4 +1,4 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 - gpt-5.4 conversations: [] diff --git a/test/snapshots/rpc_session_state/should_call_session_usage_and_permission_rpcs.yaml b/test/snapshots/rpc_session_state/should_call_session_usage_and_permission_rpcs.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_session_state/should_call_session_usage_and_permission_rpcs.yaml +++ b/test/snapshots/rpc_session_state/should_call_session_usage_and_permission_rpcs.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_session_state/should_call_workspace_file_rpc_methods.yaml b/test/snapshots/rpc_session_state/should_call_workspace_file_rpc_methods.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_session_state/should_call_workspace_file_rpc_methods.yaml +++ b/test/snapshots/rpc_session_state/should_call_workspace_file_rpc_methods.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_session_state/should_compact_session_history_after_messages.yaml b/test/snapshots/rpc_session_state/should_compact_session_history_after_messages.yaml index 001e828461..ad474d2472 100644 --- a/test/snapshots/rpc_session_state/should_compact_session_history_after_messages.yaml +++ b/test/snapshots/rpc_session_state/should_compact_session_history_after_messages.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/rpc_session_state/should_create_workspace_file_with_nested_path_auto_creating_dirs.yaml b/test/snapshots/rpc_session_state/should_create_workspace_file_with_nested_path_auto_creating_dirs.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_session_state/should_create_workspace_file_with_nested_path_auto_creating_dirs.yaml +++ b/test/snapshots/rpc_session_state/should_create_workspace_file_with_nested_path_auto_creating_dirs.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_session_state/should_emit_title_changed_event_each_time_name_set_is_called.yaml b/test/snapshots/rpc_session_state/should_emit_title_changed_event_each_time_name_set_is_called.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_session_state/should_emit_title_changed_event_each_time_name_set_is_called.yaml +++ b/test/snapshots/rpc_session_state/should_emit_title_changed_event_each_time_name_set_is_called.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_session_state/should_fork_session_to_event_id_excluding_boundary_event.yaml b/test/snapshots/rpc_session_state/should_fork_session_to_event_id_excluding_boundary_event.yaml index 76ba212c5e..c1223c8a20 100644 --- a/test/snapshots/rpc_session_state/should_fork_session_to_event_id_excluding_boundary_event.yaml +++ b/test/snapshots/rpc_session_state/should_fork_session_to_event_id_excluding_boundary_event.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/rpc_session_state/should_fork_session_with_persisted_messages.yaml b/test/snapshots/rpc_session_state/should_fork_session_with_persisted_messages.yaml index 2313bd1483..f6fc12e544 100644 --- a/test/snapshots/rpc_session_state/should_fork_session_with_persisted_messages.yaml +++ b/test/snapshots/rpc_session_state/should_fork_session_with_persisted_messages.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/rpc_session_state/should_get_and_set_session_metadata.yaml b/test/snapshots/rpc_session_state/should_get_and_set_session_metadata.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_session_state/should_get_and_set_session_metadata.yaml +++ b/test/snapshots/rpc_session_state/should_get_and_set_session_metadata.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_session_state/should_get_and_set_session_mode.yaml b/test/snapshots/rpc_session_state/should_get_and_set_session_mode.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_session_state/should_get_and_set_session_mode.yaml +++ b/test/snapshots/rpc_session_state/should_get_and_set_session_mode.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_session_state/should_handle_forking_session_without_persisted_events.yaml b/test/snapshots/rpc_session_state/should_handle_forking_session_without_persisted_events.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_session_state/should_handle_forking_session_without_persisted_events.yaml +++ b/test/snapshots/rpc_session_state/should_handle_forking_session_without_persisted_events.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_session_state/should_read_update_and_delete_plan.yaml b/test/snapshots/rpc_session_state/should_read_update_and_delete_plan.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_session_state/should_read_update_and_delete_plan.yaml +++ b/test/snapshots/rpc_session_state/should_read_update_and_delete_plan.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_session_state/should_reject_empty_or_whitespace_session_name.yaml b/test/snapshots/rpc_session_state/should_reject_empty_or_whitespace_session_name.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_session_state/should_reject_empty_or_whitespace_session_name.yaml +++ b/test/snapshots/rpc_session_state/should_reject_empty_or_whitespace_session_name.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_session_state/should_reject_workspace_file_path_traversal.yaml b/test/snapshots/rpc_session_state/should_reject_workspace_file_path_traversal.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_session_state/should_reject_workspace_file_path_traversal.yaml +++ b/test/snapshots/rpc_session_state/should_reject_workspace_file_path_traversal.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_session_state/should_report_error_reading_nonexistent_workspace_file.yaml b/test/snapshots/rpc_session_state/should_report_error_reading_nonexistent_workspace_file.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_session_state/should_report_error_reading_nonexistent_workspace_file.yaml +++ b/test/snapshots/rpc_session_state/should_report_error_reading_nonexistent_workspace_file.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_session_state/should_report_error_when_forking_session_to_unknown_event_id.yaml b/test/snapshots/rpc_session_state/should_report_error_when_forking_session_to_unknown_event_id.yaml index 788c5b75f2..e59f571b4a 100644 --- a/test/snapshots/rpc_session_state/should_report_error_when_forking_session_to_unknown_event_id.yaml +++ b/test/snapshots/rpc_session_state/should_report_error_when_forking_session_to_unknown_event_id.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/rpc_session_state/should_report_implemented_errors_for_unsupported_session_rpc_paths.yaml b/test/snapshots/rpc_session_state/should_report_implemented_errors_for_unsupported_session_rpc_paths.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_session_state/should_report_implemented_errors_for_unsupported_session_rpc_paths.yaml +++ b/test/snapshots/rpc_session_state/should_report_implemented_errors_for_unsupported_session_rpc_paths.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] 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 index 6760888d7b..6ea625da4d 100644 --- 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 @@ -1,10 +1,20 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system content: ${system} - role: user - content: "Reply with exactly: RUST_CONTEXT_INFO" + content: "Use processing_barrier, then reply with exactly: RUST_CONTEXT_INFO" + - role: assistant + tool_calls: + - id: toolcall_0 + type: function + function: + name: processing_barrier + arguments: "{}" + - role: tool + tool_call_id: toolcall_0 + content: PROCESSING_OBSERVED - role: assistant content: RUST_CONTEXT_INFO diff --git a/test/snapshots/rpc_session_state/should_set_and_get_each_session_mode_value.yaml b/test/snapshots/rpc_session_state/should_set_and_get_each_session_mode_value.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_session_state/should_set_and_get_each_session_mode_value.yaml +++ b/test/snapshots/rpc_session_state/should_set_and_get_each_session_mode_value.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_session_state/should_update_existing_workspace_file_with_update_operation.yaml b/test/snapshots/rpc_session_state/should_update_existing_workspace_file_with_update_operation.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_session_state/should_update_existing_workspace_file_with_update_operation.yaml +++ b/test/snapshots/rpc_session_state/should_update_existing_workspace_file_with_update_operation.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_session_state_extras/should_add_byok_provider_and_model_at_runtime.yaml b/test/snapshots/rpc_session_state_extras/should_add_byok_provider_and_model_at_runtime.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_session_state_extras/should_add_byok_provider_and_model_at_runtime.yaml +++ b/test/snapshots/rpc_session_state_extras/should_add_byok_provider_and_model_at_runtime.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_session_state_extras/should_get_and_set_allowall_permissions.yaml b/test/snapshots/rpc_session_state_extras/should_get_and_set_allowall_permissions.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_session_state_extras/should_get_and_set_allowall_permissions.yaml +++ b/test/snapshots/rpc_session_state_extras/should_get_and_set_allowall_permissions.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_session_state_extras/should_get_context_attribution_and_heaviest_messages_after_turn.yaml b/test/snapshots/rpc_session_state_extras/should_get_context_attribution_and_heaviest_messages_after_turn.yaml index c4798dc83d..24c4d6bbf2 100644 --- a/test/snapshots/rpc_session_state_extras/should_get_context_attribution_and_heaviest_messages_after_turn.yaml +++ b/test/snapshots/rpc_session_state_extras/should_get_context_attribution_and_heaviest_messages_after_turn.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/rpc_session_state_extras/should_get_current_tool_metadata_after_initialization.yaml b/test/snapshots/rpc_session_state_extras/should_get_current_tool_metadata_after_initialization.yaml index 73f0499002..33bb479064 100644 --- a/test/snapshots/rpc_session_state_extras/should_get_current_tool_metadata_after_initialization.yaml +++ b/test/snapshots/rpc_session_state_extras/should_get_current_tool_metadata_after_initialization.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/rpc_session_state_extras/should_get_telemetry_engagement_id.yaml b/test/snapshots/rpc_session_state_extras/should_get_telemetry_engagement_id.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_session_state_extras/should_get_telemetry_engagement_id.yaml +++ b/test/snapshots/rpc_session_state_extras/should_get_telemetry_engagement_id.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_session_state_extras/should_list_models_for_session.yaml b/test/snapshots/rpc_session_state_extras/should_list_models_for_session.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_session_state_extras/should_list_models_for_session.yaml +++ b/test/snapshots/rpc_session_state_extras/should_list_models_for_session.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_session_state_extras/should_read_empty_sql_todos_for_fresh_session.yaml b/test/snapshots/rpc_session_state_extras/should_read_empty_sql_todos_for_fresh_session.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_session_state_extras/should_read_empty_sql_todos_for_fresh_session.yaml +++ b/test/snapshots/rpc_session_state_extras/should_read_empty_sql_todos_for_fresh_session.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_session_state_extras/should_reload_session_plugins.yaml b/test/snapshots/rpc_session_state_extras/should_reload_session_plugins.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_session_state_extras/should_reload_session_plugins.yaml +++ b/test/snapshots/rpc_session_state_extras/should_reload_session_plugins.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_session_state_extras/should_report_session_activity_when_idle.yaml b/test/snapshots/rpc_session_state_extras/should_report_session_activity_when_idle.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_session_state_extras/should_report_session_activity_when_idle.yaml +++ b/test/snapshots/rpc_session_state_extras/should_report_session_activity_when_idle.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_session_state_extras/should_report_visibility_as_unsynced_for_local_session.yaml b/test/snapshots/rpc_session_state_extras/should_report_visibility_as_unsynced_for_local_session.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_session_state_extras/should_report_visibility_as_unsynced_for_local_session.yaml +++ b/test/snapshots/rpc_session_state_extras/should_report_visibility_as_unsynced_for_local_session.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_session_state_extras/should_return_empty_completions_when_host_does_not_provide_them.yaml b/test/snapshots/rpc_session_state_extras/should_return_empty_completions_when_host_does_not_provide_them.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_session_state_extras/should_return_empty_completions_when_host_does_not_provide_them.yaml +++ b/test/snapshots/rpc_session_state_extras/should_return_empty_completions_when_host_does_not_provide_them.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_session_state_extras/should_update_and_clear_live_subagent_settings.yaml b/test/snapshots/rpc_session_state_extras/should_update_and_clear_live_subagent_settings.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_session_state_extras/should_update_and_clear_live_subagent_settings.yaml +++ b/test/snapshots/rpc_session_state_extras/should_update_and_clear_live_subagent_settings.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_shell_and_fleet/should_execute_shell_command.yaml b/test/snapshots/rpc_shell_and_fleet/should_execute_shell_command.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_shell_and_fleet/should_execute_shell_command.yaml +++ b/test/snapshots/rpc_shell_and_fleet/should_execute_shell_command.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_shell_and_fleet/should_kill_shell_process.yaml b/test/snapshots/rpc_shell_and_fleet/should_kill_shell_process.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_shell_and_fleet/should_kill_shell_process.yaml +++ b/test/snapshots/rpc_shell_and_fleet/should_kill_shell_process.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_shell_and_fleet/should_start_fleet_and_complete_custom_tool_task.yaml b/test/snapshots/rpc_shell_and_fleet/should_start_fleet_and_complete_custom_tool_task.yaml index 65ced1e366..d2d8a272c1 100644 --- a/test/snapshots/rpc_shell_and_fleet/should_start_fleet_and_complete_custom_tool_task.yaml +++ b/test/snapshots/rpc_shell_and_fleet/should_start_fleet_and_complete_custom_tool_task.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/rpc_shell_edge_cases/shell_exec_with_custom_cwd_honors_override.yaml b/test/snapshots/rpc_shell_edge_cases/shell_exec_with_custom_cwd_honors_override.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_shell_edge_cases/shell_exec_with_custom_cwd_honors_override.yaml +++ b/test/snapshots/rpc_shell_edge_cases/shell_exec_with_custom_cwd_honors_override.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_shell_edge_cases/shell_exec_with_large_stdout_cleans_up.yaml b/test/snapshots/rpc_shell_edge_cases/shell_exec_with_large_stdout_cleans_up.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_shell_edge_cases/shell_exec_with_large_stdout_cleans_up.yaml +++ b/test/snapshots/rpc_shell_edge_cases/shell_exec_with_large_stdout_cleans_up.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_shell_edge_cases/shell_exec_with_nonexistent_command_returns_processid_and_cleans_up.yaml b/test/snapshots/rpc_shell_edge_cases/shell_exec_with_nonexistent_command_returns_processid_and_cleans_up.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_shell_edge_cases/shell_exec_with_nonexistent_command_returns_processid_and_cleans_up.yaml +++ b/test/snapshots/rpc_shell_edge_cases/shell_exec_with_nonexistent_command_returns_processid_and_cleans_up.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_shell_edge_cases/shell_exec_with_stderr_output_cleans_up.yaml b/test/snapshots/rpc_shell_edge_cases/shell_exec_with_stderr_output_cleans_up.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_shell_edge_cases/shell_exec_with_stderr_output_cleans_up.yaml +++ b/test/snapshots/rpc_shell_edge_cases/shell_exec_with_stderr_output_cleans_up.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_shell_edge_cases/shell_exec_with_timeout_kills_long_running_command.yaml b/test/snapshots/rpc_shell_edge_cases/shell_exec_with_timeout_kills_long_running_command.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_shell_edge_cases/shell_exec_with_timeout_kills_long_running_command.yaml +++ b/test/snapshots/rpc_shell_edge_cases/shell_exec_with_timeout_kills_long_running_command.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_shell_edge_cases/shell_kill_cleans_up_after_terminating_signal.yaml b/test/snapshots/rpc_shell_edge_cases/shell_kill_cleans_up_after_terminating_signal.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_shell_edge_cases/shell_kill_cleans_up_after_terminating_signal.yaml +++ b/test/snapshots/rpc_shell_edge_cases/shell_kill_cleans_up_after_terminating_signal.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_shell_edge_cases/shell_kill_unknown_processid_returns_false.yaml b/test/snapshots/rpc_shell_edge_cases/shell_kill_unknown_processid_returns_false.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_shell_edge_cases/shell_kill_unknown_processid_returns_false.yaml +++ b/test/snapshots/rpc_shell_edge_cases/shell_kill_unknown_processid_returns_false.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_shell_user_requested/should_cancel_user_requested_shell_command.yaml b/test/snapshots/rpc_shell_user_requested/should_cancel_user_requested_shell_command.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_shell_user_requested/should_cancel_user_requested_shell_command.yaml +++ b/test/snapshots/rpc_shell_user_requested/should_cancel_user_requested_shell_command.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_shell_user_requested/should_execute_user_requested_shell_command.yaml b/test/snapshots/rpc_shell_user_requested/should_execute_user_requested_shell_command.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_shell_user_requested/should_execute_user_requested_shell_command.yaml +++ b/test/snapshots/rpc_shell_user_requested/should_execute_user_requested_shell_command.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_tasks_and_handlers/should_list_task_state_and_return_false_for_missing_task_operations.yaml b/test/snapshots/rpc_tasks_and_handlers/should_list_task_state_and_return_false_for_missing_task_operations.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_tasks_and_handlers/should_list_task_state_and_return_false_for_missing_task_operations.yaml +++ b/test/snapshots/rpc_tasks_and_handlers/should_list_task_state_and_return_false_for_missing_task_operations.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_tasks_and_handlers/should_report_implemented_error_for_invalid_task_agent_model.yaml b/test/snapshots/rpc_tasks_and_handlers/should_report_implemented_error_for_invalid_task_agent_model.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_tasks_and_handlers/should_report_implemented_error_for_invalid_task_agent_model.yaml +++ b/test/snapshots/rpc_tasks_and_handlers/should_report_implemented_error_for_invalid_task_agent_model.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_tasks_and_handlers/should_report_implemented_error_for_missing_task_agent_type.yaml b/test/snapshots/rpc_tasks_and_handlers/should_report_implemented_error_for_missing_task_agent_type.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_tasks_and_handlers/should_report_implemented_error_for_missing_task_agent_type.yaml +++ b/test/snapshots/rpc_tasks_and_handlers/should_report_implemented_error_for_missing_task_agent_type.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_tasks_and_handlers/should_return_expected_results_for_missing_pending_handler_requestids.yaml b/test/snapshots/rpc_tasks_and_handlers/should_return_expected_results_for_missing_pending_handler_requestids.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/rpc_tasks_and_handlers/should_return_expected_results_for_missing_pending_handler_requestids.yaml +++ b/test/snapshots/rpc_tasks_and_handlers/should_return_expected_results_for_missing_pending_handler_requestids.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/rpc_tasks_and_handlers/should_start_background_agent_and_report_task_details.yaml b/test/snapshots/rpc_tasks_and_handlers/should_start_background_agent_and_report_task_details.yaml index 41bbe583d2..a9dbdd3751 100644 --- a/test/snapshots/rpc_tasks_and_handlers/should_start_background_agent_and_report_task_details.yaml +++ b/test/snapshots/rpc_tasks_and_handlers/should_start_background_agent_and_report_task_details.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/rpc_ui_ephemeral_query/should_answer_ephemeral_query.yaml b/test/snapshots/rpc_ui_ephemeral_query/should_answer_ephemeral_query.yaml index fec44be1fe..c5c1ce1c11 100644 --- a/test/snapshots/rpc_ui_ephemeral_query/should_answer_ephemeral_query.yaml +++ b/test/snapshots/rpc_ui_ephemeral_query/should_answer_ephemeral_query.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/rust_multi_client/both_clients_see_tool_request_and_completion_events.yaml b/test/snapshots/rust_multi_client/both_clients_see_tool_request_and_completion_events.yaml index 20eefc57a9..8bfc5f5053 100644 --- a/test/snapshots/rust_multi_client/both_clients_see_tool_request_and_completion_events.yaml +++ b/test/snapshots/rust_multi_client/both_clients_see_tool_request_and_completion_events.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/rust_multi_client/disconnecting_client_removes_its_tools.yaml b/test/snapshots/rust_multi_client/disconnecting_client_removes_its_tools.yaml index 192105ac7a..476d5aeb1c 100644 --- a/test/snapshots/rust_multi_client/disconnecting_client_removes_its_tools.yaml +++ b/test/snapshots/rust_multi_client/disconnecting_client_removes_its_tools.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/rust_multi_client/two_clients_register_different_tools_and_agent_uses_both.yaml b/test/snapshots/rust_multi_client/two_clients_register_different_tools_and_agent_uses_both.yaml index c97e969df6..57bc48df32 100644 --- a/test/snapshots/rust_multi_client/two_clients_register_different_tools_and_agent_uses_both.yaml +++ b/test/snapshots/rust_multi_client/two_clients_register_different_tools_and_agent_uses_both.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/disposeasync_from_handler_does_not_deadlock.yaml b/test/snapshots/session/disposeasync_from_handler_does_not_deadlock.yaml index 7c4d469970..dfead5f966 100644 --- a/test/snapshots/session/disposeasync_from_handler_does_not_deadlock.yaml +++ b/test/snapshots/session/disposeasync_from_handler_does_not_deadlock.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/handler_exception_does_not_halt_event_delivery.yaml b/test/snapshots/session/handler_exception_does_not_halt_event_delivery.yaml index 7c4d469970..dfead5f966 100644 --- a/test/snapshots/session/handler_exception_does_not_halt_event_delivery.yaml +++ b/test/snapshots/session/handler_exception_does_not_halt_event_delivery.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/resumes_a_persisted_session_from_a_new_client_when_an_mcp_oauth_handler_is_configured.yaml b/test/snapshots/session/resumes_a_persisted_session_from_a_new_client_when_an_mcp_oauth_handler_is_configured.yaml index 250402101b..c8aa45cfff 100644 --- a/test/snapshots/session/resumes_a_persisted_session_from_a_new_client_when_an_mcp_oauth_handler_is_configured.yaml +++ b/test/snapshots/session/resumes_a_persisted_session_from_a_new_client_when_an_mcp_oauth_handler_is_configured.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/send_returns_immediately_while_events_stream_in_background.yaml b/test/snapshots/session/send_returns_immediately_while_events_stream_in_background.yaml index c2e705ed2d..4ef08ab255 100644 --- a/test/snapshots/session/send_returns_immediately_while_events_stream_in_background.yaml +++ b/test/snapshots/session/send_returns_immediately_while_events_stream_in_background.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/sendandwait_blocks_until_session_idle_and_returns_final_assistant_message.yaml b/test/snapshots/session/sendandwait_blocks_until_session_idle_and_returns_final_assistant_message.yaml index 9fe2fcd070..7cd9232226 100644 --- a/test/snapshots/session/sendandwait_blocks_until_session_idle_and_returns_final_assistant_message.yaml +++ b/test/snapshots/session/sendandwait_blocks_until_session_idle_and_returns_final_assistant_message.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/sendandwait_throws_on_timeout.yaml b/test/snapshots/session/sendandwait_throws_on_timeout.yaml index 0e019bdad7..fc736fe650 100644 --- a/test/snapshots/session/sendandwait_throws_on_timeout.yaml +++ b/test/snapshots/session/sendandwait_throws_on_timeout.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/sendandwait_throws_operationcanceledexception_when_token_cancelled.yaml b/test/snapshots/session/sendandwait_throws_operationcanceledexception_when_token_cancelled.yaml index a03140fa17..7e1a256daa 100644 --- a/test/snapshots/session/sendandwait_throws_operationcanceledexception_when_token_cancelled.yaml +++ b/test/snapshots/session/sendandwait_throws_operationcanceledexception_when_token_cancelled.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/should_abort_a_session.yaml b/test/snapshots/session/should_abort_a_session.yaml index dbbbd32aa7..4ec09b51f3 100644 --- a/test/snapshots/session/should_abort_a_session.yaml +++ b/test/snapshots/session/should_abort_a_session.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/should_accept_blob_attachments.yaml b/test/snapshots/session/should_accept_blob_attachments.yaml index 1cca7142db..4caf7c8707 100644 --- a/test/snapshots/session/should_accept_blob_attachments.yaml +++ b/test/snapshots/session/should_accept_blob_attachments.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/should_create_a_session_with_appended_systemmessage_config.yaml b/test/snapshots/session/should_create_a_session_with_appended_systemmessage_config.yaml index 3bf4a39f05..a0a9f1c391 100644 --- a/test/snapshots/session/should_create_a_session_with_appended_systemmessage_config.yaml +++ b/test/snapshots/session/should_create_a_session_with_appended_systemmessage_config.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/should_create_a_session_with_availabletools.yaml b/test/snapshots/session/should_create_a_session_with_availabletools.yaml index 250402101b..c8aa45cfff 100644 --- a/test/snapshots/session/should_create_a_session_with_availabletools.yaml +++ b/test/snapshots/session/should_create_a_session_with_availabletools.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/should_create_a_session_with_customized_systemmessage_config.yaml b/test/snapshots/session/should_create_a_session_with_customized_systemmessage_config.yaml index f3ce077a62..50d9cbecfa 100644 --- a/test/snapshots/session/should_create_a_session_with_customized_systemmessage_config.yaml +++ b/test/snapshots/session/should_create_a_session_with_customized_systemmessage_config.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system @@ -8,8 +8,8 @@ conversations: content: Who are you? - role: assistant content: >- - I'm **GitHub Copilot CLI**, a terminal assistant built by GitHub. I'm powered by claude-sonnet-4.5 (model ID: - claude-sonnet-4.5). + I'm **GitHub Copilot CLI**, a terminal assistant built by GitHub. I'm powered by claude-sonnet-5 (model ID: + claude-sonnet-5). I'm here to help you with software engineering tasks, including: diff --git a/test/snapshots/session/should_create_a_session_with_defaultagent_excludedtools.yaml b/test/snapshots/session/should_create_a_session_with_defaultagent_excludedtools.yaml index 250402101b..c8aa45cfff 100644 --- a/test/snapshots/session/should_create_a_session_with_defaultagent_excludedtools.yaml +++ b/test/snapshots/session/should_create_a_session_with_defaultagent_excludedtools.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/should_create_a_session_with_excludedtools.yaml b/test/snapshots/session/should_create_a_session_with_excludedtools.yaml index 250402101b..c8aa45cfff 100644 --- a/test/snapshots/session/should_create_a_session_with_excludedtools.yaml +++ b/test/snapshots/session/should_create_a_session_with_excludedtools.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/should_create_a_session_with_replaced_systemmessage_config.yaml b/test/snapshots/session/should_create_a_session_with_replaced_systemmessage_config.yaml index ebe0881b90..88d25dc817 100644 --- a/test/snapshots/session/should_create_a_session_with_replaced_systemmessage_config.yaml +++ b/test/snapshots/session/should_create_a_session_with_replaced_systemmessage_config.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/should_create_session_with_custom_config_dir.yaml b/test/snapshots/session/should_create_session_with_custom_config_dir.yaml index 250402101b..c8aa45cfff 100644 --- a/test/snapshots/session/should_create_session_with_custom_config_dir.yaml +++ b/test/snapshots/session/should_create_session_with_custom_config_dir.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/should_create_session_with_custom_tool.yaml b/test/snapshots/session/should_create_session_with_custom_tool.yaml index 4ae6dab721..3a08cbd9fe 100644 --- a/test/snapshots/session/should_create_session_with_custom_tool.yaml +++ b/test/snapshots/session/should_create_session_with_custom_tool.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/should_delete_session.yaml b/test/snapshots/session/should_delete_session.yaml index fb8249d325..72176060ce 100644 --- a/test/snapshots/session/should_delete_session.yaml +++ b/test/snapshots/session/should_delete_session.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/should_get_last_session_id.yaml b/test/snapshots/session/should_get_last_session_id.yaml index 3b9da534c2..62da2b03a2 100644 --- a/test/snapshots/session/should_get_last_session_id.yaml +++ b/test/snapshots/session/should_get_last_session_id.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/should_get_session_metadata.yaml b/test/snapshots/session/should_get_session_metadata.yaml index b326528e1d..f9b44b833a 100644 --- a/test/snapshots/session/should_get_session_metadata.yaml +++ b/test/snapshots/session/should_get_session_metadata.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/should_get_session_metadata_by_id.yaml b/test/snapshots/session/should_get_session_metadata_by_id.yaml index b326528e1d..f9b44b833a 100644 --- a/test/snapshots/session/should_get_session_metadata_by_id.yaml +++ b/test/snapshots/session/should_get_session_metadata_by_id.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/should_have_stateful_conversation.yaml b/test/snapshots/session/should_have_stateful_conversation.yaml index 39d3c5acc5..1309304fc4 100644 --- a/test/snapshots/session/should_have_stateful_conversation.yaml +++ b/test/snapshots/session/should_have_stateful_conversation.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/should_list_sessions.yaml b/test/snapshots/session/should_list_sessions.yaml index 4683506570..d2c3ebdd4f 100644 --- a/test/snapshots/session/should_list_sessions.yaml +++ b/test/snapshots/session/should_list_sessions.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/should_list_sessions_with_context.yaml b/test/snapshots/session/should_list_sessions_with_context.yaml index 8486832a46..8e3aa9d94c 100644 --- a/test/snapshots/session/should_list_sessions_with_context.yaml +++ b/test/snapshots/session/should_list_sessions_with_context.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/should_log_messages_at_various_levels.yaml b/test/snapshots/session/should_log_messages_at_various_levels.yaml index 0e019bdad7..fc736fe650 100644 --- a/test/snapshots/session/should_log_messages_at_various_levels.yaml +++ b/test/snapshots/session/should_log_messages_at_various_levels.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/should_receive_session_events.yaml b/test/snapshots/session/should_receive_session_events.yaml index 229563a4cf..406c34f455 100644 --- a/test/snapshots/session/should_receive_session_events.yaml +++ b/test/snapshots/session/should_receive_session_events.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/should_resume_a_session_using_a_new_client.yaml b/test/snapshots/session/should_resume_a_session_using_a_new_client.yaml index bd02858372..667d0c3528 100644 --- a/test/snapshots/session/should_resume_a_session_using_a_new_client.yaml +++ b/test/snapshots/session/should_resume_a_session_using_a_new_client.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/should_resume_a_session_using_the_same_client.yaml b/test/snapshots/session/should_resume_a_session_using_the_same_client.yaml index b012e26ea8..ff54ee73da 100644 --- a/test/snapshots/session/should_resume_a_session_using_the_same_client.yaml +++ b/test/snapshots/session/should_resume_a_session_using_the_same_client.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/should_send_with_custom_requestheaders.yaml b/test/snapshots/session/should_send_with_custom_requestheaders.yaml index f9918fa133..fe4181ccd2 100644 --- a/test/snapshots/session/should_send_with_custom_requestheaders.yaml +++ b/test/snapshots/session/should_send_with_custom_requestheaders.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/should_send_with_directory_attachment.yaml b/test/snapshots/session/should_send_with_directory_attachment.yaml index aa410c9295..f2e8835c5d 100644 --- a/test/snapshots/session/should_send_with_directory_attachment.yaml +++ b/test/snapshots/session/should_send_with_directory_attachment.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/should_send_with_file_attachment.yaml b/test/snapshots/session/should_send_with_file_attachment.yaml index 23e05d946b..f2acea13fb 100644 --- a/test/snapshots/session/should_send_with_file_attachment.yaml +++ b/test/snapshots/session/should_send_with_file_attachment.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system @@ -58,7 +58,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. FILE_ATTACHMENT_SENTINEL + content: FILE_ATTACHMENT_SENTINEL - role: assistant content: |- The file contains: diff --git a/test/snapshots/session/should_send_with_github_reference_attachment.yaml b/test/snapshots/session/should_send_with_github_reference_attachment.yaml index 6e298de554..851f34c50f 100644 --- a/test/snapshots/session/should_send_with_github_reference_attachment.yaml +++ b/test/snapshots/session/should_send_with_github_reference_attachment.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/should_send_with_mode_property.yaml b/test/snapshots/session/should_send_with_mode_property.yaml index 4fec86c7f4..e6639acbdb 100644 --- a/test/snapshots/session/should_send_with_mode_property.yaml +++ b/test/snapshots/session/should_send_with_mode_property.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/should_send_with_selection_attachment.yaml b/test/snapshots/session/should_send_with_selection_attachment.yaml index ad6a2a28e8..c46f3d679b 100644 --- a/test/snapshots/session/should_send_with_selection_attachment.yaml +++ b/test/snapshots/session/should_send_with_selection_attachment.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/should_set_model_on_existing_session.yaml b/test/snapshots/session/should_set_model_on_existing_session.yaml index 0e019bdad7..fc736fe650 100644 --- a/test/snapshots/session/should_set_model_on_existing_session.yaml +++ b/test/snapshots/session/should_set_model_on_existing_session.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session/should_set_model_with_reasoningeffort.yaml b/test/snapshots/session/should_set_model_with_reasoningeffort.yaml index ccf204d2ae..db151e253a 100644 --- a/test/snapshots/session/should_set_model_with_reasoningeffort.yaml +++ b/test/snapshots/session/should_set_model_with_reasoningeffort.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 - gpt-5.4 conversations: - messages: diff --git a/test/snapshots/session_config/should_accept_blob_attachments.yaml b/test/snapshots/session_config/should_accept_blob_attachments.yaml index 672ca74d4e..71c7900348 100644 --- a/test/snapshots/session_config/should_accept_blob_attachments.yaml +++ b/test/snapshots/session_config/should_accept_blob_attachments.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_config/should_accept_message_attachments.yaml b/test/snapshots/session_config/should_accept_message_attachments.yaml index e9fbabb05e..188905752b 100644 --- a/test/snapshots/session_config/should_accept_message_attachments.yaml +++ b/test/snapshots/session_config/should_accept_message_attachments.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system @@ -61,7 +61,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. This file is attached + content: This file is attached - role: assistant content: |- The attached file contains a single line of text that says: "This file is attached" diff --git a/test/snapshots/session_config/should_apply_all_reasoning_effort_values_on_session_create.yaml b/test/snapshots/session_config/should_apply_all_reasoning_effort_values_on_session_create.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/session_config/should_apply_all_reasoning_effort_values_on_session_create.yaml +++ b/test/snapshots/session_config/should_apply_all_reasoning_effort_values_on_session_create.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/session_config/should_apply_availabletools_on_session_resume.yaml b/test/snapshots/session_config/should_apply_availabletools_on_session_resume.yaml index 250402101b..c8aa45cfff 100644 --- a/test/snapshots/session_config/should_apply_availabletools_on_session_resume.yaml +++ b/test/snapshots/session_config/should_apply_availabletools_on_session_resume.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_config/should_apply_excluded_built_in_agents_on_create.yaml b/test/snapshots/session_config/should_apply_excluded_built_in_agents_on_create.yaml index 3cbf86e981..4ef99a4d36 100644 --- a/test/snapshots/session_config/should_apply_excluded_built_in_agents_on_create.yaml +++ b/test/snapshots/session_config/should_apply_excluded_built_in_agents_on_create.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_config/should_apply_excluded_built_in_agents_on_resume.yaml b/test/snapshots/session_config/should_apply_excluded_built_in_agents_on_resume.yaml index 250402101b..c8aa45cfff 100644 --- a/test/snapshots/session_config/should_apply_excluded_built_in_agents_on_resume.yaml +++ b/test/snapshots/session_config/should_apply_excluded_built_in_agents_on_resume.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_config/should_apply_github_mcp_tool_config_on_create.yaml b/test/snapshots/session_config/should_apply_github_mcp_tool_config_on_create.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/session_config/should_apply_github_mcp_tool_config_on_create.yaml +++ b/test/snapshots/session_config/should_apply_github_mcp_tool_config_on_create.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/session_config/should_apply_instruction_directories_on_create.yaml b/test/snapshots/session_config/should_apply_instruction_directories_on_create.yaml index f9918fa133..fe4181ccd2 100644 --- a/test/snapshots/session_config/should_apply_instruction_directories_on_create.yaml +++ b/test/snapshots/session_config/should_apply_instruction_directories_on_create.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_config/should_apply_instruction_directories_on_resume.yaml b/test/snapshots/session_config/should_apply_instruction_directories_on_resume.yaml index 7c4d469970..dfead5f966 100644 --- a/test/snapshots/session_config/should_apply_instruction_directories_on_resume.yaml +++ b/test/snapshots/session_config/should_apply_instruction_directories_on_resume.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_config/should_apply_instructiondirectories_on_create.yaml b/test/snapshots/session_config/should_apply_instructiondirectories_on_create.yaml index 250402101b..c8aa45cfff 100644 --- a/test/snapshots/session_config/should_apply_instructiondirectories_on_create.yaml +++ b/test/snapshots/session_config/should_apply_instructiondirectories_on_create.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_config/should_apply_instructiondirectories_on_resume.yaml b/test/snapshots/session_config/should_apply_instructiondirectories_on_resume.yaml index f9918fa133..fe4181ccd2 100644 --- a/test/snapshots/session_config/should_apply_instructiondirectories_on_resume.yaml +++ b/test/snapshots/session_config/should_apply_instructiondirectories_on_resume.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_config/should_apply_instructiondirectories_on_session_create.yaml b/test/snapshots/session_config/should_apply_instructiondirectories_on_session_create.yaml index f9918fa133..fe4181ccd2 100644 --- a/test/snapshots/session_config/should_apply_instructiondirectories_on_session_create.yaml +++ b/test/snapshots/session_config/should_apply_instructiondirectories_on_session_create.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_config/should_apply_instructiondirectories_on_session_resume.yaml b/test/snapshots/session_config/should_apply_instructiondirectories_on_session_resume.yaml index 7c4d469970..dfead5f966 100644 --- a/test/snapshots/session_config/should_apply_instructiondirectories_on_session_resume.yaml +++ b/test/snapshots/session_config/should_apply_instructiondirectories_on_session_resume.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_config/should_apply_reasoning_effort_on_session_create.yaml b/test/snapshots/session_config/should_apply_reasoning_effort_on_session_create.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/session_config/should_apply_reasoning_effort_on_session_create.yaml +++ b/test/snapshots/session_config/should_apply_reasoning_effort_on_session_create.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/session_config/should_apply_session_limits_on_create.yaml b/test/snapshots/session_config/should_apply_session_limits_on_create.yaml index 904d69c872..142c4f6229 100644 --- a/test/snapshots/session_config/should_apply_session_limits_on_create.yaml +++ b/test/snapshots/session_config/should_apply_session_limits_on_create.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_config/should_apply_session_limits_on_resume.yaml b/test/snapshots/session_config/should_apply_session_limits_on_resume.yaml index 904d69c872..142c4f6229 100644 --- a/test/snapshots/session_config/should_apply_session_limits_on_resume.yaml +++ b/test/snapshots/session_config/should_apply_session_limits_on_resume.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_config/should_apply_systemmessage_on_session_resume.yaml b/test/snapshots/session_config/should_apply_systemmessage_on_session_resume.yaml index a455f6f6f3..a5111fd56a 100644 --- a/test/snapshots/session_config/should_apply_systemmessage_on_session_resume.yaml +++ b/test/snapshots/session_config/should_apply_systemmessage_on_session_resume.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_config/should_apply_workingdirectory_on_session_resume.yaml b/test/snapshots/session_config/should_apply_workingdirectory_on_session_resume.yaml index 52cc114f94..ec7d207e2e 100644 --- a/test/snapshots/session_config/should_apply_workingdirectory_on_session_resume.yaml +++ b/test/snapshots/session_config/should_apply_workingdirectory_on_session_resume.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system @@ -25,7 +25,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. I am in the resume working directory + content: I am in the resume working directory - role: assistant content: |- The file `resume-marker.txt` says: diff --git a/test/snapshots/session_config/should_create_session_with_custom_provider_config.yaml b/test/snapshots/session_config/should_create_session_with_custom_provider_config.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/session_config/should_create_session_with_custom_provider_config.yaml +++ b/test/snapshots/session_config/should_create_session_with_custom_provider_config.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/session_config/should_forward_clientname_in_user_agent.yaml b/test/snapshots/session_config/should_forward_clientname_in_user_agent.yaml index f9918fa133..fe4181ccd2 100644 --- a/test/snapshots/session_config/should_forward_clientname_in_user_agent.yaml +++ b/test/snapshots/session_config/should_forward_clientname_in_user_agent.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_config/should_forward_clientname_in_useragent.yaml b/test/snapshots/session_config/should_forward_clientname_in_useragent.yaml index 250402101b..c8aa45cfff 100644 --- a/test/snapshots/session_config/should_forward_clientname_in_useragent.yaml +++ b/test/snapshots/session_config/should_forward_clientname_in_useragent.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_config/should_forward_custom_provider_headers_on_create.yaml b/test/snapshots/session_config/should_forward_custom_provider_headers_on_create.yaml index 250402101b..c8aa45cfff 100644 --- a/test/snapshots/session_config/should_forward_custom_provider_headers_on_create.yaml +++ b/test/snapshots/session_config/should_forward_custom_provider_headers_on_create.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_config/should_forward_custom_provider_headers_on_resume.yaml b/test/snapshots/session_config/should_forward_custom_provider_headers_on_resume.yaml index 9fe2fcd070..7cd9232226 100644 --- a/test/snapshots/session_config/should_forward_custom_provider_headers_on_resume.yaml +++ b/test/snapshots/session_config/should_forward_custom_provider_headers_on_resume.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_config/should_use_custom_session_id.yaml b/test/snapshots/session_config/should_use_custom_session_id.yaml index 056351ddb4..0c6b353c19 100644 --- a/test/snapshots/session_config/should_use_custom_session_id.yaml +++ b/test/snapshots/session_config/should_use_custom_session_id.yaml @@ -1,3 +1,3 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: [] diff --git a/test/snapshots/session_config/should_use_provider_model_id_as_wire_model.yaml b/test/snapshots/session_config/should_use_provider_model_id_as_wire_model.yaml index 250402101b..c8aa45cfff 100644 --- a/test/snapshots/session_config/should_use_provider_model_id_as_wire_model.yaml +++ b/test/snapshots/session_config/should_use_provider_model_id_as_wire_model.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_config/should_use_workingdirectory_for_tool_execution.yaml b/test/snapshots/session_config/should_use_workingdirectory_for_tool_execution.yaml index 18dfab04e6..dd2b4592b3 100644 --- a/test/snapshots/session_config/should_use_workingdirectory_for_tool_execution.yaml +++ b/test/snapshots/session_config/should_use_workingdirectory_for_tool_execution.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system @@ -44,6 +44,6 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. I am in the subdirectory + content: I am in the subdirectory - role: assistant content: 'The file marker.txt says: "I am in the subdirectory"' diff --git a/test/snapshots/session_config/vision_disabled_then_enabled_via_setmodel.yaml b/test/snapshots/session_config/vision_disabled_then_enabled_via_setmodel.yaml index 1eb0acd729..a4000a80e6 100644 --- a/test/snapshots/session_config/vision_disabled_then_enabled_via_setmodel.yaml +++ b/test/snapshots/session_config/vision_disabled_then_enabled_via_setmodel.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_config/vision_enabled_then_disabled_via_setmodel.yaml b/test/snapshots/session_config/vision_enabled_then_disabled_via_setmodel.yaml index b987a4e630..6afc03cb05 100644 --- a/test/snapshots/session_config/vision_enabled_then_disabled_via_setmodel.yaml +++ b/test/snapshots/session_config/vision_enabled_then_disabled_via_setmodel.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_fs/should_load_session_data_from_fs_provider_on_resume.yaml b/test/snapshots/session_fs/should_load_session_data_from_fs_provider_on_resume.yaml index 4744667cd7..2d2d1e38ae 100644 --- a/test/snapshots/session_fs/should_load_session_data_from_fs_provider_on_resume.yaml +++ b/test/snapshots/session_fs/should_load_session_data_from_fs_provider_on_resume.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_fs/should_map_large_output_handling_into_sessionfs.yaml b/test/snapshots/session_fs/should_map_large_output_handling_into_sessionfs.yaml index e80ce51e64..9b8f1eb0d9 100644 --- a/test/snapshots/session_fs/should_map_large_output_handling_into_sessionfs.yaml +++ b/test/snapshots/session_fs/should_map_large_output_handling_into_sessionfs.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_fs/should_persist_plan_md_via_sessionfs.yaml b/test/snapshots/session_fs/should_persist_plan_md_via_sessionfs.yaml index 5b0e81b22d..90ceee231a 100644 --- a/test/snapshots/session_fs/should_persist_plan_md_via_sessionfs.yaml +++ b/test/snapshots/session_fs/should_persist_plan_md_via_sessionfs.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_fs/should_reject_setprovider_when_sessions_already_exist.yaml b/test/snapshots/session_fs/should_reject_setprovider_when_sessions_already_exist.yaml index 269a80f11a..9aba0fefe1 100644 --- a/test/snapshots/session_fs/should_reject_setprovider_when_sessions_already_exist.yaml +++ b/test/snapshots/session_fs/should_reject_setprovider_when_sessions_already_exist.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_fs/should_route_file_operations_through_the_session_fs_provider.yaml b/test/snapshots/session_fs/should_route_file_operations_through_the_session_fs_provider.yaml index 455652bfd8..086c69225d 100644 --- a/test/snapshots/session_fs/should_route_file_operations_through_the_session_fs_provider.yaml +++ b/test/snapshots/session_fs/should_route_file_operations_through_the_session_fs_provider.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_fs/should_succeed_with_compaction_while_using_sessionfs.yaml b/test/snapshots/session_fs/should_succeed_with_compaction_while_using_sessionfs.yaml index 5e62634668..67f8e0121e 100644 --- a/test/snapshots/session_fs/should_succeed_with_compaction_while_using_sessionfs.yaml +++ b/test/snapshots/session_fs/should_succeed_with_compaction_while_using_sessionfs.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_fs/should_write_workspace_metadata_via_sessionfs.yaml b/test/snapshots/session_fs/should_write_workspace_metadata_via_sessionfs.yaml index 0a0325417c..59ed4624c7 100644 --- a/test/snapshots/session_fs/should_write_workspace_metadata_via_sessionfs.yaml +++ b/test/snapshots/session_fs/should_write_workspace_metadata_via_sessionfs.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_fs_sqlite/should_allow_subagents_to_use_sql_tool_via_inherited_sessionfs.yaml b/test/snapshots/session_fs_sqlite/should_allow_subagents_to_use_sql_tool_via_inherited_sessionfs.yaml index 32b8bc354f..b2b50348f5 100644 --- a/test/snapshots/session_fs_sqlite/should_allow_subagents_to_use_sql_tool_via_inherited_sessionfs.yaml +++ b/test/snapshots/session_fs_sqlite/should_allow_subagents_to_use_sql_tool_via_inherited_sessionfs.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_fs_sqlite/should_route_sql_queries_through_the_sessionfs_sqlite_handler.yaml b/test/snapshots/session_fs_sqlite/should_route_sql_queries_through_the_sessionfs_sqlite_handler.yaml index b26cfbfebe..e51bc5d247 100644 --- a/test/snapshots/session_fs_sqlite/should_route_sql_queries_through_the_sessionfs_sqlite_handler.yaml +++ b/test/snapshots/session_fs_sqlite/should_route_sql_queries_through_the_sessionfs_sqlite_handler.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_lifecycle/should_delete_session_permanently.yaml b/test/snapshots/session_lifecycle/should_delete_session_permanently.yaml index b302f117d4..6e38f93031 100644 --- a/test/snapshots/session_lifecycle/should_delete_session_permanently.yaml +++ b/test/snapshots/session_lifecycle/should_delete_session_permanently.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_lifecycle/should_isolate_events_between_concurrent_sessions.yaml b/test/snapshots/session_lifecycle/should_isolate_events_between_concurrent_sessions.yaml index f4add013a1..47bd9bc305 100644 --- a/test/snapshots/session_lifecycle/should_isolate_events_between_concurrent_sessions.yaml +++ b/test/snapshots/session_lifecycle/should_isolate_events_between_concurrent_sessions.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_lifecycle/should_list_created_sessions_after_sending_a_message.yaml b/test/snapshots/session_lifecycle/should_list_created_sessions_after_sending_a_message.yaml index 274ab7d2fa..f39bcb95cd 100644 --- a/test/snapshots/session_lifecycle/should_list_created_sessions_after_sending_a_message.yaml +++ b/test/snapshots/session_lifecycle/should_list_created_sessions_after_sending_a_message.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_lifecycle/should_return_events_via_getmessages_after_conversation.yaml b/test/snapshots/session_lifecycle/should_return_events_via_getmessages_after_conversation.yaml index fd621f2b0b..005604fdd6 100644 --- a/test/snapshots/session_lifecycle/should_return_events_via_getmessages_after_conversation.yaml +++ b/test/snapshots/session_lifecycle/should_return_events_via_getmessages_after_conversation.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_lifecycle/should_support_multiple_concurrent_sessions.yaml b/test/snapshots/session_lifecycle/should_support_multiple_concurrent_sessions.yaml index fdb7ebca03..180fb52406 100644 --- a/test/snapshots/session_lifecycle/should_support_multiple_concurrent_sessions.yaml +++ b/test/snapshots/session_lifecycle/should_support_multiple_concurrent_sessions.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/session_todos_changed/fires_session_todos_changed_and_exposes_rows_and_dependencies.yaml b/test/snapshots/session_todos_changed/fires_session_todos_changed_and_exposes_rows_and_dependencies.yaml index 425c772fce..0a4b41c9f2 100644 --- a/test/snapshots/session_todos_changed/fires_session_todos_changed_and_exposes_rows_and_dependencies.yaml +++ b/test/snapshots/session_todos_changed/fires_session_todos_changed_and_exposes_rows_and_dependencies.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/skills/should_allow_agent_with_skills_to_invoke_skill.yaml b/test/snapshots/skills/should_allow_agent_with_skills_to_invoke_skill.yaml index 007c5c1c53..fcecde4286 100644 --- a/test/snapshots/skills/should_allow_agent_with_skills_to_invoke_skill.yaml +++ b/test/snapshots/skills/should_allow_agent_with_skills_to_invoke_skill.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/skills/should_load_and_apply_skill_from_skilldirectories.yaml b/test/snapshots/skills/should_load_and_apply_skill_from_skilldirectories.yaml index 38b35946b0..ee9e6452d2 100644 --- a/test/snapshots/skills/should_load_and_apply_skill_from_skilldirectories.yaml +++ b/test/snapshots/skills/should_load_and_apply_skill_from_skilldirectories.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/skills/should_not_apply_skill_when_disabled_via_disabledskills.yaml b/test/snapshots/skills/should_not_apply_skill_when_disabled_via_disabledskills.yaml index 0c678deab8..1cc5776a34 100644 --- a/test/snapshots/skills/should_not_apply_skill_when_disabled_via_disabledskills.yaml +++ b/test/snapshots/skills/should_not_apply_skill_when_disabled_via_disabledskills.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/skills/should_not_provide_skills_to_agent_without_skills_field.yaml b/test/snapshots/skills/should_not_provide_skills_to_agent_without_skills_field.yaml index 0c678deab8..1cc5776a34 100644 --- a/test/snapshots/skills/should_not_provide_skills_to_agent_without_skills_field.yaml +++ b/test/snapshots/skills/should_not_provide_skills_to_agent_without_skills_field.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/streaming_fidelity/should_emit_assistantmessagestart_before_deltas_with_matching_messageid.yaml b/test/snapshots/streaming_fidelity/should_emit_assistantmessagestart_before_deltas_with_matching_messageid.yaml index 3b18558220..c6d74510f4 100644 --- a/test/snapshots/streaming_fidelity/should_emit_assistantmessagestart_before_deltas_with_matching_messageid.yaml +++ b/test/snapshots/streaming_fidelity/should_emit_assistantmessagestart_before_deltas_with_matching_messageid.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/streaming_fidelity/should_not_produce_deltas_after_session_resume_with_streaming_disabled.yaml b/test/snapshots/streaming_fidelity/should_not_produce_deltas_after_session_resume_with_streaming_disabled.yaml index 25e10c4b12..ca514e0135 100644 --- a/test/snapshots/streaming_fidelity/should_not_produce_deltas_after_session_resume_with_streaming_disabled.yaml +++ b/test/snapshots/streaming_fidelity/should_not_produce_deltas_after_session_resume_with_streaming_disabled.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/streaming_fidelity/should_not_produce_deltas_when_streaming_is_disabled.yaml b/test/snapshots/streaming_fidelity/should_not_produce_deltas_when_streaming_is_disabled.yaml index d210f22ea0..c9c9d1200f 100644 --- a/test/snapshots/streaming_fidelity/should_not_produce_deltas_when_streaming_is_disabled.yaml +++ b/test/snapshots/streaming_fidelity/should_not_produce_deltas_when_streaming_is_disabled.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/streaming_fidelity/should_produce_delta_events_when_streaming_is_enabled.yaml b/test/snapshots/streaming_fidelity/should_produce_delta_events_when_streaming_is_enabled.yaml index 3b18558220..c6d74510f4 100644 --- a/test/snapshots/streaming_fidelity/should_produce_delta_events_when_streaming_is_enabled.yaml +++ b/test/snapshots/streaming_fidelity/should_produce_delta_events_when_streaming_is_enabled.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/streaming_fidelity/should_produce_deltas_after_session_resume.yaml b/test/snapshots/streaming_fidelity/should_produce_deltas_after_session_resume.yaml index 25e10c4b12..ca514e0135 100644 --- a/test/snapshots/streaming_fidelity/should_produce_deltas_after_session_resume.yaml +++ b/test/snapshots/streaming_fidelity/should_produce_deltas_after_session_resume.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/subagent_hooks/should_invoke_pretooluse_and_posttooluse_hooks_for_sub_agent_tool_calls.yaml b/test/snapshots/subagent_hooks/should_invoke_pretooluse_and_posttooluse_hooks_for_sub_agent_tool_calls.yaml index 2a73f1ef84..4c5419a0ab 100644 --- a/test/snapshots/subagent_hooks/should_invoke_pretooluse_and_posttooluse_hooks_for_sub_agent_tool_calls.yaml +++ b/test/snapshots/subagent_hooks/should_invoke_pretooluse_and_posttooluse_hooks_for_sub_agent_tool_calls.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system @@ -25,38 +25,6 @@ conversations: arguments: '{"agent_type":"explore","name":"read-file","description":"Reading subagent-test.txt","prompt":"Read the file \"subagent-test.txt\" in the current directory (${workdir}) and report its complete contents. Use the view tool to read the file and provide the full content in your response.","mode":"background"}' - - messages: - - role: system - content: ${system} - - role: user - content: Use the task tool to spawn an explore agent that reads the file subagent-test.txt in the current directory and - reports its contents. You must use the task tool. - - role: assistant - content: I'll spawn an explore agent to read the file and report its contents. - tool_calls: - - id: toolcall_0 - type: function - function: - name: report_intent - arguments: '{"intent":"Spawning explore agent"}' - - id: toolcall_1 - type: function - function: - name: task - arguments: '{"agent_type":"explore","name":"read-file","description":"Reading subagent-test.txt","prompt":"Read the file - \"subagent-test.txt\" in the current directory (${workdir}) and report its complete contents. Use the - view tool to read the file and provide the full content in your response.","mode":"background"}' - - role: tool - tool_call_id: toolcall_0 - content: Tool 'report_intent' does not exist. Available tools that can be called are ${shell}, ${read_shell}, - ${stop_shell}, ${list_shell}, view, create, edit, web_fetch, skill, sql, read_agent, list_agents, grep, glob, - task. - - role: tool - tool_call_id: toolcall_1 - content: "Agent started in background with agent_id: read-file. You'll be notified when it completes. Tell the user - you're waiting and end your response, or continue unrelated work until notified." - - role: assistant - content: I've launched an explore agent to read subagent-test.txt. Waiting for it to complete... - messages: - role: system content: ${system} @@ -88,7 +56,7 @@ conversations: arguments: '{"path":"${workdir}/subagent-test.txt"}' - role: tool tool_call_id: toolcall_0 - content: 1. Hello from subagent test! + content: Hello from subagent test! - role: assistant content: |- The complete contents of the file "subagent-test.txt" are: diff --git a/test/snapshots/suspend/should_allow_resume_and_continue_conversation_after_suspend.yaml b/test/snapshots/suspend/should_allow_resume_and_continue_conversation_after_suspend.yaml index c033a6cba1..5230710868 100644 --- a/test/snapshots/suspend/should_allow_resume_and_continue_conversation_after_suspend.yaml +++ b/test/snapshots/suspend/should_allow_resume_and_continue_conversation_after_suspend.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/suspend/should_cancel_pending_permission_request_when_suspending.yaml b/test/snapshots/suspend/should_cancel_pending_permission_request_when_suspending.yaml index 97939357ca..54e64b3edb 100644 --- a/test/snapshots/suspend/should_cancel_pending_permission_request_when_suspending.yaml +++ b/test/snapshots/suspend/should_cancel_pending_permission_request_when_suspending.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/suspend/should_reject_pending_external_tool_when_suspending.yaml b/test/snapshots/suspend/should_reject_pending_external_tool_when_suspending.yaml index 32e07aa5d5..3bf332978d 100644 --- a/test/snapshots/suspend/should_reject_pending_external_tool_when_suspending.yaml +++ b/test/snapshots/suspend/should_reject_pending_external_tool_when_suspending.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/suspend/should_suspend_idle_session_without_throwing.yaml b/test/snapshots/suspend/should_suspend_idle_session_without_throwing.yaml index a3a35bf25b..d853722e5b 100644 --- a/test/snapshots/suspend/should_suspend_idle_session_without_throwing.yaml +++ b/test/snapshots/suspend/should_suspend_idle_session_without_throwing.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/system_message_sections/should_use_replaced_identity_section_in_response.yaml b/test/snapshots/system_message_sections/should_use_replaced_identity_section_in_response.yaml index 5721955d27..e53acdf159 100644 --- a/test/snapshots/system_message_sections/should_use_replaced_identity_section_in_response.yaml +++ b/test/snapshots/system_message_sections/should_use_replaced_identity_section_in_response.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/system_message_sections/should_use_replaced_preamble_section_in_response.yaml b/test/snapshots/system_message_sections/should_use_replaced_preamble_section_in_response.yaml index 9d2c688c18..f73112abb5 100644 --- a/test/snapshots/system_message_sections/should_use_replaced_preamble_section_in_response.yaml +++ b/test/snapshots/system_message_sections/should_use_replaced_preamble_section_in_response.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system @@ -13,5 +13,5 @@ conversations: watering, pests, plant identification, or growing tips, I'm here to help! - I'm powered by claude-sonnet-4.5, but I focus specifically on gardening topics. What plant or gardening + I'm powered by claude-sonnet-5, but I focus specifically on gardening topics. What plant or gardening question can I help you with today? diff --git a/test/snapshots/system_message_transform/should_apply_transform_modifications_to_section_content.yaml b/test/snapshots/system_message_transform/should_apply_transform_modifications_to_section_content.yaml index 4b7c058b27..4db03cac93 100644 --- a/test/snapshots/system_message_transform/should_apply_transform_modifications_to_section_content.yaml +++ b/test/snapshots/system_message_transform/should_apply_transform_modifications_to_section_content.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system @@ -26,7 +26,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. Hello! + content: Hello! - role: assistant content: |- The file **hello.txt** contains: diff --git a/test/snapshots/system_message_transform/should_invoke_transform_callbacks_with_section_content.yaml b/test/snapshots/system_message_transform/should_invoke_transform_callbacks_with_section_content.yaml index 0b1d9755f0..93b7221aa3 100644 --- a/test/snapshots/system_message_transform/should_invoke_transform_callbacks_with_section_content.yaml +++ b/test/snapshots/system_message_transform/should_invoke_transform_callbacks_with_section_content.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system @@ -47,6 +47,6 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. Hello transform! + content: Hello transform! - role: assistant content: 'The file `test.txt` contains: **"Hello transform!"**' diff --git a/test/snapshots/system_message_transform/should_work_with_static_overrides_and_transforms_together.yaml b/test/snapshots/system_message_transform/should_work_with_static_overrides_and_transforms_together.yaml index 0681b569dd..94c50ff0bd 100644 --- a/test/snapshots/system_message_transform/should_work_with_static_overrides_and_transforms_together.yaml +++ b/test/snapshots/system_message_transform/should_work_with_static_overrides_and_transforms_together.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system @@ -47,7 +47,7 @@ conversations: task. - role: tool tool_call_id: toolcall_1 - content: 1. Combo test! + content: Combo test! - role: assistant content: |- The file `combo.txt` contains: diff --git a/test/snapshots/telemetry/should_export_file_telemetry_for_sdk_interactions.yaml b/test/snapshots/telemetry/should_export_file_telemetry_for_sdk_interactions.yaml index f8342047b7..c919c8d0b1 100644 --- a/test/snapshots/telemetry/should_export_file_telemetry_for_sdk_interactions.yaml +++ b/test/snapshots/telemetry/should_export_file_telemetry_for_sdk_interactions.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/tool_results/should_handle_structured_toolresultobject_from_custom_tool.yaml b/test/snapshots/tool_results/should_handle_structured_toolresultobject_from_custom_tool.yaml index 621dfc4e8d..0992eae919 100644 --- a/test/snapshots/tool_results/should_handle_structured_toolresultobject_from_custom_tool.yaml +++ b/test/snapshots/tool_results/should_handle_structured_toolresultobject_from_custom_tool.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/tool_results/should_handle_tool_result_with_denied_resulttype.yaml b/test/snapshots/tool_results/should_handle_tool_result_with_denied_resulttype.yaml index 6a829fb23f..3658586343 100644 --- a/test/snapshots/tool_results/should_handle_tool_result_with_denied_resulttype.yaml +++ b/test/snapshots/tool_results/should_handle_tool_result_with_denied_resulttype.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/tool_results/should_handle_tool_result_with_failure_resulttype.yaml b/test/snapshots/tool_results/should_handle_tool_result_with_failure_resulttype.yaml index 3fddb1600b..b328558471 100644 --- a/test/snapshots/tool_results/should_handle_tool_result_with_failure_resulttype.yaml +++ b/test/snapshots/tool_results/should_handle_tool_result_with_failure_resulttype.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/tool_results/should_handle_tool_result_with_rejected_resulttype.yaml b/test/snapshots/tool_results/should_handle_tool_result_with_rejected_resulttype.yaml index 891f75cb55..d329be915e 100644 --- a/test/snapshots/tool_results/should_handle_tool_result_with_rejected_resulttype.yaml +++ b/test/snapshots/tool_results/should_handle_tool_result_with_rejected_resulttype.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/tool_results/should_pass_validated_zod_parameters_to_tool_handler.yaml b/test/snapshots/tool_results/should_pass_validated_zod_parameters_to_tool_handler.yaml index 2388d7d8fd..6c8ee43bb9 100644 --- a/test/snapshots/tool_results/should_pass_validated_zod_parameters_to_tool_handler.yaml +++ b/test/snapshots/tool_results/should_pass_validated_zod_parameters_to_tool_handler.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/tool_results/should_preserve_tooltelemetry_and_not_stringify_structured_results_for_llm.yaml b/test/snapshots/tool_results/should_preserve_tooltelemetry_and_not_stringify_structured_results_for_llm.yaml index 71021d3b8d..bc65d05d6b 100644 --- a/test/snapshots/tool_results/should_preserve_tooltelemetry_and_not_stringify_structured_results_for_llm.yaml +++ b/test/snapshots/tool_results/should_preserve_tooltelemetry_and_not_stringify_structured_results_for_llm.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/tools/can_receive_and_return_complex_types.yaml b/test/snapshots/tools/can_receive_and_return_complex_types.yaml index be869484ea..982f66e3ad 100644 --- a/test/snapshots/tools/can_receive_and_return_complex_types.yaml +++ b/test/snapshots/tools/can_receive_and_return_complex_types.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 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 0000000000..39d2bed2f0 --- /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-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 diff --git a/test/snapshots/tools/denies_custom_tool_when_permission_denied.yaml b/test/snapshots/tools/denies_custom_tool_when_permission_denied.yaml index 47f9286e0c..59acd092aa 100644 --- a/test/snapshots/tools/denies_custom_tool_when_permission_denied.yaml +++ b/test/snapshots/tools/denies_custom_tool_when_permission_denied.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/tools/ergonomic_tool_arity0.yaml b/test/snapshots/tools/ergonomic_tool_arity0.yaml index a55f486816..16bbbf1cd6 100644 --- a/test/snapshots/tools/ergonomic_tool_arity0.yaml +++ b/test/snapshots/tools/ergonomic_tool_arity0.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/tools/ergonomic_tool_arity2.yaml b/test/snapshots/tools/ergonomic_tool_arity2.yaml index e34c695bd4..b6cdd07614 100644 --- a/test/snapshots/tools/ergonomic_tool_arity2.yaml +++ b/test/snapshots/tools/ergonomic_tool_arity2.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/tools/ergonomic_tool_definition.yaml b/test/snapshots/tools/ergonomic_tool_definition.yaml index ebb05ce1b9..e61e95f930 100644 --- a/test/snapshots/tools/ergonomic_tool_definition.yaml +++ b/test/snapshots/tools/ergonomic_tool_definition.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/tools/handles_tool_calling_errors.yaml b/test/snapshots/tools/handles_tool_calling_errors.yaml index 33226722dd..3b85da9acd 100644 --- a/test/snapshots/tools/handles_tool_calling_errors.yaml +++ b/test/snapshots/tools/handles_tool_calling_errors.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/tools/invokes_built_in_tools.yaml b/test/snapshots/tools/invokes_built_in_tools.yaml index 068cc4accf..72d959be46 100644 --- a/test/snapshots/tools/invokes_built_in_tools.yaml +++ b/test/snapshots/tools/invokes_built_in_tools.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system @@ -15,6 +15,6 @@ conversations: arguments: '{"path":"${workdir}/README.md"}' - role: tool tool_call_id: toolcall_0 - content: "1. # ELIZA, the only chatbot you'll ever need" + content: "# ELIZA, the only chatbot you'll ever need" - role: assistant content: "The first line of README.md is: `# ELIZA, the only chatbot you'll ever need`" diff --git a/test/snapshots/tools/invokes_custom_tool.yaml b/test/snapshots/tools/invokes_custom_tool.yaml index 6f212e4a79..6362a5e4f7 100644 --- a/test/snapshots/tools/invokes_custom_tool.yaml +++ b/test/snapshots/tools/invokes_custom_tool.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/tools/invokes_custom_tool_with_permission_handler.yaml b/test/snapshots/tools/invokes_custom_tool_with_permission_handler.yaml index fcb6fa7266..2cfccba989 100644 --- a/test/snapshots/tools/invokes_custom_tool_with_permission_handler.yaml +++ b/test/snapshots/tools/invokes_custom_tool_with_permission_handler.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/tools/low_level_tool_definition.yaml b/test/snapshots/tools/low_level_tool_definition.yaml index 03cb0748a2..5921d061cb 100644 --- a/test/snapshots/tools/low_level_tool_definition.yaml +++ b/test/snapshots/tools/low_level_tool_definition.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/tools/overrides_built_in_tool_with_custom_tool.yaml b/test/snapshots/tools/overrides_built_in_tool_with_custom_tool.yaml index 5410d3f295..ec2fa0a9a7 100644 --- a/test/snapshots/tools/overrides_built_in_tool_with_custom_tool.yaml +++ b/test/snapshots/tools/overrides_built_in_tool_with_custom_tool.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/tools/should_execute_multiple_custom_tools_in_parallel_single_turn.yaml b/test/snapshots/tools/should_execute_multiple_custom_tools_in_parallel_single_turn.yaml index a9aae3aea5..949ffd18fa 100644 --- a/test/snapshots/tools/should_execute_multiple_custom_tools_in_parallel_single_turn.yaml +++ b/test/snapshots/tools/should_execute_multiple_custom_tools_in_parallel_single_turn.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/tools/should_respect_availabletools_and_excludedtools_combined.yaml b/test/snapshots/tools/should_respect_availabletools_and_excludedtools_combined.yaml index cf0cf564da..50494f2324 100644 --- a/test/snapshots/tools/should_respect_availabletools_and_excludedtools_combined.yaml +++ b/test/snapshots/tools/should_respect_availabletools_and_excludedtools_combined.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system diff --git a/test/snapshots/tools/skippermission_sent_in_tool_definition.yaml b/test/snapshots/tools/skippermission_sent_in_tool_definition.yaml index dfdfa63fa7..fa7a5cfbe1 100644 --- a/test/snapshots/tools/skippermission_sent_in_tool_definition.yaml +++ b/test/snapshots/tools/skippermission_sent_in_tool_definition.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - claude-sonnet-5 conversations: - messages: - role: system